Fix concurrency issues (#993)

* Use http subscriptions instead of websocket for tests

To work around this issue when subscriptions are
inactive for more than 5 minutes:
https://github.com/NomicFoundation/hardhat/issues/2053

Use 100 millisecond polling; default polling interval
of 4 seconds is too close to the 5 second timeout for
`check eventually`.

* use .confirm(1) instead of confirm(0)

confirm(0) doesn't wait at all, confirm(1) waits
for the transaction to be mined

* speed up partial payout integration test

* update nim-ethers to version 0.10.0

includes fixes for http polling and .confirm()

* fix timing of marketplace tests

allow for a bit more time to withdraw funds

* use .confirm(1) in marketplace tests

to ensure that the transaction has been processed
before continuing with the test

* fix timing issue in validation unit test

* fix proof integration test

there were two logic errors in this test:
- a slot is freed anyway at the end of the contract
- when starting the request takes a long time, the
  first slot can already be freed because there were
  too many missing proofs

* fix intermittent error in contract tests

currentTime() doesn't always correctly reflect
the time of the next transaction

* reduce number of slots in integration test

otherwise the windows runner in the CI won't
be able to start the request before it expires

* fix timing in purchasing test

allow for a bit more time for a request to
be submitted

* fix timing of request submission in test

windows ci is so slow, it can take up to 40 seconds
just to submit a storage request to hardhat

* increase proof period to 90 seconds

* adjust timing of integration tests

reason: with the increased period length of 90 seconds, it
can take longer to wait for a stable challenge at the
beginning of a period.

* increase CI timeout to 2 hours

* Fix slow builds on windows

apparently it takes windows 2-3 seconds to
resolve "localhost" to 127.0.0.1 for every
json-rpc connection that we make 🤦
This commit is contained in:
markspanbroek 2024-11-25 12:23:04 +01:00 committed by GitHub
parent 6038fb456e
commit 29433bad9a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 120 additions and 98 deletions

View File

@ -26,7 +26,7 @@ jobs:
name: ${{ matrix.os }}-${{ matrix.tests }}-${{ matrix.cpu }}-${{ matrix.nim_version }} name: ${{ matrix.os }}-${{ matrix.tests }}-${{ matrix.cpu }}-${{ matrix.nim_version }}
runs-on: ${{ matrix.builder }} runs-on: ${{ matrix.builder }}
timeout-minutes: 100 timeout-minutes: 120
steps: steps:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v4 uses: actions/checkout@v4

View File

@ -53,7 +53,7 @@ proc approveFunds(market: OnChainMarket, amount: UInt256) {.async.} =
convertEthersError: convertEthersError:
let tokenAddress = await market.contract.token() let tokenAddress = await market.contract.token()
let token = Erc20Token.new(tokenAddress, market.signer) let token = Erc20Token.new(tokenAddress, market.signer)
discard await token.increaseAllowance(market.contract.address(), amount).confirm(0) discard await token.increaseAllowance(market.contract.address(), amount).confirm(1)
method getZkeyHash*(market: OnChainMarket): Future[?string] {.async.} = method getZkeyHash*(market: OnChainMarket): Future[?string] {.async.} =
let config = await market.contract.configuration() let config = await market.contract.configuration()
@ -99,7 +99,7 @@ method requestStorage(market: OnChainMarket, request: StorageRequest){.async.} =
convertEthersError: convertEthersError:
debug "Requesting storage" debug "Requesting storage"
await market.approveFunds(request.price()) await market.approveFunds(request.price())
discard await market.contract.requestStorage(request).confirm(0) discard await market.contract.requestStorage(request).confirm(1)
method getRequest(market: OnChainMarket, method getRequest(market: OnChainMarket,
id: RequestId): Future[?StorageRequest] {.async.} = id: RequestId): Future[?StorageRequest] {.async.} =
@ -171,7 +171,7 @@ method fillSlot(market: OnChainMarket,
await market.approveFunds(collateral) await market.approveFunds(collateral)
trace "calling fillSlot on contract" trace "calling fillSlot on contract"
discard await market.contract.fillSlot(requestId, slotIndex, proof).confirm(0) discard await market.contract.fillSlot(requestId, slotIndex, proof).confirm(1)
trace "fillSlot transaction completed" trace "fillSlot transaction completed"
method freeSlot*(market: OnChainMarket, slotId: SlotId) {.async.} = method freeSlot*(market: OnChainMarket, slotId: SlotId) {.async.} =
@ -191,13 +191,13 @@ method freeSlot*(market: OnChainMarket, slotId: SlotId) {.async.} =
# recipient (the contract will use msg.sender for both) # recipient (the contract will use msg.sender for both)
freeSlot = market.contract.freeSlot(slotId) freeSlot = market.contract.freeSlot(slotId)
discard await freeSlot.confirm(0) discard await freeSlot.confirm(1)
method withdrawFunds(market: OnChainMarket, method withdrawFunds(market: OnChainMarket,
requestId: RequestId) {.async.} = requestId: RequestId) {.async.} =
convertEthersError: convertEthersError:
discard await market.contract.withdrawFunds(requestId).confirm(0) discard await market.contract.withdrawFunds(requestId).confirm(1)
method isProofRequired*(market: OnChainMarket, method isProofRequired*(market: OnChainMarket,
id: SlotId): Future[bool] {.async.} = id: SlotId): Future[bool] {.async.} =
@ -230,13 +230,13 @@ method submitProof*(market: OnChainMarket,
id: SlotId, id: SlotId,
proof: Groth16Proof) {.async.} = proof: Groth16Proof) {.async.} =
convertEthersError: convertEthersError:
discard await market.contract.submitProof(id, proof).confirm(0) discard await market.contract.submitProof(id, proof).confirm(1)
method markProofAsMissing*(market: OnChainMarket, method markProofAsMissing*(market: OnChainMarket,
id: SlotId, id: SlotId,
period: Period) {.async.} = period: Period) {.async.} =
convertEthersError: convertEthersError:
discard await market.contract.markProofAsMissing(id, period).confirm(0) discard await market.contract.markProofAsMissing(id, period).confirm(1)
method canProofBeMarkedAsMissing*( method canProofBeMarkedAsMissing*(
market: OnChainMarket, market: OnChainMarket,
@ -264,7 +264,7 @@ method reserveSlot*(
slotIndex, slotIndex,
# reserveSlot runs out of gas for unknown reason, but 100k gas covers it # reserveSlot runs out of gas for unknown reason, but 100k gas covers it
TransactionOverrides(gasLimit: some 100000.u256) TransactionOverrides(gasLimit: some 100000.u256)
).confirm(0) ).confirm(1)
method canReserveSlot*( method canReserveSlot*(
market: OnChainMarket, market: OnChainMarket,

View File

@ -128,14 +128,14 @@ asyncchecksuite "validation":
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral) await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
market.setCanProofBeMarkedAsMissing(slot.id, true) market.setCanProofBeMarkedAsMissing(slot.id, true)
advanceToNextPeriod() advanceToNextPeriod()
await sleepAsync(1.millis) await sleepAsync(100.millis) # allow validation loop to run
check market.markedAsMissingProofs.contains(slot.id) check market.markedAsMissingProofs.contains(slot.id)
test "when a proof can not be marked as missing, it will not be marked": test "when a proof can not be marked as missing, it will not be marked":
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral) await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
market.setCanProofBeMarkedAsMissing(slot.id, false) market.setCanProofBeMarkedAsMissing(slot.id, false)
advanceToNextPeriod() advanceToNextPeriod()
await sleepAsync(1.millis) await sleepAsync(100.millis) # allow validation loop to run
check market.markedAsMissingProofs.len == 0 check market.markedAsMissingProofs.len == 0
test "it does not monitor more than the maximum number of slots": test "it does not monitor more than the maximum number of slots":

View File

@ -30,7 +30,7 @@ ethersuite "On-Chain Clock":
let waiting = clock.waitUntil(future) let waiting = clock.waitUntil(future)
discard await ethProvider.send("evm_setNextBlockTimestamp", @[%future]) discard await ethProvider.send("evm_setNextBlockTimestamp", @[%future])
discard await ethProvider.send("evm_mine") discard await ethProvider.send("evm_mine")
check await waiting.withTimeout(chronos.milliseconds(100)) check await waiting.withTimeout(chronos.milliseconds(500))
test "can wait until a certain time is reached by the wall-clock": test "can wait until a certain time is reached by the wall-clock":
let future = clock.now() + 1 # seconds let future = clock.now() + 1 # seconds

View File

@ -45,13 +45,13 @@ ethersuite "Marketplace contracts":
request.client = await client.getAddress() request.client = await client.getAddress()
switchAccount(client) switchAccount(client)
discard await token.approve(marketplace.address, request.price) discard await token.approve(marketplace.address, request.price).confirm(1)
discard await marketplace.requestStorage(request) discard await marketplace.requestStorage(request).confirm(1)
switchAccount(host) switchAccount(host)
discard await token.approve(marketplace.address, request.ask.collateral) discard await token.approve(marketplace.address, request.ask.collateral).confirm(1)
discard await marketplace.reserveSlot(request.id, 0.u256) discard await marketplace.reserveSlot(request.id, 0.u256).confirm(1)
filledAt = await ethProvider.currentTime() let receipt = await marketplace.fillSlot(request.id, 0.u256, proof).confirm(1)
discard await marketplace.fillSlot(request.id, 0.u256, proof) filledAt = await ethProvider.blockTime(BlockTag.init(!receipt.blockNumber))
slotId = request.slotId(0.u256) slotId = request.slotId(0.u256)
proc waitUntilProofRequired(slotId: SlotId) {.async.} = proc waitUntilProofRequired(slotId: SlotId) {.async.} =
@ -65,14 +65,14 @@ ethersuite "Marketplace contracts":
proc startContract() {.async.} = proc startContract() {.async.} =
for slotIndex in 1..<request.ask.slots: for slotIndex in 1..<request.ask.slots:
discard await token.approve(marketplace.address, request.ask.collateral) discard await token.approve(marketplace.address, request.ask.collateral).confirm(1)
discard await marketplace.reserveSlot(request.id, slotIndex.u256) discard await marketplace.reserveSlot(request.id, slotIndex.u256).confirm(1)
discard await marketplace.fillSlot(request.id, slotIndex.u256, proof) discard await marketplace.fillSlot(request.id, slotIndex.u256, proof).confirm(1)
test "accept marketplace proofs": test "accept marketplace proofs":
switchAccount(host) switchAccount(host)
await waitUntilProofRequired(slotId) await waitUntilProofRequired(slotId)
discard await marketplace.submitProof(slotId, proof) discard await marketplace.submitProof(slotId, proof).confirm(1)
test "can mark missing proofs": test "can mark missing proofs":
switchAccount(host) switchAccount(host)
@ -81,7 +81,7 @@ ethersuite "Marketplace contracts":
let endOfPeriod = periodicity.periodEnd(missingPeriod) let endOfPeriod = periodicity.periodEnd(missingPeriod)
await ethProvider.advanceTimeTo(endOfPeriod + 1) await ethProvider.advanceTimeTo(endOfPeriod + 1)
switchAccount(client) switchAccount(client)
discard await marketplace.markProofAsMissing(slotId, missingPeriod) discard await marketplace.markProofAsMissing(slotId, missingPeriod).confirm(1)
test "can be paid out at the end": test "can be paid out at the end":
switchAccount(host) switchAccount(host)
@ -90,7 +90,7 @@ ethersuite "Marketplace contracts":
let requestEnd = await marketplace.requestEnd(request.id) let requestEnd = await marketplace.requestEnd(request.id)
await ethProvider.advanceTimeTo(requestEnd.u256 + 1) await ethProvider.advanceTimeTo(requestEnd.u256 + 1)
let startBalance = await token.balanceOf(address) let startBalance = await token.balanceOf(address)
discard await marketplace.freeSlot(slotId) discard await marketplace.freeSlot(slotId).confirm(1)
let endBalance = await token.balanceOf(address) let endBalance = await token.balanceOf(address)
check endBalance == (startBalance + expectedPayout(requestEnd.u256) + request.ask.collateral) check endBalance == (startBalance + expectedPayout(requestEnd.u256) + request.ask.collateral)
@ -103,7 +103,7 @@ ethersuite "Marketplace contracts":
let startBalanceHost = await token.balanceOf(hostAddress) let startBalanceHost = await token.balanceOf(hostAddress)
let startBalanceReward = await token.balanceOf(rewardRecipient) let startBalanceReward = await token.balanceOf(rewardRecipient)
let startBalanceCollateral = await token.balanceOf(collateralRecipient) let startBalanceCollateral = await token.balanceOf(collateralRecipient)
discard await marketplace.freeSlot(slotId, rewardRecipient, collateralRecipient) discard await marketplace.freeSlot(slotId, rewardRecipient, collateralRecipient).confirm(1)
let endBalanceHost = await token.balanceOf(hostAddress) let endBalanceHost = await token.balanceOf(hostAddress)
let endBalanceReward = await token.balanceOf(rewardRecipient) let endBalanceReward = await token.balanceOf(rewardRecipient)
let endBalanceCollateral = await token.balanceOf(collateralRecipient) let endBalanceCollateral = await token.balanceOf(collateralRecipient)
@ -120,4 +120,5 @@ ethersuite "Marketplace contracts":
await ethProvider.advanceTime(periodicity.seconds) await ethProvider.advanceTime(periodicity.seconds)
check await marketplace check await marketplace
.markProofAsMissing(slotId, missingPeriod) .markProofAsMissing(slotId, missingPeriod)
.confirm(1)
.reverts("Slot not accepting proofs") .reverts("Slot not accepting proofs")

View File

@ -114,8 +114,7 @@ ethersuite "On-Chain Market":
receivedAsks.add(ask) receivedAsks.add(ask)
let subscription = await market.subscribeRequests(onRequest) let subscription = await market.subscribeRequests(onRequest)
await market.requestStorage(request) await market.requestStorage(request)
check receivedIds == @[request.id] check eventually receivedIds == @[request.id] and receivedAsks == @[request.ask]
check receivedAsks == @[request.ask]
await subscription.unsubscribe() await subscription.unsubscribe()
test "supports filling of slots": test "supports filling of slots":
@ -181,8 +180,7 @@ ethersuite "On-Chain Market":
let subscription = await market.subscribeSlotFilled(onSlotFilled) let subscription = await market.subscribeSlotFilled(onSlotFilled)
await market.reserveSlot(request.id, slotIndex) await market.reserveSlot(request.id, slotIndex)
await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral) await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral)
check receivedIds == @[request.id] check eventually receivedIds == @[request.id] and receivedSlotIndices == @[slotIndex]
check receivedSlotIndices == @[slotIndex]
await subscription.unsubscribe() await subscription.unsubscribe()
test "subscribes only to a certain slot": test "subscribes only to a certain slot":
@ -194,10 +192,9 @@ ethersuite "On-Chain Market":
let subscription = await market.subscribeSlotFilled(request.id, slotIndex, onSlotFilled) let subscription = await market.subscribeSlotFilled(request.id, slotIndex, onSlotFilled)
await market.reserveSlot(request.id, otherSlot) await market.reserveSlot(request.id, otherSlot)
await market.fillSlot(request.id, otherSlot, proof, request.ask.collateral) await market.fillSlot(request.id, otherSlot, proof, request.ask.collateral)
check receivedSlotIndices.len == 0
await market.reserveSlot(request.id, slotIndex) await market.reserveSlot(request.id, slotIndex)
await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral) await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral)
check receivedSlotIndices == @[slotIndex] check eventually receivedSlotIndices == @[slotIndex]
await subscription.unsubscribe() await subscription.unsubscribe()
test "supports slot freed subscriptions": test "supports slot freed subscriptions":
@ -211,8 +208,7 @@ ethersuite "On-Chain Market":
receivedIdxs.add(idx) receivedIdxs.add(idx)
let subscription = await market.subscribeSlotFreed(onSlotFreed) let subscription = await market.subscribeSlotFreed(onSlotFreed)
await market.freeSlot(slotId(request.id, slotIndex)) await market.freeSlot(slotId(request.id, slotIndex))
check receivedRequestIds == @[request.id] check eventually receivedRequestIds == @[request.id] and receivedIdxs == @[slotIndex]
check receivedIdxs == @[slotIndex]
await subscription.unsubscribe() await subscription.unsubscribe()
test "supports slot reservations full subscriptions": test "supports slot reservations full subscriptions":
@ -235,8 +231,7 @@ ethersuite "On-Chain Market":
switchAccount(account3) switchAccount(account3)
await market.reserveSlot(request.id, slotIndex) await market.reserveSlot(request.id, slotIndex)
check receivedRequestIds == @[request.id] check eventually receivedRequestIds == @[request.id] and receivedIdxs == @[slotIndex]
check receivedIdxs == @[slotIndex]
await subscription.unsubscribe() await subscription.unsubscribe()
test "support fulfillment subscriptions": test "support fulfillment subscriptions":
@ -248,7 +243,7 @@ ethersuite "On-Chain Market":
for slotIndex in 0..<request.ask.slots: for slotIndex in 0..<request.ask.slots:
await market.reserveSlot(request.id, slotIndex.u256) await market.reserveSlot(request.id, slotIndex.u256)
await market.fillSlot(request.id, slotIndex.u256, proof, request.ask.collateral) await market.fillSlot(request.id, slotIndex.u256, proof, request.ask.collateral)
check receivedIds == @[request.id] check eventually receivedIds == @[request.id]
await subscription.unsubscribe() await subscription.unsubscribe()
test "subscribes only to fulfillment of a certain request": test "subscribes only to fulfillment of a certain request":
@ -271,7 +266,7 @@ ethersuite "On-Chain Market":
await market.reserveSlot(otherRequest.id, slotIndex.u256) await market.reserveSlot(otherRequest.id, slotIndex.u256)
await market.fillSlot(otherRequest.id, slotIndex.u256, proof, otherRequest.ask.collateral) await market.fillSlot(otherRequest.id, slotIndex.u256, proof, otherRequest.ask.collateral)
check receivedIds == @[request.id] check eventually receivedIds == @[request.id]
await subscription.unsubscribe() await subscription.unsubscribe()
@ -285,7 +280,7 @@ ethersuite "On-Chain Market":
await advanceToCancelledRequest(request) await advanceToCancelledRequest(request)
await market.withdrawFunds(request.id) await market.withdrawFunds(request.id)
check receivedIds == @[request.id] check eventually receivedIds == @[request.id]
await subscription.unsubscribe() await subscription.unsubscribe()
test "support request failed subscriptions": test "support request failed subscriptions":
@ -308,8 +303,8 @@ ethersuite "On-Chain Market":
await waitUntilProofRequired(slotId) await waitUntilProofRequired(slotId)
let missingPeriod = periodicity.periodOf(await ethProvider.currentTime()) let missingPeriod = periodicity.periodOf(await ethProvider.currentTime())
await advanceToNextPeriod() await advanceToNextPeriod()
discard await marketplace.markProofAsMissing(slotId, missingPeriod) discard await marketplace.markProofAsMissing(slotId, missingPeriod).confirm(1)
check receivedIds == @[request.id] check eventually receivedIds == @[request.id]
await subscription.unsubscribe() await subscription.unsubscribe()
test "subscribes only to a certain request cancellation": test "subscribes only to a certain request cancellation":
@ -325,9 +320,8 @@ ethersuite "On-Chain Market":
let subscription = await market.subscribeRequestCancelled(request.id, onRequestCancelled) let subscription = await market.subscribeRequestCancelled(request.id, onRequestCancelled)
await advanceToCancelledRequest(otherRequest) # shares expiry with otherRequest await advanceToCancelledRequest(otherRequest) # shares expiry with otherRequest
await market.withdrawFunds(otherRequest.id) await market.withdrawFunds(otherRequest.id)
check receivedIds.len == 0
await market.withdrawFunds(request.id) await market.withdrawFunds(request.id)
check receivedIds == @[request.id] check eventually receivedIds == @[request.id]
await subscription.unsubscribe() await subscription.unsubscribe()
test "supports proof submission subscriptions": test "supports proof submission subscriptions":
@ -340,7 +334,7 @@ ethersuite "On-Chain Market":
receivedIds.add(id) receivedIds.add(id)
let subscription = await market.subscribeProofSubmission(onProofSubmission) let subscription = await market.subscribeProofSubmission(onProofSubmission)
await market.submitProof(slotId(request.id, slotIndex), proof) await market.submitProof(slotId(request.id, slotIndex), proof)
check receivedIds == @[slotId(request.id, slotIndex)] check eventually receivedIds == @[slotId(request.id, slotIndex)]
await subscription.unsubscribe() await subscription.unsubscribe()
test "request is none when unknown": test "request is none when unknown":

View File

@ -1,8 +1,11 @@
import pkg/ethers import pkg/ethers
import pkg/serde/json import pkg/serde/json
proc blockTime*(provider: Provider, tag: BlockTag): Future[UInt256] {.async.} =
return (!await provider.getBlock(tag)).timestamp
proc currentTime*(provider: Provider): Future[UInt256] {.async.} = proc currentTime*(provider: Provider): Future[UInt256] {.async.} =
return (!await provider.getBlock(BlockTag.pending)).timestamp return await provider.blockTime(BlockTag.pending)
proc advanceTime*(provider: JsonRpcProvider, seconds: UInt256) {.async.} = proc advanceTime*(provider: JsonRpcProvider, seconds: UInt256) {.async.} =
discard await provider.send("evm_increaseTime", @[%("0x" & seconds.toHex)]) discard await provider.send("evm_increaseTime", @[%("0x" & seconds.toHex)])

View File

@ -1,5 +1,6 @@
import std/json import std/json
import pkg/ethers import pkg/ethers
import pkg/chronos
import ./asynctest import ./asynctest
import ./checktest import ./checktest
@ -16,11 +17,15 @@ template ethersuite*(name, body) =
var snapshot: JsonNode var snapshot: JsonNode
setup: setup:
ethProvider = JsonRpcProvider.new("ws://localhost:8545") ethProvider = JsonRpcProvider.new(
"http://127.0.0.1:8545",
pollingInterval = chronos.milliseconds(100)
)
snapshot = await send(ethProvider, "evm_snapshot") snapshot = await send(ethProvider, "evm_snapshot")
accounts = await ethProvider.listAccounts() accounts = await ethProvider.listAccounts()
teardown: teardown:
await ethProvider.close()
discard await send(ethProvider, "evm_revert", @[snapshot]) discard await send(ethProvider, "evm_revert", @[snapshot])
body body

View File

@ -196,6 +196,7 @@ template multinodesuite*(name: string, body: untyped) =
proc startClientNode(conf: CodexConfig): Future[NodeProcess] {.async.} = proc startClientNode(conf: CodexConfig): Future[NodeProcess] {.async.} =
let clientIdx = clients().len let clientIdx = clients().len
var config = conf var config = conf
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, "--eth-account", $accounts[running.len])
return await newCodexProcess(clientIdx, config, Role.Client) return await newCodexProcess(clientIdx, config, Role.Client)
@ -203,6 +204,7 @@ template multinodesuite*(name: string, body: untyped) =
let providerIdx = providers().len let providerIdx = providers().len
var config = conf var config = conf
config.addCliOption("--bootstrap-node", bootstrap) 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, "--eth-account", $accounts[running.len])
config.addCliOption(PersistenceCmd.prover, "--circom-r1cs", config.addCliOption(PersistenceCmd.prover, "--circom-r1cs",
"vendor/codex-contracts-eth/verifier/networks/hardhat/proof_main.r1cs") "vendor/codex-contracts-eth/verifier/networks/hardhat/proof_main.r1cs")
@ -217,6 +219,7 @@ template multinodesuite*(name: string, body: untyped) =
let validatorIdx = validators().len let validatorIdx = validators().len
var config = conf var config = conf
config.addCliOption("--bootstrap-node", bootstrap) 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, "--eth-account", $accounts[running.len])
config.addCliOption(StartUpCmd.persistence, "--validator") config.addCliOption(StartUpCmd.persistence, "--validator")
@ -264,7 +267,10 @@ template multinodesuite*(name: string, body: untyped) =
# Workaround for https://github.com/NomicFoundation/hardhat/issues/2053 # Workaround for https://github.com/NomicFoundation/hardhat/issues/2053
# Do not use websockets, but use http and polling to stop subscriptions # Do not use websockets, but use http and polling to stop subscriptions
# from being removed after 5 minutes # from being removed after 5 minutes
ethProvider = JsonRpcProvider.new("http://localhost:8545") ethProvider = JsonRpcProvider.new(
"http://127.0.0.1:8545",
pollingInterval = chronos.milliseconds(100)
)
# if hardhat was NOT started by the test, take a snapshot so it can be # if hardhat was NOT started by the test, take a snapshot so it can be
# reverted in the test teardown # reverted in the test teardown
if nodeConfigs.hardhat.isNone: if nodeConfigs.hardhat.isNone:

View File

@ -23,9 +23,9 @@ marketplacesuite "Bug #821 - node crashes during erasure coding":
.some, .some,
): ):
let reward = 400.u256 let reward = 400.u256
let duration = 10.periods let duration = 20.periods
let collateral = 200.u256 let collateral = 200.u256
let expiry = 5.periods let expiry = 10.periods
let data = await RandomChunker.example(blocks=8) let data = await RandomChunker.example(blocks=8)
let client = clients()[0] let client = clients()[0]
let clientApi = client.client let clientApi = client.client

View File

@ -24,15 +24,15 @@ twonodessuite "Marketplace", debug1 = false, debug2 = false:
let cid = client1.upload(data).get let cid = client1.upload(data).get
let id = client1.requestStorage( let id = client1.requestStorage(
cid, cid,
duration=10*60.u256, duration=20*60.u256,
reward=400.u256, reward=400.u256,
proofProbability=3.u256, proofProbability=3.u256,
expiry=5*60, expiry=10*60,
collateral=200.u256, collateral=200.u256,
nodes = 5, nodes = 3,
tolerance = 2).get tolerance = 1).get
check eventually(client1.purchaseStateIs(id, "started"), timeout=5*60*1000) check eventually(client1.purchaseStateIs(id, "started"), timeout=10*60*1000)
let purchase = client1.getPurchase(id).get let purchase = client1.getPurchase(id).get
check purchase.error == none string check purchase.error == none string
let availabilities = client2.getAvailabilities().get let availabilities = client2.getAvailabilities().get
@ -41,7 +41,7 @@ twonodessuite "Marketplace", debug1 = false, debug2 = false:
check newSize > 0 and newSize < size check newSize > 0 and newSize < size
let reservations = client2.getAvailabilityReservations(availability.id).get let reservations = client2.getAvailabilityReservations(availability.id).get
check reservations.len == 5 check reservations.len == 3
check reservations[0].requestId == purchase.requestId 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":
@ -51,8 +51,8 @@ twonodessuite "Marketplace", debug1 = false, debug2 = false:
let tokenAddress = await marketplace.token() let tokenAddress = await marketplace.token()
let token = Erc20Token.new(tokenAddress, ethProvider.getSigner()) let token = Erc20Token.new(tokenAddress, ethProvider.getSigner())
let reward = 400.u256 let reward = 400.u256
let duration = 10*60.u256 let duration = 20*60.u256
let nodes = 5'u let nodes = 3'u
# client 2 makes storage available # client 2 makes storage available
let startBalanceHost = await token.balanceOf(account2) let startBalanceHost = await token.balanceOf(account2)
@ -65,12 +65,12 @@ twonodessuite "Marketplace", debug1 = false, debug2 = false:
duration=duration, duration=duration,
reward=reward, reward=reward,
proofProbability=3.u256, proofProbability=3.u256,
expiry=5*60, expiry=10*60,
collateral=200.u256, collateral=200.u256,
nodes = nodes, nodes = nodes,
tolerance = 2).get tolerance = 1).get
check eventually(client1.purchaseStateIs(id, "started"), timeout=5*60*1000) check eventually(client1.purchaseStateIs(id, "started"), timeout=10*60*1000)
let purchase = client1.getPurchase(id).get let purchase = client1.getPurchase(id).get
check purchase.error == none string check purchase.error == none string
@ -85,7 +85,10 @@ twonodessuite "Marketplace", debug1 = false, debug2 = false:
check eventually (await token.balanceOf(account2)) - startBalanceHost >= (duration-5*60)*reward*nodes.u256 check eventually (await token.balanceOf(account2)) - startBalanceHost >= (duration-5*60)*reward*nodes.u256
# Checking that client node receives some funds back that were not used for the host nodes # Checking that client node receives some funds back that were not used for the host nodes
check eventually (await token.balanceOf(account1)) - clientBalanceBeforeFinished > 0 check eventually(
(await token.balanceOf(account1)) - clientBalanceBeforeFinished > 0,
timeout = 10*1000 # give client a bit of time to withdraw its funds
)
marketplacesuite "Marketplace payouts": marketplacesuite "Marketplace payouts":
@ -109,9 +112,9 @@ marketplacesuite "Marketplace payouts":
.some, .some,
): ):
let reward = 400.u256 let reward = 400.u256
let duration = 10.periods let duration = 20.periods
let collateral = 200.u256 let collateral = 200.u256
let expiry = 5.periods let expiry = 10.periods
let data = await RandomChunker.example(blocks=8) let data = await RandomChunker.example(blocks=8)
let client = clients()[0] let client = clients()[0]
let provider = providers()[0] let provider = providers()[0]
@ -150,12 +153,11 @@ marketplacesuite "Marketplace payouts":
# wait until one slot is filled # wait until one slot is filled
check eventually(slotIdxFilled.isSome, timeout=expiry.int * 1000) check eventually(slotIdxFilled.isSome, timeout=expiry.int * 1000)
let slotId = slotId(!clientApi.requestId(id), !slotIdxFilled)
# wait until sale is cancelled # wait until sale is cancelled
without requestId =? clientApi.requestId(id): await ethProvider.advanceTime(expiry.u256)
fail() check eventually providerApi.saleStateIs(slotId, "SaleCancelled")
let slotId = slotId(requestId, !slotIdxFilled)
check eventually(providerApi.saleStateIs(slotId, "SaleCancelled"), timeout=expiry.int * 1000)
check eventually ( check eventually (
let endBalanceProvider = (await token.balanceOf(provider.ethAccount)); let endBalanceProvider = (await token.balanceOf(provider.ethAccount));
@ -163,9 +165,12 @@ marketplacesuite "Marketplace payouts":
endBalanceProvider < startBalanceProvider + expiry.u256*reward endBalanceProvider < startBalanceProvider + expiry.u256*reward
) )
check eventually( check eventually(
(
let endBalanceClient = (await token.balanceOf(client.ethAccount)); let endBalanceClient = (await token.balanceOf(client.ethAccount));
let endBalanceProvider = (await token.balanceOf(provider.ethAccount)); let endBalanceProvider = (await token.balanceOf(provider.ethAccount));
(startBalanceClient - endBalanceClient) == (endBalanceProvider - startBalanceProvider) (startBalanceClient - endBalanceClient) == (endBalanceProvider - startBalanceProvider)
),
timeout = 10*1000 # give client a bit of time to withdraw its funds
) )
await subscription.unsubscribe() await subscription.unsubscribe()

View File

@ -36,7 +36,7 @@ marketplacesuite "Hosts submit regular proofs":
.some, .some,
): ):
let client0 = clients()[0].client let client0 = clients()[0].client
let expiry = 5.periods let expiry = 10.periods
let duration = expiry + 5.periods let duration = expiry + 5.periods
let data = await RandomChunker.example(blocks=8) let data = await RandomChunker.example(blocks=8)
@ -99,7 +99,7 @@ marketplacesuite "Simulate invalid proofs":
.some .some
): ):
let client0 = clients()[0].client let client0 = clients()[0].client
let expiry = 5.periods let expiry = 10.periods
let duration = expiry + 10.periods let duration = expiry + 10.periods
let data = await RandomChunker.example(blocks=8) let data = await RandomChunker.example(blocks=8)
@ -157,9 +157,8 @@ marketplacesuite "Simulate invalid proofs":
.some .some
): ):
let client0 = clients()[0].client let client0 = clients()[0].client
let expiry = 5.periods let expiry = 10.periods
# In 2 periods you cannot have enough invalid proofs submitted: let duration = expiry + 10.periods
let duration = expiry + 2.periods
let data = await RandomChunker.example(blocks=8) let data = await RandomChunker.example(blocks=8)
createAvailabilities(data.len * 2, duration) # TODO: better value for data.len createAvailabilities(data.len * 2, duration) # TODO: better value for data.len
@ -176,20 +175,27 @@ marketplacesuite "Simulate invalid proofs":
) )
let requestId = client0.requestId(purchaseId).get let requestId = client0.requestId(purchaseId).get
check eventually(client0.purchaseStateIs(purchaseId, "started"), timeout = expiry.int * 1000) var slotWasFilled = false
proc onSlotFilled(event: SlotFilled) =
if event.requestId == requestId:
slotWasFilled = true
let filledSubscription = await marketplace.subscribe(SlotFilled, onSlotFilled)
# wait for the first slot to be filled
check eventually(slotWasFilled, timeout = expiry.int * 1000)
var slotWasFreed = false var slotWasFreed = false
proc onSlotFreed(event: SlotFreed) = proc onSlotFreed(event: SlotFreed) =
if event.requestId == requestId: if event.requestId == requestId:
slotWasFreed = true slotWasFreed = true
let freedSubscription = await marketplace.subscribe(SlotFreed, onSlotFreed)
let subscription = await marketplace.subscribe(SlotFreed, onSlotFreed) # In 2 periods you cannot have enough invalid proofs submitted:
await sleepAsync(2.periods.int.seconds)
# check not freed
await sleepAsync((duration - expiry).int.seconds)
check not slotWasFreed check not slotWasFreed
await subscription.unsubscribe() await filledSubscription.unsubscribe()
await freedSubscription.unsubscribe()
# TODO: uncomment once fixed # TODO: uncomment once fixed
# test "host that submits invalid proofs is paid out less", NodeConfigs( # test "host that submits invalid proofs is paid out less", NodeConfigs(

View File

@ -56,24 +56,24 @@ twonodessuite "Purchasing", debug1 = false, debug2 = false:
let data = await RandomChunker.example(blocks=2) let data = await RandomChunker.example(blocks=2)
let cid = client1.upload(data).get let cid = client1.upload(data).get
let id = client1.requestStorage(cid, let id = client1.requestStorage(cid,
duration=100.u256, duration=10*60.u256,
reward=2.u256, reward=2.u256,
proofProbability=3.u256, proofProbability=3.u256,
expiry=30, expiry=5*60,
collateral=200.u256, collateral=200.u256,
nodes=3.uint, nodes=3.uint,
tolerance=1.uint).get tolerance=1.uint).get
check eventually client1.purchaseStateIs(id, "submitted") check eventually(client1.purchaseStateIs(id, "submitted"), timeout = 3*60*1000)
node1.restart() node1.restart()
client1.restart() client1.restart()
check eventually client1.purchaseStateIs(id, "submitted") check eventually(client1.purchaseStateIs(id, "submitted"), timeout = 3*60*1000)
let request = client1.getPurchase(id).get.request.get let request = client1.getPurchase(id).get.request.get
check request.ask.duration == 100.u256 check request.ask.duration == (10*60).u256
check request.ask.reward == 2.u256 check request.ask.reward == 2.u256
check request.ask.proofProbability == 3.u256 check request.ask.proofProbability == 3.u256
check request.expiry == 30 check request.expiry == (5*60).u256
check request.ask.collateral == 200.u256 check request.ask.collateral == 200.u256
check request.ask.slots == 3'u64 check request.ask.slots == 3'u64
check request.ask.maxSlotLoss == 1'u64 check request.ask.maxSlotLoss == 1'u64

View File

@ -60,15 +60,15 @@ twonodessuite "Sales", debug1 = false, debug2 = false:
let cid = client2.upload(data).get let cid = client2.upload(data).get
let id = client2.requestStorage( let id = client2.requestStorage(
cid, cid,
duration=10*60.u256, duration=20*60.u256,
reward=400.u256, reward=400.u256,
proofProbability=3.u256, proofProbability=3.u256,
expiry=5*60, expiry=10*60,
collateral=200.u256, collateral=200.u256,
nodes = 5, nodes = 3,
tolerance = 2).get tolerance = 1).get
check eventually(client2.purchaseStateIs(id, "started"), timeout=5*60*1000) check eventually(client2.purchaseStateIs(id, "started"), timeout=10*60*1000)
let updatedAvailability = (client1.getAvailabilities().get).findItem(availability).get let updatedAvailability = (client1.getAvailabilities().get).findItem(availability).get
check updatedAvailability.totalSize != updatedAvailability.freeSize check updatedAvailability.totalSize != updatedAvailability.freeSize

View File

@ -43,6 +43,7 @@ template twonodessuite*(name: string, debug1, debug2: string, body) =
"--circom-r1cs=tests/circuits/fixtures/proof_main.r1cs", "--circom-r1cs=tests/circuits/fixtures/proof_main.r1cs",
"--circom-wasm=tests/circuits/fixtures/proof_main.wasm", "--circom-wasm=tests/circuits/fixtures/proof_main.wasm",
"--circom-zkey=tests/circuits/fixtures/proof_main.zkey", "--circom-zkey=tests/circuits/fixtures/proof_main.zkey",
"--eth-provider=http://127.0.0.1:8545",
"--eth-account=" & $account1 "--eth-account=" & $account1
] ]
@ -67,6 +68,7 @@ template twonodessuite*(name: string, debug1, debug2: string, body) =
"--circom-r1cs=tests/circuits/fixtures/proof_main.r1cs", "--circom-r1cs=tests/circuits/fixtures/proof_main.r1cs",
"--circom-wasm=tests/circuits/fixtures/proof_main.wasm", "--circom-wasm=tests/circuits/fixtures/proof_main.wasm",
"--circom-zkey=tests/circuits/fixtures/proof_main.zkey", "--circom-zkey=tests/circuits/fixtures/proof_main.zkey",
"--eth-provider=http://127.0.0.1:8545",
"--eth-account=" & $account2 "--eth-account=" & $account2
] ]

View File

@ -14,7 +14,7 @@ suite "tools/cirdl":
test "circuit download tool": test "circuit download tool":
let let
circuitPath = "testcircuitpath" circuitPath = "testcircuitpath"
rpcEndpoint = "ws://localhost:8545" rpcEndpoint = "http://127.0.0.1:8545"
marketplaceAddress = Marketplace.address marketplaceAddress = Marketplace.address
discard existsOrCreateDir(circuitPath) discard existsOrCreateDir(circuitPath)

@ -1 +1 @@
Subproject commit 11ccefd720f1932608b67db95af5b72d73d1257b Subproject commit 945f6008c8817abd7ca43a40368d33bb1e014c14

2
vendor/nim-ethers vendored

@ -1 +1 @@
Subproject commit 0ce6abf0fe942fe7cb1d14b8e4485621be9c3fe8 Subproject commit 1ae2cd4a35aa7c7ca21ca750fb7f951b3a6a97c0