diff --git a/codex/codex.nim b/codex/codex.nim index ce353814..55fc1965 100644 --- a/codex/codex.nim +++ b/codex/codex.nim @@ -94,7 +94,10 @@ proc stop*(s: CodexServer) {.async.} = s.runHandle.complete() -proc new(_: type ContractInteractions, config: CodexConf): ?ContractInteractions = +proc new(_: type ContractInteractions, + config: CodexConf, + repo: RepoStore): Contracts = + if not config.persistence: if config.ethAccount.isSome: warn "Ethereum account was set, but persistence is not enabled" @@ -104,10 +107,16 @@ proc new(_: type ContractInteractions, config: CodexConf): ?ContractInteractions error "Persistence enabled, but no Ethereum account was set" quit QuitFailure + var client: ?ClientInteractions + var host: ?HostInteractions if deployment =? config.ethDeployment: - ContractInteractions.new(config.ethProvider, account, deployment) + client = ClientInteractions.new(config.ethProvider, account, deployment) + host = HostInteractions.new(config.ethProvider, account, repo, deployment) else: - ContractInteractions.new(config.ethProvider, account) + client = ClientInteractions.new(config.ethProvider, account) + host = HostInteractions.new(config.ethProvider, account, repo) + + (client, host) proc new*(T: type CodexServer, config: CodexConf, privateKey: CodexPrivateKey): T = diff --git a/codex/contracts/interactions.nim b/codex/contracts/interactions.nim index 4cf16492..de77348b 100644 --- a/codex/contracts/interactions.nim +++ b/codex/contracts/interactions.nim @@ -1,78 +1,5 @@ -import pkg/ethers -import pkg/chronicles -import ../purchasing -import ../sales -import ../proving -import ./deployment -import ./marketplace -import ./market -import ./proofs -import ./clock +import ./interactions/interactions +import ./interactions/hostinteractions +import ./interactions/clientinteractions -export purchasing -export sales -export proving -export chronicles - -type - ContractInteractions* = ref object - purchasing*: Purchasing - sales*: Sales - proving*: Proving - clock: OnChainClock - -proc new*(_: type ContractInteractions, - signer: Signer, - deployment: Deployment): ?ContractInteractions = - - without address =? deployment.address(Marketplace): - error "Unable to determine address of the Marketplace smart contract" - return none ContractInteractions - - let contract = Marketplace.new(address, signer) - let market = OnChainMarket.new(contract) - let proofs = OnChainProofs.new(contract) - let clock = OnChainClock.new(signer.provider) - let proving = Proving.new(proofs, clock) - some ContractInteractions( - purchasing: Purchasing.new(market, clock), - sales: Sales.new(market, clock, proving), - proving: proving, - clock: clock - ) - -proc new*(_: type ContractInteractions, - providerUrl: string, - account: Address, - deploymentFile: string = string.default): ?ContractInteractions = - - let provider = JsonRpcProvider.new(providerUrl) - let signer = provider.getSigner(account) - - var deploy: Deployment - try: - if deploymentFile == string.default: - deploy = deployment() - else: - deploy = deployment(deploymentFile) - except IOError as e: - error "Unable to read deployment json", msg = e.msg - return none ContractInteractions - - ContractInteractions.new(signer, deploy) - -proc new*(_: type ContractInteractions, - account: Address): ?ContractInteractions = - ContractInteractions.new("ws://localhost:8545", account) - -proc start*(interactions: ContractInteractions) {.async.} = - await interactions.clock.start() - await interactions.sales.start() - await interactions.proving.start() - await interactions.purchasing.start() - -proc stop*(interactions: ContractInteractions) {.async.} = - await interactions.purchasing.stop() - await interactions.sales.stop() - await interactions.proving.stop() - await interactions.clock.stop() +export interactions, hostinteractions, clientinteractions \ No newline at end of file diff --git a/codex/contracts/interactions/clientinteractions.nim b/codex/contracts/interactions/clientinteractions.nim new file mode 100644 index 00000000..a71aa46f --- /dev/null +++ b/codex/contracts/interactions/clientinteractions.nim @@ -0,0 +1,55 @@ +import pkg/ethers +import pkg/chronicles + +import ../../purchasing +import ../deployment +import ../marketplace +import ../market +import ../proofs +import ../clock +import ./interactions + +export purchasing +export chronicles + +type + ClientInteractions* = ref object of ContractInteractions + purchasing*: Purchasing + +proc new*(_: type ClientInteractions, + signer: Signer, + deployment: Deployment): ?ClientInteractions = + + without address =? deployment.address(Marketplace): + error "Unable to determine address of the Marketplace smart contract" + return none ClientInteractions + + let contract = Marketplace.new(address, signer) + let market = OnChainMarket.new(contract) + let clock = OnChainClock.new(signer.provider) + + let c = ClientInteractions.new(clock) + c.purchasing = Purchasing.new(market, clock) + some c + +proc new*(_: type ClientInteractions, + providerUrl: string, + account: Address, + deploymentFile: string = string.default): ?ClientInteractions = + + without prepared =? prepare(providerUrl, account, deploymentFile): + return none ClientInteractions + + ClientInteractions.new(prepared.signer, prepared.deploy) + +proc new*(_: type ClientInteractions, + account: Address): ?ClientInteractions = + ClientInteractions.new("ws://localhost:8545", account) + +proc start*(self: ClientInteractions) {.async.} = + await self.purchasing.start() + await procCall ContractInteractions(self).start() + +proc stop*(self: ClientInteractions) {.async.} = + await self.purchasing.stop() + await procCall ContractInteractions(self).stop() diff --git a/codex/contracts/interactions/hostinteractions.nim b/codex/contracts/interactions/hostinteractions.nim new file mode 100644 index 00000000..cf69bb9c --- /dev/null +++ b/codex/contracts/interactions/hostinteractions.nim @@ -0,0 +1,67 @@ +import pkg/ethers +import pkg/chronicles + +import ../../sales +import ../../proving +import ../../stores +import ../deployment +import ../marketplace +import ../market +import ../proofs +import ../clock +import ./interactions + +export sales +export proving +export chronicles + +type + HostInteractions* = ref object of ContractInteractions + sales*: Sales + proving*: Proving + +proc new*(_: type HostInteractions, + signer: Signer, + deployment: Deployment, + repoStore: RepoStore): ?HostInteractions = + + without address =? deployment.address(Marketplace): + error "Unable to determine address of the Marketplace smart contract" + return none HostInteractions + + let contract = Marketplace.new(address, signer) + let market = OnChainMarket.new(contract) + let proofs = OnChainProofs.new(contract) + let clock = OnChainClock.new(signer.provider) + let proving = Proving.new(proofs, clock) + + let h = HostInteractions.new(clock) + h.sales = Sales.new(market, clock, proving, repoStore) + h.proving = proving + some h + +proc new*(_: type HostInteractions, + providerUrl: string, + account: Address, + repo: RepoStore, + deploymentFile: string = string.default): ?HostInteractions = + + without prepared =? prepare(providerUrl, account, deploymentFile): + return none HostInteractions + + HostInteractions.new(prepared.signer, prepared.deploy, repo) + +proc new*(_: type HostInteractions, + account: Address, + repo: RepoStore): ?HostInteractions = + HostInteractions.new("ws://localhost:8545", account, repo) + +method start*(self: HostInteractions) {.async.} = + await self.sales.start() + await self.proving.start() + await procCall ContractInteractions(self).start() + +method stop*(self: HostInteractions) {.async.} = + await self.sales.stop() + await self.proving.stop() + await procCall ContractInteractions(self).start() diff --git a/codex/contracts/interactions/interactions.nim b/codex/contracts/interactions/interactions.nim new file mode 100644 index 00000000..a8101492 --- /dev/null +++ b/codex/contracts/interactions/interactions.nim @@ -0,0 +1,45 @@ +import pkg/ethers +import pkg/chronicles +import pkg/questionable +import pkg/questionable/results +import ../../errors +import ../deployment +import ../clock + +type + ContractInteractions* = ref object of RootObj + clock: OnChainClock + ContractInteractionsError* = object of CodexError + ReadDeploymentFileFailureError* = object of ContractInteractionsError + +method new*[T: ContractInteractions](_: type T, clock: OnChainClock): T {.base.} = + T(clock: clock) + +proc prepare*( + providerUrl: string = "ws://localhost:8545", + account: Address, + deploymentFile: string = string.default): + ?!tuple[signer: JsonRpcSigner, deploy: Deployment] = + + let provider = JsonRpcProvider.new(providerUrl) + let signer = provider.getSigner(account) + + var deploy: Deployment + try: + if deploymentFile == string.default: + deploy = deployment() + else: + deploy = deployment(deploymentFile) + except IOError as e: + let err = newException(ReadDeploymentFileFailureError, + "Unable to read deployment json") + err.parent = e + return failure(err) + + return success((signer, deploy)) + +method start*(interactions: ContractInteractions) {.async, base.} = + await interactions.clock.start() + +method stop*(interactions: ContractInteractions) {.async, base.} = + await interactions.clock.stop() diff --git a/codex/node.nim b/codex/node.nim index b7d4af43..7ceaf404 100644 --- a/codex/node.nim +++ b/codex/node.nim @@ -43,6 +43,10 @@ type CodexError = object of CatchableError + Contracts* = tuple + client: ?ClientInteractions + host: ?HostInteractions + CodexNodeRef* = ref object switch*: Switch networkId*: PeerID @@ -50,7 +54,7 @@ type engine*: BlockExcEngine erasure*: Erasure discovery*: Discovery - contracts*: ?ContractInteractions + contracts*: Contracts proc findPeer*( node: CodexNodeRef, @@ -250,7 +254,7 @@ proc requestStorage*(self: CodexNodeRef, ## trace "Received a request for storage!", cid, duration, nodes, tolerance, reward - without contracts =? self.contracts: + without contracts =? self.contracts.client: trace "Purchasing not available" return failure "Purchasing not available" @@ -307,7 +311,7 @@ proc new*( engine: BlockExcEngine, erasure: Erasure, discovery: Discovery, - contracts = ContractInteractions.none): T = + contracts: Contracts = (ClientInteractions.none, HostInteractions.none)): T = T( switch: switch, blockStore: store, @@ -329,7 +333,7 @@ proc start*(node: CodexNodeRef) {.async.} = if not node.discovery.isNil: await node.discovery.start() - if contracts =? node.contracts: + if contracts =? node.contracts.host: # TODO: remove Sales callbacks, pass BlockStore and StorageProofs instead contracts.sales.onStore = proc(request: StorageRequest, slot: UInt256, @@ -369,7 +373,7 @@ proc start*(node: CodexNodeRef) {.async.} = await contracts.start() except CatchableError as error: error "Unable to start contract interactions: ", error=error.msg - node.contracts = ContractInteractions.none + node.contracts.host = HostInteractions.none node.networkId = node.switch.peerInfo.peerId notice "Started codex node", id = $node.networkId, addrs = node.switch.peerInfo.addrs @@ -389,8 +393,11 @@ proc stop*(node: CodexNodeRef) {.async.} = if not node.discovery.isNil: await node.discovery.stop() - if contracts =? node.contracts: - await contracts.stop() + if clientContracts =? node.contracts.client: + await clientContracts.stop() + + if hostContracts =? node.contracts.host: + await hostContracts.stop() if not node.blockStore.isNil: await node.blockStore.close diff --git a/codex/rest/api.nim b/codex/rest/api.nim index 88ed490c..f293e862 100644 --- a/codex/rest/api.nim +++ b/codex/rest/api.nim @@ -244,7 +244,7 @@ proc initRestApi*(node: CodexNodeRef, conf: CodexConf): RestRouter = "/api/codex/v1/sales/availability") do () -> RestApiResponse: ## Returns storage that is for sale - without contracts =? node.contracts: + without contracts =? node.contracts.host: return RestApiResponse.error(Http503, "Sales unavailable") let json = %contracts.sales.available @@ -259,7 +259,7 @@ proc initRestApi*(node: CodexNodeRef, conf: CodexConf): RestRouter = ## duration - maximum time the storage should be sold for (in seconds) ## minPrice - minimum price to be paid (in amount of tokens) - without contracts =? node.contracts: + without contracts =? node.contracts.host: return RestApiResponse.error(Http503, "Sales unavailable") let body = await request.getBody() @@ -281,7 +281,7 @@ proc initRestApi*(node: CodexNodeRef, conf: CodexConf): RestRouter = "/api/codex/v1/storage/purchases/{id}") do ( id: PurchaseId) -> RestApiResponse: - without contracts =? node.contracts: + without contracts =? node.contracts.client: return RestApiResponse.error(Http503, "Purchasing unavailable") without id =? id.tryGet.catch, error: diff --git a/tests/contracts/testInteractions.nim b/tests/contracts/testInteractions.nim index d9dfc0de..7e031c8a 100644 --- a/tests/contracts/testInteractions.nim +++ b/tests/contracts/testInteractions.nim @@ -1,32 +1,63 @@ import std/os -import codex/contracts + +import pkg/datastore + +import pkg/codex/contracts +import pkg/codex/stores + import ../ethertest import ./examples -ethersuite "Marketplace Contract Interactions": +ethersuite "Marketplace Contract Client Interactions": let account = Address.example - var contracts: ContractInteractions + var contracts: ClientInteractions setup: - contracts = !ContractInteractions.new(account) + contracts = !ClientInteractions.new(account) test "can be instantiated with a signer and deployment info": let signer = provider.getSigner() let deployment = deployment() - check ContractInteractions.new(signer, deployment).isSome + check ClientInteractions.new(signer, deployment).isSome test "can be instantiated with a provider url": let url = "http://localhost:8545" let account = Address.example let deployment = "vendor" / "codex-contracts-eth" / "deployment-localhost.json" - check ContractInteractions.new(url, account).isSome - check ContractInteractions.new(url, account, deployment).isSome + check ClientInteractions.new(url, account).isSome + check ClientInteractions.new(url, account, deployment).isSome test "provides purchasing": check contracts.purchasing != nil +ethersuite "Marketplace Contract Host Interactions": + + let account = Address.example + + var + contracts: HostInteractions + repo: RepoStore + + setup: + let repoDs = SQLiteDatastore.new(Memory).tryGet() + let metaDs = SQLiteDatastore.new(Memory).tryGet() + repo = RepoStore.new(repoDs, metaDs) + contracts = !HostInteractions.new(account, repo) + + test "can be instantiated with a signer and deployment info": + let signer = provider.getSigner() + let deployment = deployment() + check HostInteractions.new(signer, deployment, repo).isSome + + test "can be instantiated with a provider url": + let url = "http://localhost:8545" + let account = Address.example + let deployment = "vendor" / "codex-contracts-eth" / "deployment-localhost.json" + check HostInteractions.new(url, account, repo).isSome + check HostInteractions.new(url, account, repo, deployment).isSome + test "provides sales": check contracts.sales != nil