Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix concurrency issues #993

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:

name: '${{ matrix.os }}-${{ matrix.cpu }}-${{ matrix.nim_version }}-${{ matrix.tests }}'
runs-on: ${{ matrix.builder }}
timeout-minutes: 100
timeout-minutes: 120
steps:
- name: Checkout sources
uses: actions/checkout@v4
Expand Down
16 changes: 8 additions & 8 deletions codex/contracts/market.nim
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
convertEthersError:
let tokenAddress = await market.contract.token()
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)

Check warning on line 56 in codex/contracts/market.nim

View check run for this annotation

Codecov / codecov/patch

codex/contracts/market.nim#L56

Added line #L56 was not covered by tests

method getZkeyHash*(market: OnChainMarket): Future[?string] {.async.} =
let config = await market.contract.configuration()
Expand Down Expand Up @@ -99,7 +99,7 @@
convertEthersError:
debug "Requesting storage"
await market.approveFunds(request.price())
discard await market.contract.requestStorage(request).confirm(0)
discard await market.contract.requestStorage(request).confirm(1)

Check warning on line 102 in codex/contracts/market.nim

View check run for this annotation

Codecov / codecov/patch

codex/contracts/market.nim#L102

Added line #L102 was not covered by tests

method getRequest(market: OnChainMarket,
id: RequestId): Future[?StorageRequest] {.async.} =
Expand Down Expand Up @@ -171,7 +171,7 @@

await market.approveFunds(collateral)
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)

Check warning on line 174 in codex/contracts/market.nim

View check run for this annotation

Codecov / codecov/patch

codex/contracts/market.nim#L174

Added line #L174 was not covered by tests
trace "fillSlot transaction completed"

method freeSlot*(market: OnChainMarket, slotId: SlotId) {.async.} =
Expand All @@ -191,13 +191,13 @@
# recipient (the contract will use msg.sender for both)
freeSlot = market.contract.freeSlot(slotId)

discard await freeSlot.confirm(0)
discard await freeSlot.confirm(1)

Check warning on line 194 in codex/contracts/market.nim

View check run for this annotation

Codecov / codecov/patch

codex/contracts/market.nim#L194

Added line #L194 was not covered by tests


method withdrawFunds(market: OnChainMarket,
requestId: RequestId) {.async.} =
convertEthersError:
discard await market.contract.withdrawFunds(requestId).confirm(0)
discard await market.contract.withdrawFunds(requestId).confirm(1)

Check warning on line 200 in codex/contracts/market.nim

View check run for this annotation

Codecov / codecov/patch

codex/contracts/market.nim#L200

Added line #L200 was not covered by tests

method isProofRequired*(market: OnChainMarket,
id: SlotId): Future[bool] {.async.} =
Expand Down Expand Up @@ -230,13 +230,13 @@
id: SlotId,
proof: Groth16Proof) {.async.} =
convertEthersError:
discard await market.contract.submitProof(id, proof).confirm(0)
discard await market.contract.submitProof(id, proof).confirm(1)

Check warning on line 233 in codex/contracts/market.nim

View check run for this annotation

Codecov / codecov/patch

codex/contracts/market.nim#L233

Added line #L233 was not covered by tests

method markProofAsMissing*(market: OnChainMarket,
id: SlotId,
period: Period) {.async.} =
convertEthersError:
discard await market.contract.markProofAsMissing(id, period).confirm(0)
discard await market.contract.markProofAsMissing(id, period).confirm(1)

Check warning on line 239 in codex/contracts/market.nim

View check run for this annotation

Codecov / codecov/patch

codex/contracts/market.nim#L239

Added line #L239 was not covered by tests

method canProofBeMarkedAsMissing*(
market: OnChainMarket,
Expand Down Expand Up @@ -264,7 +264,7 @@
slotIndex,
# reserveSlot runs out of gas for unknown reason, but 100k gas covers it
TransactionOverrides(gasLimit: some 100000.u256)
).confirm(0)
).confirm(1)

Check warning on line 267 in codex/contracts/market.nim

View check run for this annotation

Codecov / codecov/patch

codex/contracts/market.nim#L267

Added line #L267 was not covered by tests

method canReserveSlot*(
market: OnChainMarket,
Expand Down
10 changes: 5 additions & 5 deletions tests/codex/testvalidation.nim
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,15 @@ asyncchecksuite "validation":
check validationConfig.error.msg == "The value of the group index " &
"must be less than validation groups! " &
fmt"(got: {groupIndex = }, groups = {!groups})"

test "initializing ValidationConfig fails when maxSlots is negative":
let maxSlots = -1
let validationConfig = ValidationConfig.init(
maxSlots = maxSlots, groups = ValidationGroups.none)
check validationConfig.isFailure == true
check validationConfig.error.msg == "The value of maxSlots must " &
fmt"be greater than or equal to 0! (got: {maxSlots})"

test "initializing ValidationConfig fails when maxSlots is negative " &
"(validationGroups set)":
let maxSlots = -1
Expand All @@ -97,7 +97,7 @@ asyncchecksuite "validation":
test "when a slot is filled on chain, it is added to the list":
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
check validation.slots == @[slot.id]

test "slot should be observed if maxSlots is set to 0":
let validationConfig = initValidationConfig(
maxSlots = 0, ValidationGroups.none)
Expand Down Expand Up @@ -128,14 +128,14 @@ asyncchecksuite "validation":
await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
market.setCanProofBeMarkedAsMissing(slot.id, true)
advanceToNextPeriod()
await sleepAsync(1.millis)
await sleepAsync(100.millis) # allow validation loop to run
check market.markedAsMissingProofs.contains(slot.id)

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)
market.setCanProofBeMarkedAsMissing(slot.id, false)
advanceToNextPeriod()
await sleepAsync(1.millis)
await sleepAsync(100.millis) # allow validation loop to run
check market.markedAsMissingProofs.len == 0

test "it does not monitor more than the maximum number of slots":
Expand Down
2 changes: 1 addition & 1 deletion tests/contracts/testClock.nim
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ ethersuite "On-Chain Clock":
let waiting = clock.waitUntil(future)
discard await ethProvider.send("evm_setNextBlockTimestamp", @[%future])
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":
let future = clock.now() + 1 # seconds
Expand Down
27 changes: 14 additions & 13 deletions tests/contracts/testContracts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ ethersuite "Marketplace contracts":
request.client = await client.getAddress()

switchAccount(client)
discard await token.approve(marketplace.address, request.price)
discard await marketplace.requestStorage(request)
discard await token.approve(marketplace.address, request.price).confirm(1)
discard await marketplace.requestStorage(request).confirm(1)
switchAccount(host)
discard await token.approve(marketplace.address, request.ask.collateral)
discard await marketplace.reserveSlot(request.id, 0.u256)
filledAt = await ethProvider.currentTime()
discard await marketplace.fillSlot(request.id, 0.u256, proof)
discard await token.approve(marketplace.address, request.ask.collateral).confirm(1)
discard await marketplace.reserveSlot(request.id, 0.u256).confirm(1)
let receipt = await marketplace.fillSlot(request.id, 0.u256, proof).confirm(1)
filledAt = await ethProvider.blockTime(BlockTag.init(!receipt.blockNumber))
slotId = request.slotId(0.u256)

proc waitUntilProofRequired(slotId: SlotId) {.async.} =
Expand All @@ -65,14 +65,14 @@ ethersuite "Marketplace contracts":

proc startContract() {.async.} =
for slotIndex in 1..<request.ask.slots:
discard await token.approve(marketplace.address, request.ask.collateral)
discard await marketplace.reserveSlot(request.id, slotIndex.u256)
discard await marketplace.fillSlot(request.id, slotIndex.u256, proof)
discard await token.approve(marketplace.address, request.ask.collateral).confirm(1)
discard await marketplace.reserveSlot(request.id, slotIndex.u256).confirm(1)
discard await marketplace.fillSlot(request.id, slotIndex.u256, proof).confirm(1)

test "accept marketplace proofs":
switchAccount(host)
await waitUntilProofRequired(slotId)
discard await marketplace.submitProof(slotId, proof)
discard await marketplace.submitProof(slotId, proof).confirm(1)

test "can mark missing proofs":
switchAccount(host)
Expand All @@ -81,7 +81,7 @@ ethersuite "Marketplace contracts":
let endOfPeriod = periodicity.periodEnd(missingPeriod)
await ethProvider.advanceTimeTo(endOfPeriod + 1)
switchAccount(client)
discard await marketplace.markProofAsMissing(slotId, missingPeriod)
discard await marketplace.markProofAsMissing(slotId, missingPeriod).confirm(1)

test "can be paid out at the end":
switchAccount(host)
Expand All @@ -90,7 +90,7 @@ ethersuite "Marketplace contracts":
let requestEnd = await marketplace.requestEnd(request.id)
await ethProvider.advanceTimeTo(requestEnd.u256 + 1)
let startBalance = await token.balanceOf(address)
discard await marketplace.freeSlot(slotId)
discard await marketplace.freeSlot(slotId).confirm(1)
let endBalance = await token.balanceOf(address)
check endBalance == (startBalance + expectedPayout(requestEnd.u256) + request.ask.collateral)

Expand All @@ -103,7 +103,7 @@ ethersuite "Marketplace contracts":
let startBalanceHost = await token.balanceOf(hostAddress)
let startBalanceReward = await token.balanceOf(rewardRecipient)
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 endBalanceReward = await token.balanceOf(rewardRecipient)
let endBalanceCollateral = await token.balanceOf(collateralRecipient)
Expand All @@ -120,4 +120,5 @@ ethersuite "Marketplace contracts":
await ethProvider.advanceTime(periodicity.seconds)
check await marketplace
.markProofAsMissing(slotId, missingPeriod)
.confirm(1)
.reverts("Slot not accepting proofs")
30 changes: 12 additions & 18 deletions tests/contracts/testMarket.nim
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,7 @@ ethersuite "On-Chain Market":
receivedAsks.add(ask)
let subscription = await market.subscribeRequests(onRequest)
await market.requestStorage(request)
check receivedIds == @[request.id]
check receivedAsks == @[request.ask]
check eventually receivedIds == @[request.id] and receivedAsks == @[request.ask]
await subscription.unsubscribe()

test "supports filling of slots":
Expand Down Expand Up @@ -181,8 +180,7 @@ ethersuite "On-Chain Market":
let subscription = await market.subscribeSlotFilled(onSlotFilled)
await market.reserveSlot(request.id, slotIndex)
await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral)
check receivedIds == @[request.id]
check receivedSlotIndices == @[slotIndex]
check eventually receivedIds == @[request.id] and receivedSlotIndices == @[slotIndex]
await subscription.unsubscribe()

test "subscribes only to a certain slot":
Expand All @@ -194,10 +192,9 @@ ethersuite "On-Chain Market":
let subscription = await market.subscribeSlotFilled(request.id, slotIndex, onSlotFilled)
await market.reserveSlot(request.id, otherSlot)
await market.fillSlot(request.id, otherSlot, proof, request.ask.collateral)
check receivedSlotIndices.len == 0
await market.reserveSlot(request.id, slotIndex)
await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral)
check receivedSlotIndices == @[slotIndex]
check eventually receivedSlotIndices == @[slotIndex]
await subscription.unsubscribe()

test "supports slot freed subscriptions":
Expand All @@ -211,8 +208,7 @@ ethersuite "On-Chain Market":
receivedIdxs.add(idx)
let subscription = await market.subscribeSlotFreed(onSlotFreed)
await market.freeSlot(slotId(request.id, slotIndex))
check receivedRequestIds == @[request.id]
check receivedIdxs == @[slotIndex]
check eventually receivedRequestIds == @[request.id] and receivedIdxs == @[slotIndex]
await subscription.unsubscribe()

test "supports slot reservations full subscriptions":
Expand All @@ -235,8 +231,7 @@ ethersuite "On-Chain Market":
switchAccount(account3)
await market.reserveSlot(request.id, slotIndex)

check receivedRequestIds == @[request.id]
check receivedIdxs == @[slotIndex]
check eventually receivedRequestIds == @[request.id] and receivedIdxs == @[slotIndex]
await subscription.unsubscribe()

test "support fulfillment subscriptions":
Expand All @@ -248,7 +243,7 @@ ethersuite "On-Chain Market":
for slotIndex in 0..<request.ask.slots:
await market.reserveSlot(request.id, slotIndex.u256)
await market.fillSlot(request.id, slotIndex.u256, proof, request.ask.collateral)
check receivedIds == @[request.id]
check eventually receivedIds == @[request.id]
await subscription.unsubscribe()

test "subscribes only to fulfillment of a certain request":
Expand All @@ -271,7 +266,7 @@ ethersuite "On-Chain Market":
await market.reserveSlot(otherRequest.id, slotIndex.u256)
await market.fillSlot(otherRequest.id, slotIndex.u256, proof, otherRequest.ask.collateral)

check receivedIds == @[request.id]
check eventually receivedIds == @[request.id]

await subscription.unsubscribe()

Expand All @@ -285,7 +280,7 @@ ethersuite "On-Chain Market":

await advanceToCancelledRequest(request)
await market.withdrawFunds(request.id)
check receivedIds == @[request.id]
check eventually receivedIds == @[request.id]
await subscription.unsubscribe()

test "support request failed subscriptions":
Expand All @@ -308,8 +303,8 @@ ethersuite "On-Chain Market":
await waitUntilProofRequired(slotId)
let missingPeriod = periodicity.periodOf(await ethProvider.currentTime())
await advanceToNextPeriod()
discard await marketplace.markProofAsMissing(slotId, missingPeriod)
check receivedIds == @[request.id]
discard await marketplace.markProofAsMissing(slotId, missingPeriod).confirm(1)
check eventually receivedIds == @[request.id]
await subscription.unsubscribe()

test "subscribes only to a certain request cancellation":
Expand All @@ -325,9 +320,8 @@ ethersuite "On-Chain Market":
let subscription = await market.subscribeRequestCancelled(request.id, onRequestCancelled)
await advanceToCancelledRequest(otherRequest) # shares expiry with otherRequest
await market.withdrawFunds(otherRequest.id)
check receivedIds.len == 0
await market.withdrawFunds(request.id)
check receivedIds == @[request.id]
check eventually receivedIds == @[request.id]
await subscription.unsubscribe()

test "supports proof submission subscriptions":
Expand All @@ -340,7 +334,7 @@ ethersuite "On-Chain Market":
receivedIds.add(id)
let subscription = await market.subscribeProofSubmission(onProofSubmission)
await market.submitProof(slotId(request.id, slotIndex), proof)
check receivedIds == @[slotId(request.id, slotIndex)]
check eventually receivedIds == @[slotId(request.id, slotIndex)]
await subscription.unsubscribe()

test "request is none when unknown":
Expand Down
5 changes: 4 additions & 1 deletion tests/contracts/time.nim
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import pkg/ethers
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.} =
return (!await provider.getBlock(BlockTag.pending)).timestamp
return await provider.blockTime(BlockTag.pending)

proc advanceTime*(provider: JsonRpcProvider, seconds: UInt256) {.async.} =
discard await provider.send("evm_increaseTime", @[%("0x" & seconds.toHex)])
Expand Down
7 changes: 6 additions & 1 deletion tests/ethertest.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import std/json
import pkg/ethers
import pkg/chronos

import ./asynctest
import ./checktest
Expand All @@ -16,11 +17,15 @@ template ethersuite*(name, body) =
var snapshot: JsonNode

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")
accounts = await ethProvider.listAccounts()

teardown:
await ethProvider.close()
discard await send(ethProvider, "evm_revert", @[snapshot])

body
Expand Down
8 changes: 7 additions & 1 deletion tests/integration/multinodes.nim
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,15 @@ template multinodesuite*(name: string, body: untyped) =
proc startClientNode(conf: CodexConfig): Future[NodeProcess] {.async.} =
let clientIdx = clients().len
var config = conf
config.addCliOption(StartUpCmd.persistence, "--eth-provider", "http://127.0.0.1:8545")
config.addCliOption(StartUpCmd.persistence, "--eth-account", $accounts[running.len])
return await newCodexProcess(clientIdx, config, Role.Client)

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",
"vendor/codex-contracts-eth/verifier/networks/hardhat/proof_main.r1cs")
Expand All @@ -217,6 +219,7 @@ template multinodesuite*(name: string, body: untyped) =
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")

Expand Down Expand Up @@ -264,7 +267,10 @@ template multinodesuite*(name: string, body: untyped) =
# Workaround for https://github.com/NomicFoundation/hardhat/issues/2053
# Do not use websockets, but use http and polling to stop subscriptions
# 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
# reverted in the test teardown
if nodeConfigs.hardhat.isNone:
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/testecbug.nim
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ marketplacesuite "Bug #821 - node crashes during erasure coding":
.some,
):
let reward = 400.u256
let duration = 10.periods
let duration = 20.periods
let collateral = 200.u256
let expiry = 5.periods
let expiry = 10.periods
let data = await RandomChunker.example(blocks=8)
let client = clients()[0]
let clientApi = client.client
Expand Down
Loading