diff --git a/codex/contracts/requests.nim b/codex/contracts/requests.nim index 1e8b94a2..4c6e8b10 100644 --- a/codex/contracts/requests.nim +++ b/codex/contracts/requests.nim @@ -3,6 +3,7 @@ import pkg/contractabi import pkg/nimcrypto import pkg/ethers/fields import pkg/questionable/results +import pkg/stew/byteutils export contractabi @@ -63,6 +64,15 @@ func toArray*(id: RequestId | SlotId | Nonce): array[32, byte] = proc `$`*(id: RequestId | SlotId | Nonce): string = id.toArray.toHex +proc fromHex*(T: type RequestId, hex: string): T = + T array[32, byte].fromHex(hex) + +proc fromHex*(T: type SlotId, hex: string): T = + T array[32, byte].fromHex(hex) + +proc fromHex*(T: type Nonce, hex: string): T = + T array[32, byte].fromHex(hex) + func fromTuple(_: type StorageRequest, tupl: tuple): StorageRequest = StorageRequest( client: tupl[0], diff --git a/codex/rest/json.nim b/codex/rest/json.nim index 1a7941eb..d53a3ac6 100644 --- a/codex/rest/json.nim +++ b/codex/rest/json.nim @@ -60,4 +60,5 @@ func `%`*(purchase: Purchase): JsonNode = "state": (purchase.state as PurchaseState).?description |? "none", "error": purchase.error.?msg, "request": purchase.request, + "requestId": purchase.requestId } diff --git a/tests/integration/testproofs.nim b/tests/integration/testproofs.nim index b9851aef..7581b520 100644 --- a/tests/integration/testproofs.nim +++ b/tests/integration/testproofs.nim @@ -1,4 +1,4 @@ -import std/os +import std/sequtils import codex/contracts/marketplace import codex/contracts/deployment import codex/periods @@ -96,18 +96,131 @@ twonodessuite "Proving integration test", debug1=false, debug2=false: await subscription.unsubscribe() stopValidator(validator) +invalidproofsuite "Simulate invalid proofs", debugClient=false, debugProvider=false: + + var marketplace: Marketplace + var period: uint64 + var proofSubmitted: Future[void] + var subscription: Subscription + var submitted: seq[seq[byte]] + var missed: UInt256 + var slotId: SlotId + var validator: NodeProcess + + + proc startValidator: NodeProcess = + let datadir = getTempDir() / "CodexValidator" + startNode([ + "--data-dir=" & datadir, + "--api-port=8180", + "--disc-port=8190", + "--validator", + "--eth-account=" & $accounts[2] + ], debug = true) + + proc stopValidator(node: NodeProcess) = + node.stop() + removeDir(getTempDir() / "CodexValidator") + + setup: + let deployment = Deployment.init() + marketplace = Marketplace.new(!deployment.address(Marketplace), provider) + period = (await marketplace.config()).proofs.period.truncate(uint64) + await provider.getSigner(accounts[0]).mint() + await provider.getSigner(accounts[1]).mint() + await provider.getSigner(accounts[1]).deposit() + proofSubmitted = newFuture[void]("proofSubmitted") + proc onProofSubmitted(event: ProofSubmitted) = + debugEcho ">>> proof submitted: ", event.proof + submitted.add(event.proof) + proofSubmitted.complete() + proofSubmitted = newFuture[void]("proofSubmitted") + subscription = await marketplace.subscribe(ProofSubmitted, onProofSubmitted) + missed = 0.u256 + slotId = SlotId(array[32, byte].default) + validator = startValidator() + + teardown: + await subscription.unsubscribe() + validator.stopValidator() + + proc waitUntilPurchaseIsStarted(proofProbability: uint64 = 3, + duration: uint64 = 100 * period, + expiry: uint64 = 30) {.async.} = + + if clients().len < 1 or providers().len < 1: + raiseAssert("must start at least one client and one provider") + + let client = clients()[0].restClient + let storageProvider = providers()[0].restClient + + discard storageProvider.postAvailability( + size=0xFFFFF, + duration=duration, + minPrice=300 + ) + let cid = client.upload("some file contents " & $ getTime().toUnix) + let expiry = (await provider.currentTime()) + expiry.u256 + let purchase = client.requestStorage( + cid, + expiry=expiry, + duration=duration, + proofProbability=proofProbability, + reward=400 + ) + check eventually client.getPurchase(purchase){"state"} == %"started" + let requestId = RequestId.fromHex client.getPurchase(purchase){"requestId"}.getStr + slotId = slotId(requestId, 0.u256) + + + proc advanceToNextPeriod() {.async.} = + await provider.advanceTime(period.u256) + + proc waitForProvingRounds(rounds: Positive) {.async.} = + var rnds = rounds - 1 # proof round runs prior to advancing + missed += await marketplace.missingProofs(slotId) + + while rnds > 0: + await advanceToNextPeriod() + rnds -= 1 + + proc invalid(proofs: seq[seq[byte]]): uint = + proofs.count(@[]).uint + test "simulates invalid proof every N proofs": # TODO: waiting on validation work to be completed before these tests are possible # 1. instantiate node manually (startNode) with --simulate-failed-proofs=3 # 2. check that the number of expected proofs are missed - check 1 == 1 + let failEveryNProofs = 3 + let totalProofs = 6 + let expectedInvalid = totalProofs div failEveryNProofs + let expectedValid = totalProofs - expectedInvalid + startProviderNode(failEveryNProofs.uint) - test "does not simulate invalid proof when --simulate-failed-proofs is 0": - # 1. instantiate node manually (startNode) with --simulate-failed-proofs=0 - # 2. check that the number of expected missed proofs is 0 - check 1 == 1 + await waitUntilPurchaseIsStarted(proofProbability=1) + await waitForProvingRounds(totalProofs) - test "does not simulate invalid proof when chainId is 1": - # 1. instantiate node manually (startNode) with --simulate-failed-proofs=3 - # 2. check that the number of expected missed proofs is 0 - check 1 == 1 + check eventually submitted.len == expectedValid + check missed.truncate(int) == expectedInvalid + + + # await waitUntilPurchaseIsStarted(proofProbability=1) + # var proofWasSubmitted = false + # proc onProofSubmitted(event: ProofSubmitted) = + # proofWasSubmitted = true + # let subscription = await marketplace.subscribe(ProofSubmitted, onProofSubmitted) + # await provider.advanceTime(period.u256) + # check eventually proofWasSubmitted + # await subscription.unsubscribe() + + # check 1 == 1 + + # test "does not simulate invalid proof when --simulate-failed-proofs is 0": + # # 1. instantiate node manually (startNode) with --simulate-failed-proofs=0 + # # 2. check that the number of expected missed proofs is 0 + # check 1 == 1 + + # test "does not simulate invalid proof when chainId is 1": + # # 1. instantiate node manually (startNode) with --simulate-failed-proofs=3 + # # 2. check that the number of expected missed proofs is 0 + # check 1 == 1 \ No newline at end of file diff --git a/tests/integration/twonodes.nim b/tests/integration/twonodes.nim index 306a8af1..fe6cce9b 100644 --- a/tests/integration/twonodes.nim +++ b/tests/integration/twonodes.nim @@ -60,3 +60,85 @@ template twonodessuite*(name: string, debug1, debug2: bool, body) = removeDir(dataDir2) body + +type + Role = enum + Client, + Provider + RunningNode* = ref object + role: Role + node: NodeProcess + restClient: CodexClient + datadir: string + +template invalidproofsuite*(name: string, debugClient, debugProvider: bool, body) = + + + + ethersuite name: + + var running: seq[RunningNode] + var bootstrap: string + + proc newNodeProcess(index: int, + addlOptions: seq[string], + debug: bool): (NodeProcess, string) = + + let datadir = getTempDir() / "Codex" & $index + let node = startNode(@[ + "--api-port=" & $(8080 + index), + "--data-dir=" & datadir, + "--nat=127.0.0.1", + "--disc-ip=127.0.0.1", + "--disc-port=" & $(8090 + index), + "--persistence", + "--eth-account=" & $accounts[index] + ].concat(addlOptions), debug = debug) + debugEcho "started new codex node listening with rest api listening on port ", 8080 + index + (node, datadir) + + proc newCodexClient(index: int): CodexClient = + debugEcho "started new rest client talking to port ", 8080 + index + CodexClient.new("http://localhost:" & $(8080 + index) & "/api/codex/v1") + + proc startClientNode() = + let index = running.len + let (node, datadir) = newNodeProcess(index, @[], debugClient) + let restClient = newCodexClient(index) + running.add RunningNode(role: Role.Client, + node: node, + restClient: restClient, + datadir: datadir) + debugEcho "started client node, index ", index + + proc startProviderNode(failEveryNProofs: uint) = + let index = running.len + let (node, datadir) = newNodeProcess(index, @[ + "--bootstrap-node=" & bootstrap, + "--simulate-proof-failures=" & $failEveryNProofs], + debugProvider) + let restClient = newCodexClient(index) + running.add RunningNode(role: Role.Provider, + node: node, + restClient: restClient, + datadir: datadir) + debugEcho "started provider node, index ", index + + proc clients(): seq[RunningNode] = + running.filter(proc(r: RunningNode): bool = r.role == Role.Client) + + proc providers(): seq[RunningNode] = + running.filter(proc(r: RunningNode): bool = r.role == Role.Provider) + + setup: + startClientNode() + + bootstrap = running[0].restClient.info()["spr"].getStr() + + teardown: + for r in running: + r.restClient.close() + r.node.stop() + removeDir(r.datadir) + + body