import std/sequtils import std/os from std/times import getTime, toUnix import pkg/codex/contracts import pkg/codex/logutils import pkg/codex/periods import ../contracts/time import ../contracts/deployment import ./twonodes import ./multinodes logScope: topics = "test proofs" twonodessuite "Proving integration test", debug1=false, debug2=false: let validatorDir = getTempDir() / "CodexValidator" var marketplace: Marketplace var period: uint64 proc purchaseStateIs(client: CodexClient, id: PurchaseId, state: string): bool = client.getPurchase(id).option.?state == some state setup: marketplace = Marketplace.new(Marketplace.address, ethProvider) period = (await marketplace.config()).proofs.period.truncate(uint64) # 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) proc waitUntilPurchaseIsStarted( proofProbability: uint64 = 3, duration: uint64 = 100 * period, expiry: uint64 = 30 ) {.async: (handleException: true, raises: [AsyncExceptionError]).} = discard client2.postAvailability( size=0xFFFFF.u256, duration=duration.u256, minPrice=300.u256, maxCollateral=200.u256 ) let cid = client1.upload("some file contents").get let expiry = (await ethProvider.currentTime()) + expiry.u256 let id = client1.requestStorage( cid, expiry=expiry, duration=duration.u256, proofProbability=proofProbability.u256, collateral=100.u256, reward=400.u256 ).get check eventually client1.purchaseStateIs(id, "started") proc advanceToNextPeriod {.async.} = let periodicity = Periodicity(seconds: period.u256) let currentPeriod = periodicity.periodOf(await ethProvider.currentTime()) let endOfPeriod = periodicity.periodEnd(currentPeriod) await ethProvider.advanceTimeTo(endOfPeriod + 1) proc startValidator: NodeProcess = let validator = startNode( [ "--data-dir=" & validatorDir, "--api-port=8089", "--disc-port=8099", "--listen-addrs=/ip4/127.0.0.1/tcp/0", "--validator", "--eth-account=" & $accounts[2] ], debug = false ) validator.waitUntilStarted() validator proc stopValidator(node: NodeProcess) = node.stop() removeDir(validatorDir) test "hosts submit periodic proofs for slots they fill": await waitUntilPurchaseIsStarted(proofProbability=1) var proofWasSubmitted = false proc onProofSubmitted(event: ProofSubmitted) = proofWasSubmitted = true let subscription = await marketplace.subscribe(ProofSubmitted, onProofSubmitted) await ethProvider.advanceTime(period.u256) check eventually proofWasSubmitted await subscription.unsubscribe() test "validator will mark proofs as missing": let validator = startValidator() await waitUntilPurchaseIsStarted(proofProbability=1) node2.stop() var slotWasFreed = false proc onSlotFreed(event: SlotFreed) = slotWasFreed = true let subscription = await marketplace.subscribe(SlotFreed, onSlotFreed) for _ in 0..<100: if slotWasFreed: break else: await advanceToNextPeriod() await sleepAsync(1.seconds) check slotWasFreed await subscription.unsubscribe() stopValidator(validator) multinodesuite "Simulate invalid proofs", StartNodes.init(clients=1'u, providers=0'u, validators=1'u), DebugNodes.init(client=false, ethProvider=false, validator=false): proc purchaseStateIs(client: CodexClient, id: PurchaseId, state: string): bool = client.getPurchase(id).option.?state == some state var marketplace: Marketplace var period: uint64 var slotId: SlotId setup: marketplace = Marketplace.new(Marketplace.address, ethProvider) let config = await marketplace.config() period = config.proofs.period.truncate(uint64) slotId = SlotId(array[32, byte].default) # ensure we aren't reusing from prev test # 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) proc periods(p: Ordinal | uint): uint64 = when p is uint: p * period else: p.uint * period proc advanceToNextPeriod {.async.} = let periodicity = Periodicity(seconds: period.u256) let currentPeriod = periodicity.periodOf(await ethProvider.currentTime()) let endOfPeriod = periodicity.periodEnd(currentPeriod) await ethProvider.advanceTimeTo(endOfPeriod + 1) proc waitUntilPurchaseIsStarted( proofProbability: uint64 = 1, duration: uint64 = 12.periods, expiry: uint64 = 4.periods ) {.async: (handleException: true, raises: [AsyncExceptionError]).} = if clients().len < 1 or providers().len < 1: raiseAssert("must start at least one client and one ethProvider") let client = clients()[0].restClient let storageProvider = providers()[0].restClient discard storageProvider.postAvailability( size=0xFFFFF.u256, duration=duration.u256, minPrice=300.u256, maxCollateral=200.u256 ) let cid = client.upload("some file contents " & $ getTime().toUnix).get let expiry = (await ethProvider.currentTime()) + expiry.u256 # avoid timing issues by filling the slot at the start of the next period await advanceToNextPeriod() let id = client.requestStorage( cid, expiry=expiry, duration=duration.u256, proofProbability=proofProbability.u256, collateral=100.u256, reward=400.u256 ).get check eventually client.purchaseStateIs(id, "started") let purchase = client.getPurchase(id).get slotId = slotId(purchase.requestId, 0.u256) # TODO: these are very loose tests in that they are not testing EXACTLY how # proofs were marked as missed by the validator. These tests should be # tightened so that they are showing, as an integration test, that specific # proofs are being marked as missed by the validator. test "slot is freed after too many invalid proofs submitted": let failEveryNProofs = 2'u let totalProofs = 100'u startProviderNode(failEveryNProofs) await waitUntilPurchaseIsStarted(duration=totalProofs.periods) var slotWasFreed = false proc onSlotFreed(event: SlotFreed) = if slotId(event.requestId, event.slotIndex) == slotId: slotWasFreed = true let subscription = await marketplace.subscribe(SlotFreed, onSlotFreed) for _ in 0..