From 1261f255bb09ee84b03dd52cfc0529e1b3a62fbf Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 11 Dec 2024 10:14:01 +0100 Subject: [PATCH 01/14] Sets timeout for codexclient httpClient. Adds reliable transfer test. --- tests/integration/codexclient.nim | 6 ++++-- tests/integration/testupdownload.nim | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/tests/integration/codexclient.nim b/tests/integration/codexclient.nim index 0e415a860..5e8761d16 100644 --- a/tests/integration/codexclient.nim +++ b/tests/integration/codexclient.nim @@ -20,9 +20,11 @@ type CodexClient* = ref object type CodexClientError* = object of CatchableError +const HttpClientTimeoutMs = 60 * 1000 + proc new*(_: type CodexClient, baseurl: string): CodexClient = CodexClient( - http: newHttpClient(), + http: newHttpClient(timeout=HttpClientTimeoutMs), baseurl: baseurl, session: HttpSessionRef.new({HttpClientFlag.Http11Pipeline}) ) @@ -247,7 +249,7 @@ proc close*(client: CodexClient) = proc restart*(client: CodexClient) = client.http.close() - client.http = newHttpClient() + client.http = newHttpClient(timeout=HttpClientTimeoutMs) proc purchaseStateIs*(client: CodexClient, id: PurchaseId, state: string): bool = client.getPurchase(id).option.?state == some state diff --git a/tests/integration/testupdownload.nim b/tests/integration/testupdownload.nim index 8077b1af4..5ff6aed19 100644 --- a/tests/integration/testupdownload.nim +++ b/tests/integration/testupdownload.nim @@ -1,5 +1,6 @@ import pkg/codex/rest/json import ./twonodes +import ../codex/examples import json from pkg/libp2p import Cid, `$` @@ -80,3 +81,16 @@ twonodessuite "Uploads and downloads", debug1 = false, debug2 = false: let resp2 = client2.download(cid1, local = true).get check: content1 == resp2 + + test "reliable transfer test": + proc transferTest(a: CodexClient, b: CodexClient) {.async.} = + let data = await RandomChunker.example(blocks=8) + let cid = a.upload(data).get + let response = b.download(cid).get + check: + response == data + + for run in 0..20: + echo "Run: " & $run + await transferTest(client1, client2) + await transferTest(client2, client1) From 98843f370045cf4ecbb7f607644ab2b3debac8a2 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 11 Dec 2024 10:54:03 +0100 Subject: [PATCH 02/14] disable new test to check timeout setting in CI --- tests/integration/testupdownload.nim | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/integration/testupdownload.nim b/tests/integration/testupdownload.nim index 5ff6aed19..43455957d 100644 --- a/tests/integration/testupdownload.nim +++ b/tests/integration/testupdownload.nim @@ -82,15 +82,15 @@ twonodessuite "Uploads and downloads", debug1 = false, debug2 = false: check: content1 == resp2 - test "reliable transfer test": - proc transferTest(a: CodexClient, b: CodexClient) {.async.} = - let data = await RandomChunker.example(blocks=8) - let cid = a.upload(data).get - let response = b.download(cid).get - check: - response == data - - for run in 0..20: - echo "Run: " & $run - await transferTest(client1, client2) - await transferTest(client2, client1) + # test "reliable transfer test": + # proc transferTest(a: CodexClient, b: CodexClient) {.async.} = + # let data = await RandomChunker.example(blocks=8) + # let cid = a.upload(data).get + # let response = b.download(cid).get + # check: + # response == data + + # for run in 0..20: + # echo "Run: " & $run + # await transferTest(client1, client2) + # await transferTest(client2, client1) From 4c8788b8623500897fc087b5c4e285d87e517108 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 11 Dec 2024 15:43:07 +0100 Subject: [PATCH 03/14] restores new test --- tests/integration/testupdownload.nim | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/integration/testupdownload.nim b/tests/integration/testupdownload.nim index 43455957d..5ff6aed19 100644 --- a/tests/integration/testupdownload.nim +++ b/tests/integration/testupdownload.nim @@ -82,15 +82,15 @@ twonodessuite "Uploads and downloads", debug1 = false, debug2 = false: check: content1 == resp2 - # test "reliable transfer test": - # proc transferTest(a: CodexClient, b: CodexClient) {.async.} = - # let data = await RandomChunker.example(blocks=8) - # let cid = a.upload(data).get - # let response = b.download(cid).get - # check: - # response == data - - # for run in 0..20: - # echo "Run: " & $run - # await transferTest(client1, client2) - # await transferTest(client2, client1) + test "reliable transfer test": + proc transferTest(a: CodexClient, b: CodexClient) {.async.} = + let data = await RandomChunker.example(blocks=8) + let cid = a.upload(data).get + let response = b.download(cid).get + check: + response == data + + for run in 0..20: + echo "Run: " & $run + await transferTest(client1, client2) + await transferTest(client2, client1) From 8b8b85f944f973f73e24ccb0e1ce89b84a963efb Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 13 Dec 2024 12:55:34 +0100 Subject: [PATCH 04/14] adds heartbeat log and logfile to ci output --- codex/node.nim | 7 +++++++ tests/integration/testupdownload.nim | 2 +- tests/integration/twonodes.nim | 8 ++++++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/codex/node.nim b/codex/node.nim index f180fd629..9e03fe7e4 100644 --- a/codex/node.nim +++ b/codex/node.nim @@ -745,6 +745,13 @@ proc start*(self: CodexNodeRef) {.async.} = error "Unable to start validator contract interactions: ", error=error.msg self.contracts.validator = ValidatorInteractions.none + proc heartbeat() {.async.} = + while true: + await sleepAsync(1000.millis) + warn "a" + + asyncSpawn heartbeat() + self.networkId = self.switch.peerInfo.peerId notice "Started codex node", id = self.networkId, addrs = self.switch.peerInfo.addrs diff --git a/tests/integration/testupdownload.nim b/tests/integration/testupdownload.nim index 5ff6aed19..320dd1c7d 100644 --- a/tests/integration/testupdownload.nim +++ b/tests/integration/testupdownload.nim @@ -4,7 +4,6 @@ import ../codex/examples import json from pkg/libp2p import Cid, `$` -twonodessuite "Uploads and downloads", debug1 = false, debug2 = false: test "node allows local file downloads": let content1 = "some file contents" @@ -82,6 +81,7 @@ twonodessuite "Uploads and downloads", debug1 = false, debug2 = false: check: content1 == resp2 +twonodessuite "Uploads and downloads", debug1 = "false", debug2 = "false", logfile = true: test "reliable transfer test": proc transferTest(a: CodexClient, b: CodexClient) {.async.} = let data = await RandomChunker.example(blocks=8) diff --git a/tests/integration/twonodes.nim b/tests/integration/twonodes.nim index 69773c810..7ad8540d4 100644 --- a/tests/integration/twonodes.nim +++ b/tests/integration/twonodes.nim @@ -10,9 +10,9 @@ export codexclient export nodes template twonodessuite*(name: string, debug1, debug2: bool | string, body) = - twonodessuite(name, $debug1, $debug2, body) + twonodessuite(name, $debug1, $debug2, false, body) -template twonodessuite*(name: string, debug1, debug2: string, body) = +template twonodessuite*(name: string, debug1, debug2: string, logfile: bool, body) = ethersuite name: var node1 {.inject, used.}: NodeProcess @@ -49,6 +49,8 @@ template twonodessuite*(name: string, debug1, debug2: string, body) = if debug1 != "true" and debug1 != "false": node1Args.add("--log-level=" & debug1) + if logfile: + node1Args.add("--log-file=tests/integration/logs/node1.log") node1 = startNode(node1Args, debug = debug1) node1.waitUntilStarted() @@ -74,6 +76,8 @@ template twonodessuite*(name: string, debug1, debug2: string, body) = if debug2 != "true" and debug2 != "false": node2Args.add("--log-level=" & debug2) + if logfile: + node2Args.add("--log-file=tests/integration/logs/node2.log") node2 = startNode(node2Args, debug = debug2) node2.waitUntilStarted() From 661a3a40a2e00a5fb94ca743a7f721dfddcc6d40 Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 13 Dec 2024 13:39:46 +0100 Subject: [PATCH 05/14] fixes suite --- tests/integration/testupdownload.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/testupdownload.nim b/tests/integration/testupdownload.nim index 320dd1c7d..08d6af95f 100644 --- a/tests/integration/testupdownload.nim +++ b/tests/integration/testupdownload.nim @@ -4,7 +4,7 @@ import ../codex/examples import json from pkg/libp2p import Cid, `$` - +twonodessuite "Uploads and downloads", debug1 = false, debug2 = false: test "node allows local file downloads": let content1 = "some file contents" let content2 = "some other contents" @@ -81,7 +81,7 @@ from pkg/libp2p import Cid, `$` check: content1 == resp2 -twonodessuite "Uploads and downloads", debug1 = "false", debug2 = "false", logfile = true: +twonodessuite "Uploads and downloads - logfile", debug1 = "false", debug2 = "false", logfile = true: test "reliable transfer test": proc transferTest(a: CodexClient, b: CodexClient) {.async.} = let data = await RandomChunker.example(blocks=8) From c79c0afe1f3222685eeb9745b9471d2465bb1dd7 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 16 Dec 2024 10:51:22 +0100 Subject: [PATCH 06/14] fixes blocked output stream by switching to multinode fixture --- tests/integration/multinodes.nim | 6 +-- tests/integration/testupdownload.nim | 66 ++++++++++++++++------------ 2 files changed, 41 insertions(+), 31 deletions(-) diff --git a/tests/integration/multinodes.nim b/tests/integration/multinodes.nim index 55d672f7f..1144fe9c4 100644 --- a/tests/integration/multinodes.nim +++ b/tests/integration/multinodes.nim @@ -142,6 +142,8 @@ template multinodesuite*(name: string, body: untyped) = let updatedLogFile = getLogFile(role, some roleIdx) config.withLogFile(updatedLogFile) + if bootstrap.len > 0: + config.addCliOption("--bootstrap-node", bootstrap) config.addCliOption("--api-port", $ await nextFreePort(8080 + nodeIdx)) config.addCliOption("--data-dir", datadir) config.addCliOption("--nat", "127.0.0.1") @@ -203,7 +205,6 @@ template multinodesuite*(name: string, body: untyped) = proc startProviderNode(conf: CodexConfig): Future[NodeProcess] {.async.} = let providerIdx = providers().len var config = conf - config.addCliOption("--bootstrap-node", bootstrap) config.addCliOption(StartUpCmd.persistence, "--eth-provider", "http://127.0.0.1:8545") config.addCliOption(StartUpCmd.persistence, "--eth-account", $accounts[running.len]) config.addCliOption(PersistenceCmd.prover, "--circom-r1cs", @@ -218,7 +219,6 @@ template multinodesuite*(name: string, body: untyped) = proc startValidatorNode(conf: CodexConfig): Future[NodeProcess] {.async.} = let validatorIdx = validators().len var config = conf - config.addCliOption("--bootstrap-node", bootstrap) config.addCliOption(StartUpCmd.persistence, "--eth-provider", "http://127.0.0.1:8545") config.addCliOption(StartUpCmd.persistence, "--eth-account", $accounts[running.len]) config.addCliOption(StartUpCmd.persistence, "--validator") @@ -291,7 +291,7 @@ template multinodesuite*(name: string, body: untyped) = role: Role.Client, node: node ) - if clients().len == 1: + if running.len == 1: without ninfo =? CodexProcess(node).client.info(): # raise CatchableError instead of Defect (with .get or !) so we # can gracefully shutdown and prevent zombies diff --git a/tests/integration/testupdownload.nim b/tests/integration/testupdownload.nim index 08d6af95f..aee090dee 100644 --- a/tests/integration/testupdownload.nim +++ b/tests/integration/testupdownload.nim @@ -1,42 +1,54 @@ import pkg/codex/rest/json -import ./twonodes +import pkg/questionable +import ./multinodes +import ./codexconfig +import ./nodeconfigs import ../codex/examples import json from pkg/libp2p import Cid, `$` -twonodessuite "Uploads and downloads", debug1 = false, debug2 = false: - test "node allows local file downloads": +multinodesuite "Uploads and downloads": + let twoNodesConfig = NodeConfigs( + clients: + CodexConfigs.init(nodes=2) + # .debug() # uncomment to enable console log output + # .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log + # .withLogTopics("node, marketplace") + .some, + ) + + test "node allows local file downloads", twoNodesConfig: let content1 = "some file contents" let content2 = "some other contents" - let cid1 = client1.upload(content1).get - let cid2 = client2.upload(content2).get + let cid1 = clients()[0].client.upload(content1).get + let cid2 = clients()[1].client.upload(content2).get - let resp1 = client1.download(cid1, local = true).get - let resp2 = client2.download(cid2, local = true).get + let resp1 = clients()[0].client.download(cid1, local = true).get + let resp2 = clients()[1].client.download(cid2, local = true).get check: content1 == resp1 content2 == resp2 - test "node allows remote file downloads": + test "node allows remote file downloads", twoNodesConfig: let content1 = "some file contents" let content2 = "some other contents" - let cid1 = client1.upload(content1).get - let cid2 = client2.upload(content2).get + let cid1 = clients()[0].client.upload(content1).get + let cid2 = clients()[1].client.upload(content2).get - let resp2 = client1.download(cid2, local = false).get - let resp1 = client2.download(cid1, local = false).get + let resp2 = clients()[0].client.download(cid2, local = false).get + let resp1 = clients()[1].client.download(cid1, local = false).get check: content1 == resp1 content2 == resp2 - test "node fails retrieving non-existing local file": + test "node fails retrieving non-existing local file", twoNodesConfig: let content1 = "some file contents" - let cid1 = client1.upload(content1).get # upload to first node - let resp2 = client2.download(cid1, local = true) # try retrieving from second node + let cid1 = clients()[0].client.upload(content1).get # upload to first node + let resp2 = clients()[1].client.download(cid1, local = true) # try retrieving from second node check: resp2.error.msg == "404 Not Found" @@ -64,25 +76,24 @@ twonodessuite "Uploads and downloads", debug1 = false, debug2 = false: check manifest.hasKey("protected") == true check manifest["protected"].getBool() == false - test "node allows downloading only manifest": + test "node allows downloading only manifest", twoNodesConfig: let content1 = "some file contents" - let cid1 = client1.upload(content1).get + let cid1 = clients()[0].client.upload(content1).get - let resp2 = client1.downloadManifestOnly(cid1) + let resp2 = clients()[0].client.downloadManifestOnly(cid1) checkRestContent(cid1, resp2) - test "node allows downloading content without stream": + test "node allows downloading content without stream", twoNodesConfig: let content1 = "some file contents" - let cid1 = client1.upload(content1).get + let cid1 = clients()[0].client.upload(content1).get - let resp1 = client2.downloadNoStream(cid1) + let resp1 = clients()[1].client.downloadNoStream(cid1) checkRestContent(cid1, resp1) - let resp2 = client2.download(cid1, local = true).get + let resp2 = clients()[1].client.download(cid1, local = true).get check: content1 == resp2 -twonodessuite "Uploads and downloads - logfile", debug1 = "false", debug2 = "false", logfile = true: - test "reliable transfer test": + test "reliable transfer test", twoNodesConfig: proc transferTest(a: CodexClient, b: CodexClient) {.async.} = let data = await RandomChunker.example(blocks=8) let cid = a.upload(data).get @@ -90,7 +101,6 @@ twonodessuite "Uploads and downloads - logfile", debug1 = "false", debug2 = "fal check: response == data - for run in 0..20: - echo "Run: " & $run - await transferTest(client1, client2) - await transferTest(client2, client1) + for run in 0..10: + await transferTest(clients()[0].client, clients()[1].client) + await transferTest(clients()[1].client, clients()[0].client) From de3a09dacd5517e3ea8d77f8db46ff3e6038a7f9 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 16 Dec 2024 12:57:54 +0100 Subject: [PATCH 07/14] new twonodessuite based on multinodesuite --- tests/integration/atwonodes.nim | 34 +++++++++++++++++++ tests/integration/testupdownload.nim | 50 +++++++++++----------------- tests/integration/twonodes.nim | 2 +- 3 files changed, 54 insertions(+), 32 deletions(-) create mode 100644 tests/integration/atwonodes.nim diff --git a/tests/integration/atwonodes.nim b/tests/integration/atwonodes.nim new file mode 100644 index 000000000..cbbb691d5 --- /dev/null +++ b/tests/integration/atwonodes.nim @@ -0,0 +1,34 @@ +import std/os +import std/macros +import pkg/questionable +import ./multinodes +import ./codexconfig +import ./codexprocess +import ./codexclient +import ./nodeconfigs + +export codexclient +export multinodes + +template atwonodessuite*(name: string, body: untyped) = + multinodesuite name: + let twoNodesConfig {.inject, used.} = NodeConfigs(clients: CodexConfigs.init(nodes=2).some) + + var node1 {.inject, used.}: CodexProcess + var node2 {.inject, used.}: CodexProcess + var client1 {.inject, used.}: CodexClient + var client2 {.inject, used.}: CodexClient + var account1 {.inject, used.}: Address + var account2 {.inject, used.}: Address + + setup: + account1 = accounts[0] + account2 = accounts[1] + + node1 = clients()[0] + node2 = clients()[1] + + client1 = node1.client + client2 = node2.client + + body diff --git a/tests/integration/testupdownload.nim b/tests/integration/testupdownload.nim index aee090dee..716169769 100644 --- a/tests/integration/testupdownload.nim +++ b/tests/integration/testupdownload.nim @@ -1,31 +1,19 @@ import pkg/codex/rest/json -import pkg/questionable -import ./multinodes -import ./codexconfig -import ./nodeconfigs +import ./atwonodes import ../codex/examples import json from pkg/libp2p import Cid, `$` -multinodesuite "Uploads and downloads": - let twoNodesConfig = NodeConfigs( - clients: - CodexConfigs.init(nodes=2) - # .debug() # uncomment to enable console log output - # .withLogFile() # uncomment to output log file to tests/integration/logs/ //_.log - # .withLogTopics("node, marketplace") - .some, - ) - +atwonodessuite "Uploads and downloads": test "node allows local file downloads", twoNodesConfig: let content1 = "some file contents" let content2 = "some other contents" - let cid1 = clients()[0].client.upload(content1).get - let cid2 = clients()[1].client.upload(content2).get + let cid1 = client1.upload(content1).get + let cid2 = client2.upload(content2).get - let resp1 = clients()[0].client.download(cid1, local = true).get - let resp2 = clients()[1].client.download(cid2, local = true).get + let resp1 = client1.download(cid1, local = true).get + let resp2 = client2.download(cid2, local = true).get check: content1 == resp1 @@ -35,11 +23,11 @@ multinodesuite "Uploads and downloads": let content1 = "some file contents" let content2 = "some other contents" - let cid1 = clients()[0].client.upload(content1).get - let cid2 = clients()[1].client.upload(content2).get + let cid1 = client1.upload(content1).get + let cid2 = client2.upload(content2).get - let resp2 = clients()[0].client.download(cid2, local = false).get - let resp1 = clients()[1].client.download(cid1, local = false).get + let resp2 = client1.download(cid2, local = false).get + let resp1 = client2.download(cid1, local = false).get check: content1 == resp1 @@ -47,8 +35,8 @@ multinodesuite "Uploads and downloads": test "node fails retrieving non-existing local file", twoNodesConfig: let content1 = "some file contents" - let cid1 = clients()[0].client.upload(content1).get # upload to first node - let resp2 = clients()[1].client.download(cid1, local = true) # try retrieving from second node + let cid1 = client1.upload(content1).get # upload to first node + let resp2 = client2.download(cid1, local = true) # try retrieving from second node check: resp2.error.msg == "404 Not Found" @@ -78,18 +66,18 @@ multinodesuite "Uploads and downloads": test "node allows downloading only manifest", twoNodesConfig: let content1 = "some file contents" - let cid1 = clients()[0].client.upload(content1).get + let cid1 = client1.upload(content1).get - let resp2 = clients()[0].client.downloadManifestOnly(cid1) + let resp2 = client1.downloadManifestOnly(cid1) checkRestContent(cid1, resp2) test "node allows downloading content without stream", twoNodesConfig: let content1 = "some file contents" - let cid1 = clients()[0].client.upload(content1).get + let cid1 = client1.upload(content1).get - let resp1 = clients()[1].client.downloadNoStream(cid1) + let resp1 = client2.downloadNoStream(cid1) checkRestContent(cid1, resp1) - let resp2 = clients()[1].client.download(cid1, local = true).get + let resp2 = client2.download(cid1, local = true).get check: content1 == resp2 @@ -102,5 +90,5 @@ multinodesuite "Uploads and downloads": response == data for run in 0..10: - await transferTest(clients()[0].client, clients()[1].client) - await transferTest(clients()[1].client, clients()[0].client) + await transferTest(client1, client2) + await transferTest(client2, client1) diff --git a/tests/integration/twonodes.nim b/tests/integration/twonodes.nim index 7ad8540d4..cce387acc 100644 --- a/tests/integration/twonodes.nim +++ b/tests/integration/twonodes.nim @@ -13,7 +13,7 @@ template twonodessuite*(name: string, debug1, debug2: bool | string, body) = twonodessuite(name, $debug1, $debug2, false, body) template twonodessuite*(name: string, debug1, debug2: string, logfile: bool, body) = - ethersuite name: + var node1 {.inject, used.}: NodeProcess var node2 {.inject, used.}: NodeProcess From b895c41098247bdf7c027611f32f63c6c751662d Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 16 Dec 2024 14:23:52 +0100 Subject: [PATCH 08/14] Applies updated twonodessuite --- tests/integration/atwonodes.nim | 34 -------- tests/integration/nodeprocess.nim | 12 ++- tests/integration/nodes.nim | 91 ---------------------- tests/integration/testblockexpiration.nim | 23 +++--- tests/integration/testcli.nim | 45 ++++++----- tests/integration/testmarketplace.nim | 6 +- tests/integration/testpurchasing.nim | 14 ++-- tests/integration/testrestapi.nim | 38 ++++----- tests/integration/testsales.nim | 16 ++-- tests/integration/testupdownload.nim | 4 +- tests/integration/twonodes.nim | 94 ++++------------------- 11 files changed, 102 insertions(+), 275 deletions(-) delete mode 100644 tests/integration/atwonodes.nim delete mode 100644 tests/integration/nodes.nim diff --git a/tests/integration/atwonodes.nim b/tests/integration/atwonodes.nim deleted file mode 100644 index cbbb691d5..000000000 --- a/tests/integration/atwonodes.nim +++ /dev/null @@ -1,34 +0,0 @@ -import std/os -import std/macros -import pkg/questionable -import ./multinodes -import ./codexconfig -import ./codexprocess -import ./codexclient -import ./nodeconfigs - -export codexclient -export multinodes - -template atwonodessuite*(name: string, body: untyped) = - multinodesuite name: - let twoNodesConfig {.inject, used.} = NodeConfigs(clients: CodexConfigs.init(nodes=2).some) - - var node1 {.inject, used.}: CodexProcess - var node2 {.inject, used.}: CodexProcess - var client1 {.inject, used.}: CodexClient - var client2 {.inject, used.}: CodexClient - var account1 {.inject, used.}: Address - var account2 {.inject, used.}: Address - - setup: - account1 = accounts[0] - account2 = accounts[1] - - node1 = clients()[0] - node2 = clients()[1] - - client1 = node1.client - client2 = node2.client - - body diff --git a/tests/integration/nodeprocess.nim b/tests/integration/nodeprocess.nim index 61947c20c..8fa013d69 100644 --- a/tests/integration/nodeprocess.nim +++ b/tests/integration/nodeprocess.nim @@ -147,16 +147,20 @@ method stop*(node: NodeProcess) {.base, async.} = trace "node stopped" -proc waitUntilStarted*(node: NodeProcess) {.async.} = +proc waitUntilOutput*(node: NodeProcess, output: string) {.async.} = logScope: nodeName = node.name - trace "waiting until node started" + trace "waiting until", output + let started = newFuture[void]() + discard node.captureOutput(output, started).track(node) + await started.wait(35.seconds) # allow enough time for proof generation + +proc waitUntilStarted*(node: NodeProcess) {.async.} = let started = newFuture[void]() try: - discard node.captureOutput(node.startedOutput, started).track(node) - await started.wait(35.seconds) # allow enough time for proof generation + await node.waitUntilOutput(node.startedOutput) except AsyncTimeoutError: # attempt graceful shutdown in case node was partially started, prevent # zombies diff --git a/tests/integration/nodes.nim b/tests/integration/nodes.nim deleted file mode 100644 index a60a55c70..000000000 --- a/tests/integration/nodes.nim +++ /dev/null @@ -1,91 +0,0 @@ -import std/osproc -import std/os -import std/streams -import std/strutils -import pkg/codex/conf -import pkg/codex/logutils -import pkg/confutils -import pkg/libp2p -import pkg/questionable -import ./codexclient - -export codexclient - -const workingDir = currentSourcePath() / ".." / ".." / ".." -const executable = "build" / "codex" - -type - NodeProcess* = ref object - process: Process - arguments: seq[string] - debug: bool - client: ?CodexClient - -proc start(node: NodeProcess) = - if node.debug: - node.process = osproc.startProcess( - executable, - workingDir, - node.arguments, - options={poParentStreams} - ) - else: - node.process = osproc.startProcess( - executable, - workingDir, - node.arguments - ) - -proc waitUntilOutput*(node: NodeProcess, output: string) = - if node.debug: - raiseAssert "cannot read node output when in debug mode" - for line in node.process.outputStream.lines: - if line.contains(output): - return - raiseAssert "node did not output '" & output & "'" - -proc waitUntilStarted*(node: NodeProcess) = - if node.debug: - sleep(10_000) - else: - node.waitUntilOutput("Started codex node") - -proc startNode*(args: openArray[string], debug: string | bool = false): NodeProcess = - ## Starts a Codex Node with the specified arguments. - ## Set debug to 'true' to see output of the node. - let node = NodeProcess(arguments: @args, debug: ($debug != "false")) - node.start() - node - -proc dataDir(node: NodeProcess): string = - let config = CodexConf.load(cmdLine = node.arguments, quitOnFailure = false) - config.dataDir.string - -proc apiUrl(node: NodeProcess): string = - let config = CodexConf.load(cmdLine = node.arguments, quitOnFailure = false) - "http://" & config.apiBindAddress & ":" & $config.apiPort & "/api/codex/v1" - -proc client*(node: NodeProcess): CodexClient = - if client =? node.client: - return client - let client = CodexClient.new(node.apiUrl) - node.client = some client - client - -proc stop*(node: NodeProcess) = - if node.process != nil: - node.process.terminate() - discard node.process.waitForExit(timeout=5_000) - node.process.close() - node.process = nil - if client =? node.client: - node.client = none CodexClient - client.close() - -proc restart*(node: NodeProcess) = - node.stop() - node.start() - node.waitUntilStarted() - -proc removeDataDir*(node: NodeProcess) = - removeDir(node.dataDir) diff --git a/tests/integration/testblockexpiration.nim b/tests/integration/testblockexpiration.nim index 6502243a2..3ad9e4f25 100644 --- a/tests/integration/testblockexpiration.nim +++ b/tests/integration/testblockexpiration.nim @@ -5,10 +5,11 @@ from std/net import TimeoutError import pkg/chronos import ../ethertest -import ./nodes +import ./codexprocess +import ./nodeprocess ethersuite "Node block expiration tests": - var node: NodeProcess + var node: CodexProcess var baseurl: string let dataDir = getTempDir() / "Codex1" @@ -18,12 +19,12 @@ ethersuite "Node block expiration tests": baseurl = "http://localhost:8080/api/codex/v1" teardown: - node.stop() + await node.stop() dataDir.removeDir() - proc startTestNode(blockTtlSeconds: int) = - node = startNode([ + proc startTestNode(blockTtlSeconds: int) {.async.} = + node = await CodexProcess.startNode(@[ "--api-port=8080", "--data-dir=" & dataDir, "--nat=127.0.0.1", @@ -32,9 +33,11 @@ ethersuite "Node block expiration tests": "--disc-port=8090", "--block-ttl=" & $blockTtlSeconds, "--block-mi=1", - "--block-mn=10" - ], debug = false) - node.waitUntilStarted() + "--block-mn=10"], + false, + "cli-test-node" + ) + await node.waitUntilStarted() proc uploadTestFile(): string = let client = newHttpClient() @@ -61,7 +64,7 @@ ethersuite "Node block expiration tests": content.code == Http200 test "node retains not-expired file": - startTestNode(blockTtlSeconds = 10) + await startTestNode(blockTtlSeconds = 10) let contentId = uploadTestFile() @@ -74,7 +77,7 @@ ethersuite "Node block expiration tests": response.body == content test "node deletes expired file": - startTestNode(blockTtlSeconds = 1) + await startTestNode(blockTtlSeconds = 1) let contentId = uploadTestFile() diff --git a/tests/integration/testcli.nim b/tests/integration/testcli.nim index 1fcf6c3b0..fad85846b 100644 --- a/tests/integration/testcli.nim +++ b/tests/integration/testcli.nim @@ -1,29 +1,38 @@ -import std/unittest import std/tempfiles import codex/conf import codex/utils/fileutils -import ./nodes +import ../asynctest +import ../checktest +import ./codexprocess +import ./nodeprocess import ../examples -suite "Command line interface": +asyncchecksuite "Command line interface": let key = "4242424242424242424242424242424242424242424242424242424242424242" + proc startCodex(args: seq[string]): Future[CodexProcess] {.async.} = + return await CodexProcess.startNode( + args, + false, + "cli-test-node" + ) + test "complains when persistence is enabled without ethereum account": - let node = startNode(@[ + let node = await startCodex(@[ "persistence" ]) - node.waitUntilOutput("Persistence enabled, but no Ethereum account was set") - node.stop() + await node.waitUntilOutput("Persistence enabled, but no Ethereum account was set") + await node.stop() test "complains when ethereum private key file has wrong permissions": let unsafeKeyFile = genTempPath("", "") discard unsafeKeyFile.writeFile(key, 0o666) - let node = startNode(@[ + let node = await startCodex(@[ "persistence", "--eth-private-key=" & unsafeKeyFile]) - node.waitUntilOutput("Ethereum private key file does not have safe file permissions") - node.stop() + await node.waitUntilOutput("Ethereum private key file does not have safe file permissions") + await node.stop() discard removeFile(unsafeKeyFile) let @@ -31,27 +40,27 @@ suite "Command line interface": expectedDownloadInstruction = "Proving circuit files are not found. Please run the following to download them:" test "suggests downloading of circuit files when persistence is enabled without accessible r1cs file": - let node = startNode(@["persistence", "prover", marketplaceArg]) - node.waitUntilOutput(expectedDownloadInstruction) - node.stop() + let node = await startCodex(@["persistence", "prover", marketplaceArg]) + await node.waitUntilOutput(expectedDownloadInstruction) + await node.stop() test "suggests downloading of circuit files when persistence is enabled without accessible wasm file": - let node = startNode(@[ + let node = await startCodex(@[ "persistence", "prover", marketplaceArg, "--circom-r1cs=tests/circuits/fixtures/proof_main.r1cs" ]) - node.waitUntilOutput(expectedDownloadInstruction) - node.stop() + await node.waitUntilOutput(expectedDownloadInstruction) + await node.stop() test "suggests downloading of circuit files when persistence is enabled without accessible zkey file": - let node = startNode(@[ + let node = await startCodex(@[ "persistence", "prover", marketplaceArg, "--circom-r1cs=tests/circuits/fixtures/proof_main.r1cs", "--circom-wasm=tests/circuits/fixtures/proof_main.wasm" ]) - node.waitUntilOutput(expectedDownloadInstruction) - node.stop() + await node.waitUntilOutput(expectedDownloadInstruction) + await node.stop() diff --git a/tests/integration/testmarketplace.nim b/tests/integration/testmarketplace.nim index 0c18ff1a9..8fce33f86 100644 --- a/tests/integration/testmarketplace.nim +++ b/tests/integration/testmarketplace.nim @@ -7,14 +7,14 @@ import ./marketplacesuite import ./twonodes import ./nodeconfigs -twonodessuite "Marketplace", debug1 = false, debug2 = false: +twonodessuite "Marketplace": setup: # Our Hardhat configuration does use automine, which means that time tracked by `ethProvider.currentTime()` is not # advanced until blocks are mined and that happens only when transaction is submitted. # As we use in tests ethProvider.currentTime() which uses block timestamp this can lead to synchronization issues. await ethProvider.advanceTime(1.u256) - test "nodes negotiate contracts on the marketplace": + test "nodes negotiate contracts on the marketplace", twoNodesConfig: let size = 0xFFFFFF.u256 let data = await RandomChunker.example(blocks=8) # client 2 makes storage available @@ -44,7 +44,7 @@ twonodessuite "Marketplace", debug1 = false, debug2 = false: check reservations.len == 3 check reservations[0].requestId == purchase.requestId - test "node slots gets paid out and rest of tokens are returned to client": + test "node slots gets paid out and rest of tokens are returned to client", twoNodesConfig: let size = 0xFFFFFF.u256 let data = await RandomChunker.example(blocks = 8) let marketplace = Marketplace.new(Marketplace.address, ethProvider.getSigner()) diff --git a/tests/integration/testpurchasing.nim b/tests/integration/testpurchasing.nim index 0968b34f2..4e5fa8664 100644 --- a/tests/integration/testpurchasing.nim +++ b/tests/integration/testpurchasing.nim @@ -5,16 +5,16 @@ import ./twonodes import ../contracts/time import ../examples -twonodessuite "Purchasing", debug1 = false, debug2 = false: +twonodessuite "Purchasing": - test "node handles storage request": + test "node handles storage request", twoNodesConfig: let data = await RandomChunker.example(blocks=2) let cid = client1.upload(data).get let id1 = client1.requestStorage(cid, duration=100.u256, reward=2.u256, proofProbability=3.u256, expiry=10, collateral=200.u256).get let id2 = client1.requestStorage(cid, duration=400.u256, reward=5.u256, proofProbability=6.u256, expiry=10, collateral=201.u256).get check id1 != id2 - test "node retrieves purchase status": + test "node retrieves purchase status", twoNodesConfig: # get one contiguous chunk let rng = rng.Rng.instance() let chunker = RandomChunker.new(rng, size = DefaultBlockSize * 2, chunkSize = DefaultBlockSize * 2) @@ -40,7 +40,7 @@ twonodessuite "Purchasing", debug1 = false, debug2 = false: check request.ask.maxSlotLoss == 1'u64 # TODO: We currently do not support encoding single chunks - # test "node retrieves purchase status with 1 chunk": + # test "node retrieves purchase status with 1 chunk", twoNodesConfig: # let cid = client1.upload("some file contents").get # let id = client1.requestStorage(cid, duration=1.u256, reward=2.u256, proofProbability=3.u256, expiry=30, collateral=200.u256, nodes=2, tolerance=1).get # let request = client1.getPurchase(id).get.request.get @@ -52,7 +52,7 @@ twonodessuite "Purchasing", debug1 = false, debug2 = false: # check request.ask.slots == 3'u64 # check request.ask.maxSlotLoss == 1'u64 - test "node remembers purchase status after restart": + test "node remembers purchase status after restart", twoNodesConfig: let data = await RandomChunker.example(blocks=2) let cid = client1.upload(data).get let id = client1.requestStorage(cid, @@ -65,7 +65,7 @@ twonodessuite "Purchasing", debug1 = false, debug2 = false: tolerance=1.uint).get check eventually(client1.purchaseStateIs(id, "submitted"), timeout = 3*60*1000) - node1.restart() + await node1.restart() client1.restart() check eventually(client1.purchaseStateIs(id, "submitted"), timeout = 3*60*1000) @@ -78,7 +78,7 @@ twonodessuite "Purchasing", debug1 = false, debug2 = false: check request.ask.slots == 3'u64 check request.ask.maxSlotLoss == 1'u64 - test "node requires expiry and its value to be in future": + test "node requires expiry and its value to be in future", twoNodesConfig: let data = await RandomChunker.example(blocks=2) let cid = client1.upload(data).get diff --git a/tests/integration/testrestapi.nim b/tests/integration/testrestapi.nim index 361e616f0..2d5c3392b 100644 --- a/tests/integration/testrestapi.nim +++ b/tests/integration/testrestapi.nim @@ -6,20 +6,20 @@ import ./twonodes import ../examples import json -twonodessuite "REST API", debug1 = false, debug2 = false: - test "nodes can print their peer information": +twonodessuite "REST API": + test "nodes can print their peer information", twoNodesConfig: check !client1.info() != !client2.info() - test "nodes can set chronicles log level": + test "nodes can set chronicles log level", twoNodesConfig: client1.setLogLevel("DEBUG;TRACE:codex") - test "node accepts file uploads": + test "node accepts file uploads", twoNodesConfig: let cid1 = client1.upload("some file contents").get let cid2 = client1.upload("some other contents").get check cid1 != cid2 - test "node shows used and available space": + test "node shows used and available space", twoNodesConfig: discard client1.upload("some file contents").get discard client1.postAvailability(totalSize=12.u256, duration=2.u256, minPrice=3.u256, maxCollateral=4.u256).get let space = client1.space().tryGet() @@ -29,7 +29,7 @@ twonodessuite "REST API", debug1 = false, debug2 = false: space.quotaUsedBytes == 65598.NBytes space.quotaReservedBytes == 12.NBytes - test "node lists local files": + test "node lists local files", twoNodesConfig: let content1 = "some file contents" let content2 = "some other contents" @@ -40,7 +40,7 @@ twonodessuite "REST API", debug1 = false, debug2 = false: check: [cid1, cid2].allIt(it in list.content.mapIt(it.cid)) - test "request storage fails for datasets that are too small": + test "request storage fails for datasets that are too small", twoNodesConfig: let cid = client1.upload("some file contents").get let response = client1.requestStorageRaw(cid, duration=10.u256, reward=2.u256, proofProbability=3.u256, collateral=200.u256, expiry=9) @@ -48,7 +48,7 @@ twonodessuite "REST API", debug1 = false, debug2 = false: response.status == "400 Bad Request" response.body == "Dataset too small for erasure parameters, need at least " & $(2*DefaultBlockSize.int) & " bytes" - test "request storage succeeds for sufficiently sized datasets": + test "request storage succeeds for sufficiently sized datasets", twoNodesConfig: let data = await RandomChunker.example(blocks=2) let cid = client1.upload(data).get let response = client1.requestStorageRaw(cid, duration=10.u256, reward=2.u256, proofProbability=3.u256, collateral=200.u256, expiry=9) @@ -56,7 +56,7 @@ twonodessuite "REST API", debug1 = false, debug2 = false: check: response.status == "200 OK" - test "request storage fails if tolerance is zero": + test "request storage fails if tolerance is zero", twoNodesConfig: let data = await RandomChunker.example(blocks=2) let cid = client1.upload(data).get let duration = 100.u256 @@ -79,7 +79,7 @@ twonodessuite "REST API", debug1 = false, debug2 = false: check responseBefore.status == "400 Bad Request" check responseBefore.body == "Tolerance needs to be bigger then zero" - test "request storage fails if nodes and tolerance aren't correct": + test "request storage fails if nodes and tolerance aren't correct", twoNodesConfig: let data = await RandomChunker.example(blocks=2) let cid = client1.upload(data).get let duration = 100.u256 @@ -104,7 +104,7 @@ twonodessuite "REST API", debug1 = false, debug2 = false: check responseBefore.status == "400 Bad Request" check responseBefore.body == "Invalid parameters: parameters must satify `1 < (nodes - tolerance) ≥ tolerance`" - test "request storage fails if tolerance > nodes (underflow protection)": + test "request storage fails if tolerance > nodes (underflow protection)", twoNodesConfig: let data = await RandomChunker.example(blocks=2) let cid = client1.upload(data).get let duration = 100.u256 @@ -129,7 +129,7 @@ twonodessuite "REST API", debug1 = false, debug2 = false: check responseBefore.status == "400 Bad Request" check responseBefore.body == "Invalid parameters: `tolerance` cannot be greater than `nodes`" - test "request storage succeeds if nodes and tolerance within range": + test "request storage succeeds if nodes and tolerance within range", twoNodesConfig: let data = await RandomChunker.example(blocks=2) let cid = client1.upload(data).get let duration = 100.u256 @@ -153,42 +153,42 @@ twonodessuite "REST API", debug1 = false, debug2 = false: check responseBefore.status == "200 OK" - test "node accepts file uploads with content type": + test "node accepts file uploads with content type", twoNodesConfig: let headers = newHttpHeaders({"Content-Type": "text/plain"}) let response = client1.uploadRaw("some file contents", headers) check response.status == "200 OK" check response.body != "" - test "node accepts file uploads with content disposition": + test "node accepts file uploads with content disposition", twoNodesConfig: let headers = newHttpHeaders({"Content-Disposition": "attachment; filename=\"example.txt\""}) let response = client1.uploadRaw("some file contents", headers) check response.status == "200 OK" check response.body != "" - test "node accepts file uploads with content disposition without filename": + test "node accepts file uploads with content disposition without filename", twoNodesConfig: let headers = newHttpHeaders({"Content-Disposition": "attachment"}) let response = client1.uploadRaw("some file contents", headers) check response.status == "200 OK" check response.body != "" - test "upload fails if content disposition contains bad filename": + test "upload fails if content disposition contains bad filename", twoNodesConfig: let headers = newHttpHeaders({"Content-Disposition": "attachment; filename=\"exam*ple.txt\""}) let response = client1.uploadRaw("some file contents", headers) check response.status == "422 Unprocessable Entity" check response.body == "The filename is not valid." - test "upload fails if content type is invalid": + test "upload fails if content type is invalid", twoNodesConfig: let headers = newHttpHeaders({"Content-Type": "hello/world"}) let response = client1.uploadRaw("some file contents", headers) check response.status == "422 Unprocessable Entity" check response.body == "The MIME type is not valid." - test "node retrieve the metadata": + test "node retrieve the metadata", twoNodesConfig: let headers = newHttpHeaders({"Content-Type": "text/plain", "Content-Disposition": "attachment; filename=\"example.txt\""}) let uploadResponse = client1.uploadRaw("some file contents", headers) let cid = uploadResponse.body @@ -211,7 +211,7 @@ twonodessuite "REST API", debug1 = false, debug2 = false: check manifest.hasKey("uploadedAt") == true check manifest["uploadedAt"].getInt() > 0 - test "node set the headers when for download": + test "node set the headers when for download", twoNodesConfig: let headers = newHttpHeaders({ "Content-Disposition": "attachment; filename=\"example.txt\"", "Content-Type": "text/plain" diff --git a/tests/integration/testsales.nim b/tests/integration/testsales.nim index b904f405d..a9470bb38 100644 --- a/tests/integration/testsales.nim +++ b/tests/integration/testsales.nim @@ -11,22 +11,22 @@ proc findItem[T](items: seq[T], item: T): ?!T = return failure("Not found") -twonodessuite "Sales", debug1 = false, debug2 = false: +twonodessuite "Sales": - test "node handles new storage availability": + test "node handles new storage availability", twoNodesConfig: let availability1 = client1.postAvailability(totalSize=1.u256, duration=2.u256, minPrice=3.u256, maxCollateral=4.u256).get let availability2 = client1.postAvailability(totalSize=4.u256, duration=5.u256, minPrice=6.u256, maxCollateral=7.u256).get check availability1 != availability2 - test "node lists storage that is for sale": + test "node lists storage that is for sale", twoNodesConfig: let availability = client1.postAvailability(totalSize=1.u256, duration=2.u256, minPrice=3.u256, maxCollateral=4.u256).get check availability in client1.getAvailabilities().get - test "updating non-existing availability": + test "updating non-existing availability", twoNodesConfig: let nonExistingResponse = client1.patchAvailabilityRaw(AvailabilityId.example, duration=100.u256.some, minPrice=200.u256.some, maxCollateral=200.u256.some) check nonExistingResponse.status == "404 Not Found" - test "updating availability": + test "updating availability", twoNodesConfig: let availability = client1.postAvailability(totalSize=140000.u256, duration=200.u256, minPrice=300.u256, maxCollateral=300.u256).get client1.patchAvailability(availability.id, duration=100.u256.some, minPrice=200.u256.some, maxCollateral=200.u256.some) @@ -38,20 +38,20 @@ twonodessuite "Sales", debug1 = false, debug2 = false: check updatedAvailability.totalSize == 140000 check updatedAvailability.freeSize == 140000 - test "updating availability - freeSize is not allowed to be changed": + test "updating availability - freeSize is not allowed to be changed", twoNodesConfig: let availability = client1.postAvailability(totalSize=140000.u256, duration=200.u256, minPrice=300.u256, maxCollateral=300.u256).get let freeSizeResponse = client1.patchAvailabilityRaw(availability.id, freeSize=110000.u256.some) check freeSizeResponse.status == "400 Bad Request" check "not allowed" in freeSizeResponse.body - test "updating availability - updating totalSize": + test "updating availability - updating totalSize", twoNodesConfig: let availability = client1.postAvailability(totalSize=140000.u256, duration=200.u256, minPrice=300.u256, maxCollateral=300.u256).get client1.patchAvailability(availability.id, totalSize=100000.u256.some) let updatedAvailability = (client1.getAvailabilities().get).findItem(availability).get check updatedAvailability.totalSize == 100000 check updatedAvailability.freeSize == 100000 - test "updating availability - updating totalSize does not allow bellow utilized": + test "updating availability - updating totalSize does not allow bellow utilized", twoNodesConfig: let originalSize = 0xFFFFFF.u256 let data = await RandomChunker.example(blocks=8) let availability = client1.postAvailability(totalSize=originalSize, duration=20*60.u256, minPrice=300.u256, maxCollateral=300.u256).get diff --git a/tests/integration/testupdownload.nim b/tests/integration/testupdownload.nim index 716169769..73107b525 100644 --- a/tests/integration/testupdownload.nim +++ b/tests/integration/testupdownload.nim @@ -1,10 +1,10 @@ import pkg/codex/rest/json -import ./atwonodes +import ./twonodes import ../codex/examples import json from pkg/libp2p import Cid, `$` -atwonodessuite "Uploads and downloads": +twonodessuite "Uploads and downloads": test "node allows local file downloads", twoNodesConfig: let content1 = "some file contents" let content2 = "some other contents" diff --git a/tests/integration/twonodes.nim b/tests/integration/twonodes.nim index cce387acc..ac2d149ad 100644 --- a/tests/integration/twonodes.nim +++ b/tests/integration/twonodes.nim @@ -1,98 +1,34 @@ import std/os import std/macros -import std/httpclient -import ../ethertest +import pkg/questionable +import ./multinodes +import ./codexconfig +import ./codexprocess import ./codexclient -import ./nodes +import ./nodeconfigs -export ethertest export codexclient -export nodes +export multinodes -template twonodessuite*(name: string, debug1, debug2: bool | string, body) = - twonodessuite(name, $debug1, $debug2, false, body) +template twonodessuite*(name: string, body: untyped) = + multinodesuite name: + let twoNodesConfig {.inject, used.} = NodeConfigs(clients: CodexConfigs.init(nodes=2).some) -template twonodessuite*(name: string, debug1, debug2: string, logfile: bool, body) = - - - var node1 {.inject, used.}: NodeProcess - var node2 {.inject, used.}: NodeProcess + var node1 {.inject, used.}: CodexProcess + var node2 {.inject, used.}: CodexProcess var client1 {.inject, used.}: CodexClient var client2 {.inject, used.}: CodexClient var account1 {.inject, used.}: Address var account2 {.inject, used.}: Address - let dataDir1 = getTempDir() / "Codex1" - let dataDir2 = getTempDir() / "Codex2" - setup: - client1 = CodexClient.new("http://localhost:8080/api/codex/v1") - client2 = CodexClient.new("http://localhost:8081/api/codex/v1") account1 = accounts[0] account2 = accounts[1] - var node1Args = @[ - "--api-port=8080", - "--data-dir=" & dataDir1, - "--nat=127.0.0.1", - "--disc-ip=127.0.0.1", - "--disc-port=8090", - "--listen-addrs=/ip4/127.0.0.1/tcp/0", - "persistence", - "prover", - "--circom-r1cs=tests/circuits/fixtures/proof_main.r1cs", - "--circom-wasm=tests/circuits/fixtures/proof_main.wasm", - "--circom-zkey=tests/circuits/fixtures/proof_main.zkey", - "--eth-provider=http://127.0.0.1:8545", - "--eth-account=" & $account1 - ] - - if debug1 != "true" and debug1 != "false": - node1Args.add("--log-level=" & debug1) - if logfile: - node1Args.add("--log-file=tests/integration/logs/node1.log") - - node1 = startNode(node1Args, debug = debug1) - node1.waitUntilStarted() - - let bootstrap = (!client1.info()["spr"]).getStr() - - var node2Args = @[ - "--api-port=8081", - "--data-dir=" & dataDir2, - "--nat=127.0.0.1", - "--disc-ip=127.0.0.1", - "--disc-port=8091", - "--listen-addrs=/ip4/127.0.0.1/tcp/0", - "--bootstrap-node=" & bootstrap, - "persistence", - "prover", - "--circom-r1cs=tests/circuits/fixtures/proof_main.r1cs", - "--circom-wasm=tests/circuits/fixtures/proof_main.wasm", - "--circom-zkey=tests/circuits/fixtures/proof_main.zkey", - "--eth-provider=http://127.0.0.1:8545", - "--eth-account=" & $account2 - ] - - if debug2 != "true" and debug2 != "false": - node2Args.add("--log-level=" & debug2) - if logfile: - node2Args.add("--log-file=tests/integration/logs/node2.log") - - node2 = startNode(node2Args, debug = debug2) - node2.waitUntilStarted() - - # ensure that we have a recent block with a fresh timestamp - discard await send(ethProvider, "evm_mine") - - teardown: - client1.close() - client2.close() - - node1.stop() - node2.stop() + node1 = clients()[0] + node2 = clients()[1] - removeDir(dataDir1) - removeDir(dataDir2) + client1 = node1.client + client2 = node2.client body From 4170ded63166b84d27eb7c6db9da8f137d8ff1ea Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 16 Dec 2024 14:51:21 +0100 Subject: [PATCH 09/14] removes heartbeat log --- codex/node.nim | 7 ------- 1 file changed, 7 deletions(-) diff --git a/codex/node.nim b/codex/node.nim index 68f759a89..a43c9270a 100644 --- a/codex/node.nim +++ b/codex/node.nim @@ -745,13 +745,6 @@ proc start*(self: CodexNodeRef) {.async.} = error "Unable to start validator contract interactions: ", error=error.msg self.contracts.validator = ValidatorInteractions.none - proc heartbeat() {.async.} = - while true: - await sleepAsync(1000.millis) - warn "a" - - asyncSpawn heartbeat() - self.networkId = self.switch.peerInfo.peerId notice "Started codex node", id = self.networkId, addrs = self.switch.peerInfo.addrs From 101df5ec8f9ec783c5525b9328d34006ff2dfe7f Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 16 Dec 2024 15:25:15 +0100 Subject: [PATCH 10/14] applies multinodesuite in testsales --- tests/integration/testsales.nim | 74 ++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/tests/integration/testsales.nim b/tests/integration/testsales.nim index a9470bb38..26397f6dd 100644 --- a/tests/integration/testsales.nim +++ b/tests/integration/testsales.nim @@ -3,6 +3,9 @@ import pkg/codex/contracts import ./twonodes import ../codex/examples import ../contracts/time +import ./codexconfig +import ./codexclient +import ./nodeconfigs proc findItem[T](items: seq[T], item: T): ?!T = for tmp in items: @@ -11,54 +14,65 @@ proc findItem[T](items: seq[T], item: T): ?!T = return failure("Not found") -twonodessuite "Sales": - - test "node handles new storage availability", twoNodesConfig: - let availability1 = client1.postAvailability(totalSize=1.u256, duration=2.u256, minPrice=3.u256, maxCollateral=4.u256).get - let availability2 = client1.postAvailability(totalSize=4.u256, duration=5.u256, minPrice=6.u256, maxCollateral=7.u256).get +multinodesuite "Sales": + let salesConfig = NodeConfigs( + clients: CodexConfigs.init(nodes=1).some, + providers: CodexConfigs.init(nodes=1).some, + ) + + var host: CodexClient + var client: CodexClient + + setup: + host = providers()[0].client + client = clients()[0].client + + test "node handles new storage availability", salesConfig: + let availability1 = host.postAvailability(totalSize=1.u256, duration=2.u256, minPrice=3.u256, maxCollateral=4.u256).get + let availability2 = host.postAvailability(totalSize=4.u256, duration=5.u256, minPrice=6.u256, maxCollateral=7.u256).get check availability1 != availability2 - test "node lists storage that is for sale", twoNodesConfig: - let availability = client1.postAvailability(totalSize=1.u256, duration=2.u256, minPrice=3.u256, maxCollateral=4.u256).get - check availability in client1.getAvailabilities().get + test "node lists storage that is for sale", salesConfig: + let availability = host.postAvailability(totalSize=1.u256, duration=2.u256, minPrice=3.u256, maxCollateral=4.u256).get + check availability in host.getAvailabilities().get - test "updating non-existing availability", twoNodesConfig: - let nonExistingResponse = client1.patchAvailabilityRaw(AvailabilityId.example, duration=100.u256.some, minPrice=200.u256.some, maxCollateral=200.u256.some) + test "updating non-existing availability", salesConfig: + let nonExistingResponse = host.patchAvailabilityRaw(AvailabilityId.example, duration=100.u256.some, minPrice=200.u256.some, maxCollateral=200.u256.some) check nonExistingResponse.status == "404 Not Found" - test "updating availability", twoNodesConfig: - let availability = client1.postAvailability(totalSize=140000.u256, duration=200.u256, minPrice=300.u256, maxCollateral=300.u256).get + test "updating availability", salesConfig: + let availability = host.postAvailability(totalSize=140000.u256, duration=200.u256, minPrice=300.u256, maxCollateral=300.u256).get - client1.patchAvailability(availability.id, duration=100.u256.some, minPrice=200.u256.some, maxCollateral=200.u256.some) + host.patchAvailability(availability.id, duration=100.u256.some, minPrice=200.u256.some, maxCollateral=200.u256.some) - let updatedAvailability = (client1.getAvailabilities().get).findItem(availability).get + let updatedAvailability = (host.getAvailabilities().get).findItem(availability).get check updatedAvailability.duration == 100 check updatedAvailability.minPrice == 200 check updatedAvailability.maxCollateral == 200 check updatedAvailability.totalSize == 140000 check updatedAvailability.freeSize == 140000 - test "updating availability - freeSize is not allowed to be changed", twoNodesConfig: - let availability = client1.postAvailability(totalSize=140000.u256, duration=200.u256, minPrice=300.u256, maxCollateral=300.u256).get - let freeSizeResponse = client1.patchAvailabilityRaw(availability.id, freeSize=110000.u256.some) + test "updating availability - freeSize is not allowed to be changed", salesConfig: + let availability = host.postAvailability(totalSize=140000.u256, duration=200.u256, minPrice=300.u256, maxCollateral=300.u256).get + let freeSizeResponse = host.patchAvailabilityRaw(availability.id, freeSize=110000.u256.some) check freeSizeResponse.status == "400 Bad Request" check "not allowed" in freeSizeResponse.body - test "updating availability - updating totalSize", twoNodesConfig: - let availability = client1.postAvailability(totalSize=140000.u256, duration=200.u256, minPrice=300.u256, maxCollateral=300.u256).get - client1.patchAvailability(availability.id, totalSize=100000.u256.some) - let updatedAvailability = (client1.getAvailabilities().get).findItem(availability).get + test "updating availability - updating totalSize", salesConfig: + let availability = host.postAvailability(totalSize=140000.u256, duration=200.u256, minPrice=300.u256, maxCollateral=300.u256).get + host.patchAvailability(availability.id, totalSize=100000.u256.some) + let updatedAvailability = (host.getAvailabilities().get).findItem(availability).get check updatedAvailability.totalSize == 100000 check updatedAvailability.freeSize == 100000 - test "updating availability - updating totalSize does not allow bellow utilized", twoNodesConfig: + test "updating availability - updating totalSize does not allow bellow utilized", salesConfig: let originalSize = 0xFFFFFF.u256 let data = await RandomChunker.example(blocks=8) - let availability = client1.postAvailability(totalSize=originalSize, duration=20*60.u256, minPrice=300.u256, maxCollateral=300.u256).get + let availability = host.postAvailability(totalSize=originalSize, duration=20*60.u256, minPrice=300.u256, maxCollateral=300.u256).get # Lets create storage request that will utilize some of the availability's space - let cid = client2.upload(data).get - let id = client2.requestStorage( + let cid = client.upload(data).get + let id = client.requestStorage( cid, duration=20*60.u256, reward=400.u256, @@ -68,16 +82,16 @@ twonodessuite "Sales": nodes = 3, tolerance = 1).get - check eventually(client2.purchaseStateIs(id, "started"), timeout=10*60*1000) - let updatedAvailability = (client1.getAvailabilities().get).findItem(availability).get + check eventually(client.purchaseStateIs(id, "started"), timeout=10*60*1000) + let updatedAvailability = (host.getAvailabilities().get).findItem(availability).get check updatedAvailability.totalSize != updatedAvailability.freeSize let utilizedSize = updatedAvailability.totalSize - updatedAvailability.freeSize - let totalSizeResponse = client1.patchAvailabilityRaw(availability.id, totalSize=(utilizedSize-1.u256).some) + let totalSizeResponse = host.patchAvailabilityRaw(availability.id, totalSize=(utilizedSize-1.u256).some) check totalSizeResponse.status == "400 Bad Request" check "totalSize must be larger then current totalSize" in totalSizeResponse.body - client1.patchAvailability(availability.id, totalSize=(originalSize + 20000).some) - let newUpdatedAvailability = (client1.getAvailabilities().get).findItem(availability).get + host.patchAvailability(availability.id, totalSize=(originalSize + 20000).some) + let newUpdatedAvailability = (host.getAvailabilities().get).findItem(availability).get check newUpdatedAvailability.totalSize == originalSize + 20000 check newUpdatedAvailability.freeSize - updatedAvailability.freeSize == 20000 From 38c6eb1ca0b87084008b488e169c4917dcae7de5 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 16 Dec 2024 15:49:15 +0100 Subject: [PATCH 11/14] applies multinodesuite in testmarketplace --- tests/integration/testmarketplace.nim | 61 +++++++++++++++++---------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/tests/integration/testmarketplace.nim b/tests/integration/testmarketplace.nim index 2c6ff76f4..a4e35e309 100644 --- a/tests/integration/testmarketplace.nim +++ b/tests/integration/testmarketplace.nim @@ -5,22 +5,37 @@ import ./marketplacesuite import ./twonodes import ./nodeconfigs -twonodessuite "Marketplace": +multinodesuite "Marketplace": + let marketplaceConfig = NodeConfigs( + clients: CodexConfigs.init(nodes=1).some, + providers: CodexConfigs.init(nodes=1).some, + ) + + var host: CodexClient + var hostAccount: Address + var client: CodexClient + var clientAccount: Address + setup: + host = providers()[0].client + hostAccount = accounts[1] + client = clients()[0].client + clientAccount = accounts[0] # Account indices determined by order in which multinodesuite starts nodes. + # Our Hardhat configuration does use automine, which means that time tracked by `ethProvider.currentTime()` is not # advanced until blocks are mined and that happens only when transaction is submitted. # As we use in tests ethProvider.currentTime() which uses block timestamp this can lead to synchronization issues. await ethProvider.advanceTime(1.u256) - test "nodes negotiate contracts on the marketplace", twoNodesConfig: + test "nodes negotiate contracts on the marketplace", marketplaceConfig: let size = 0xFFFFFF.u256 let data = await RandomChunker.example(blocks=8) - # client 2 makes storage available - let availability = client2.postAvailability(totalSize=size, duration=20*60.u256, minPrice=300.u256, maxCollateral=300.u256).get + # host makes storage available + let availability = host.postAvailability(totalSize=size, duration=20*60.u256, minPrice=300.u256, maxCollateral=300.u256).get - # client 1 requests storage - let cid = client1.upload(data).get - let id = client1.requestStorage( + # client requests storage + let cid = client.upload(data).get + let id = client.requestStorage( cid, duration=20*60.u256, reward=400.u256, @@ -30,19 +45,19 @@ twonodessuite "Marketplace": nodes = 3, tolerance = 1).get - check eventually(client1.purchaseStateIs(id, "started"), timeout=10*60*1000) - let purchase = client1.getPurchase(id).get + check eventually(client.purchaseStateIs(id, "started"), timeout=10*60*1000) + let purchase = client.getPurchase(id).get check purchase.error == none string - let availabilities = client2.getAvailabilities().get + let availabilities = host.getAvailabilities().get check availabilities.len == 1 let newSize = availabilities[0].freeSize check newSize > 0 and newSize < size - let reservations = client2.getAvailabilityReservations(availability.id).get + let reservations = host.getAvailabilityReservations(availability.id).get check reservations.len == 3 check reservations[0].requestId == purchase.requestId - test "node slots gets paid out and rest of tokens are returned to client", twoNodesConfig: + test "node slots gets paid out and rest of tokens are returned to client", marketplaceConfig: let size = 0xFFFFFF.u256 let data = await RandomChunker.example(blocks = 8) let marketplace = Marketplace.new(Marketplace.address, ethProvider.getSigner()) @@ -52,13 +67,13 @@ twonodessuite "Marketplace": let duration = 20*60.u256 let nodes = 3'u - # client 2 makes storage available - let startBalanceHost = await token.balanceOf(account2) - discard client2.postAvailability(totalSize=size, duration=20*60.u256, minPrice=300.u256, maxCollateral=300.u256).get + # host makes storage available + let startBalanceHost = await token.balanceOf(hostAccount) + discard host.postAvailability(totalSize=size, duration=20*60.u256, minPrice=300.u256, maxCollateral=300.u256).get - # client 1 requests storage - let cid = client1.upload(data).get - let id = client1.requestStorage( + # client requests storage + let cid = client.upload(data).get + let id = client.requestStorage( cid, duration=duration, reward=reward, @@ -68,11 +83,11 @@ twonodessuite "Marketplace": nodes = nodes, tolerance = 1).get - check eventually(client1.purchaseStateIs(id, "started"), timeout=10*60*1000) - let purchase = client1.getPurchase(id).get + check eventually(client.purchaseStateIs(id, "started"), timeout=10*60*1000) + let purchase = client.getPurchase(id).get check purchase.error == none string - let clientBalanceBeforeFinished = await token.balanceOf(account1) + let clientBalanceBeforeFinished = await token.balanceOf(clientAccount) # Proving mechanism uses blockchain clock to do proving/collect/cleanup round # hence we must use `advanceTime` over `sleepAsync` as Hardhat does mine new blocks @@ -80,11 +95,11 @@ twonodessuite "Marketplace": await ethProvider.advanceTime(duration) # Checking that the hosting node received reward for at least the time between - check eventually (await token.balanceOf(account2)) - startBalanceHost >= (duration-5*60)*reward*nodes.u256 + check eventually (await token.balanceOf(hostAccount)) - startBalanceHost >= (duration-5*60)*reward*nodes.u256 # Checking that client node receives some funds back that were not used for the host nodes check eventually( - (await token.balanceOf(account1)) - clientBalanceBeforeFinished > 0, + (await token.balanceOf(clientAccount)) - clientBalanceBeforeFinished > 0, timeout = 10*1000 # give client a bit of time to withdraw its funds ) From d884b944ad8061843327f287d80c014aa2883bb4 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 16 Dec 2024 15:54:57 +0100 Subject: [PATCH 12/14] fixes account fetch for host and client in testmarketplace --- tests/integration/testmarketplace.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/testmarketplace.nim b/tests/integration/testmarketplace.nim index a4e35e309..f1752c1e7 100644 --- a/tests/integration/testmarketplace.nim +++ b/tests/integration/testmarketplace.nim @@ -18,9 +18,9 @@ multinodesuite "Marketplace": setup: host = providers()[0].client - hostAccount = accounts[1] + hostAccount = providers()[0].ethAccount client = clients()[0].client - clientAccount = accounts[0] # Account indices determined by order in which multinodesuite starts nodes. + clientAccount = clients()[0].ethAccount # Our Hardhat configuration does use automine, which means that time tracked by `ethProvider.currentTime()` is not # advanced until blocks are mined and that happens only when transaction is submitted. From ff0130c59dfeda5d2ac8587385f227d4512e3d86 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 16 Dec 2024 16:14:11 +0100 Subject: [PATCH 13/14] adds waitTillNextPeriod at end of marketplace test --- tests/integration/testmarketplace.nim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration/testmarketplace.nim b/tests/integration/testmarketplace.nim index f1752c1e7..cc664459b 100644 --- a/tests/integration/testmarketplace.nim +++ b/tests/integration/testmarketplace.nim @@ -173,6 +173,8 @@ marketplacesuite "Marketplace payouts": await ethProvider.advanceTime(expiry.u256) check eventually providerApi.saleStateIs(slotId, "SaleCancelled") + await advanceToNextPeriod() + check eventually ( let endBalanceProvider = (await token.balanceOf(provider.ethAccount)); endBalanceProvider > startBalanceProvider and From f1b1027d9d9eb752d21594ea723b889ab8eff14d Mon Sep 17 00:00:00 2001 From: Ben Date: Tue, 17 Dec 2024 09:09:31 +0100 Subject: [PATCH 14/14] Uses marketplacesuite in testmarketplace --- tests/integration/testmarketplace.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/testmarketplace.nim b/tests/integration/testmarketplace.nim index cc664459b..17a3ec17d 100644 --- a/tests/integration/testmarketplace.nim +++ b/tests/integration/testmarketplace.nim @@ -5,7 +5,7 @@ import ./marketplacesuite import ./twonodes import ./nodeconfigs -multinodesuite "Marketplace": +marketplacesuite "Marketplace": let marketplaceConfig = NodeConfigs( clients: CodexConfigs.init(nodes=1).some, providers: CodexConfigs.init(nodes=1).some,