diff --git a/codex/codex.nim b/codex/codex.nim index bc0d53ff..4fc0f922 100644 --- a/codex/codex.nim +++ b/codex/codex.nim @@ -34,6 +34,7 @@ import ./utils/fileutils import ./erasure import ./discovery import ./contracts +import ./systemclock import ./contracts/clock import ./contracts/deployment import ./utils/addrutils @@ -55,12 +56,15 @@ type EthWallet = ethers.Wallet proc bootstrapInteractions( - config: CodexConf, - repo: RepoStore -): Future[Contracts] {.async.} = + s: CodexServer +): Future[void] {.async.} = ## bootstrap interactions and return contracts ## using clients, hosts, validators pairings ## + let + config = s.config + repo = s.repoStore + if not config.persistence and not config.validator: if config.ethAccount.isSome or config.ethPrivateKey.isSome: @@ -106,6 +110,11 @@ proc bootstrapInteractions( var host: ?HostInteractions var validator: ?ValidatorInteractions + if config.validator or config.persistence: + s.codexNode.clock = clock + else: + s.codexNode.clock = SystemClock() + if config.persistence: # This is used for simulation purposes. Normal nodes won't be compiled with this flag # and hence the proof failure will always be 0. @@ -126,7 +135,7 @@ proc bootstrapInteractions( let validation = Validation.new(clock, market, config.validatorMaxSlots) validator = some ValidatorInteractions.new(clock, validation) - return (client, host, validator) + s.codexNode.contracts = (client, host, validator) proc start*(s: CodexServer) {.async.} = trace "Starting codex node", config = $s.config @@ -161,7 +170,7 @@ proc start*(s: CodexServer) {.async.} = s.codexNode.discovery.updateAnnounceRecord(announceAddrs) s.codexNode.discovery.updateDhtRecord(s.config.nat, s.config.discoveryPort) - s.codexNode.contracts = await bootstrapInteractions(s.config, s.repoStore) + await s.bootstrapInteractions() await s.codexNode.start() s.restServer.start() diff --git a/codex/contracts/interactions/interactions.nim b/codex/contracts/interactions/interactions.nim index 287cf297..d4fddf54 100644 --- a/codex/contracts/interactions/interactions.nim +++ b/codex/contracts/interactions/interactions.nim @@ -10,7 +10,7 @@ type clock*: Clock method start*(self: ContractInteractions) {.async, base.} = - await self.clock.start() + discard method stop*(self: ContractInteractions) {.async, base.} = - await self.clock.stop() + discard diff --git a/codex/node.nim b/codex/node.nim index bcb8e58e..ed16a79f 100644 --- a/codex/node.nim +++ b/codex/node.nim @@ -26,6 +26,7 @@ import pkg/libp2p/routing_record import pkg/libp2p/signed_envelope import ./chunker +import ./clock import ./blocktype as bt import ./manifest import ./merkletree @@ -62,6 +63,7 @@ type erasure*: Erasure discovery*: Discovery contracts*: Contracts + clock*: Clock OnManifest* = proc(cid: Cid, manifest: Manifest): void {.gcsafe, closure.} @@ -320,7 +322,7 @@ proc requestStorage*( tolerance: uint, reward: UInt256, collateral: UInt256, - expiry = UInt256.none): Future[?!PurchaseId] {.async.} = + expiry: UInt256): Future[?!PurchaseId] {.async.} = ## Initiate a request for storage sequence, this might ## be a multistep procedure. ## @@ -384,7 +386,7 @@ proc requestStorage*( name: @[] # TODO: PoR setup ) ), - expiry: expiry |? 0.u256 + expiry: expiry ) let purchase = await contracts.purchasing.purchase(request) @@ -418,6 +420,9 @@ proc start*(node: CodexNodeRef) {.async.} = if not node.discovery.isNil: await node.discovery.start() + if not node.clock.isNil: + await node.clock.start() + if hostContracts =? node.contracts.host: # TODO: remove Sales callbacks, pass BlockStore and StorageProofs instead hostContracts.sales.onStore = proc(request: StorageRequest, @@ -499,6 +504,9 @@ proc stop*(node: CodexNodeRef) {.async.} = if not node.discovery.isNil: await node.discovery.stop() + if not node.clock.isNil: + await node.clock.stop() + if clientContracts =? node.contracts.client: await clientContracts.stop() diff --git a/codex/rest/api.nim b/codex/rest/api.nim index 2631ed9c..7c0224d7 100644 --- a/codex/rest/api.nim +++ b/codex/rest/api.nim @@ -270,6 +270,9 @@ proc initPurchasingApi(node: CodexNodeRef, router: var RestRouter) = ## colateral - requested collateral from hosts when they fill slot try: + without contracts =? node.contracts.client: + return RestApiResponse.error(Http503, "Purchasing unavailable") + without cid =? cid.tryGet.catch, error: return RestApiResponse.error(Http400, error.msg) @@ -281,6 +284,15 @@ proc initPurchasingApi(node: CodexNodeRef, router: var RestRouter) = let nodes = params.nodes |? 1 let tolerance = params.tolerance |? 0 + without expiry =? params.expiry: + return RestApiResponse.error(Http400, "Expiry required") + + if node.clock.isNil: + return RestApiResponse.error(Http500) + + if expiry <= node.clock.now.u256: + return RestApiResponse.error(Http400, "Expiry needs to be in future") + without purchaseId =? await node.requestStorage( cid, params.duration, @@ -289,7 +301,7 @@ proc initPurchasingApi(node: CodexNodeRef, router: var RestRouter) = tolerance, params.reward, params.collateral, - params.expiry), error: + expiry), error: return RestApiResponse.error(Http500, error.msg) diff --git a/openapi.yaml b/openapi.yaml index 40eb3288..90fdc732 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -135,6 +135,7 @@ components: - duration - proofProbability - collateral + - expiry properties: duration: $ref: "#/components/schemas/Duration" diff --git a/tests/integration/codexclient.nim b/tests/integration/codexclient.nim index b0b24bf9..28739d7d 100644 --- a/tests/integration/codexclient.nim +++ b/tests/integration/codexclient.nim @@ -54,6 +54,35 @@ proc list*(client: CodexClient): ?!seq[RestContent] = let json = ? parseJson(response.body).catch seq[RestContent].fromJson(json) +proc requestStorageRaw*( + client: CodexClient, + cid: Cid, + duration: UInt256, + reward: UInt256, + proofProbability: UInt256, + collateral: UInt256, + expiry: UInt256 = 0.u256, + nodes: uint = 1, + tolerance: uint = 0 +): Response = + + ## Call request storage REST endpoint + ## + let url = client.baseurl & "/storage/request/" & $cid + let json = %*{ + "duration": duration, + "reward": reward, + "proofProbability": proofProbability, + "collateral": collateral, + "nodes": nodes, + "tolerance": tolerance + } + + if expiry != 0: + json["expiry"] = %expiry + + return client.http.post(url, $json) + proc requestStorage*( client: CodexClient, cid: Cid, @@ -67,17 +96,7 @@ proc requestStorage*( ): ?!PurchaseId = ## Call request storage REST endpoint ## - let url = client.baseurl & "/storage/request/" & $cid - let json = %*{ - "duration": duration, - "reward": reward, - "proofProbability": proofProbability, - "expiry": expiry, - "collateral": collateral, - "nodes": nodes, - "tolerance": tolerance - } - let response = client.http.post(url, $json) + let response = client.requestStorageRaw(cid, duration, reward, proofProbability, collateral, expiry, nodes, tolerance) assert response.status == "200 OK" PurchaseId.fromHex(response.body).catch diff --git a/tests/integration/testIntegration.nim b/tests/integration/testIntegration.nim index 9d1cd1c4..1f51d513 100644 --- a/tests/integration/testIntegration.nim +++ b/tests/integration/testIntegration.nim @@ -1,5 +1,6 @@ import std/options import std/sequtils +import std/httpclient from pkg/libp2p import `==` import pkg/chronos import pkg/stint @@ -216,6 +217,19 @@ twonodessuite "Integration tests", debug1 = false, debug2 = false: check eventually (await token.balanceOf(account2)) - startBalance == duration*reward + test "node requires expiry and its value to be in future": + let currentTime = await provider.currentTime() + let cid = client1.upload("some file contents").get + + let responseMissing = client1.requestStorageRaw(cid, duration=1.u256, reward=2.u256, proofProbability=3.u256, collateral=200.u256) + check responseMissing.status == "400 Bad Request" + check responseMissing.body == "Expiry required" + + let responsePast = client1.requestStorageRaw(cid, duration=1.u256, reward=2.u256, proofProbability=3.u256, collateral=200.u256, expiry=currentTime-10) + check responsePast.status == "400 Bad Request" + check responsePast.body == "Expiry needs to be in future" + + test "expired request partially pays out for stored time": let marketplace = Marketplace.new(Marketplace.address, provider.getSigner()) let tokenAddress = await marketplace.token()