Fix on-chain clock (#732)
* market: use `pending` blocktag when querying onchain state * clock: use wall clock in integration tests reason: we'll need to wait for the next period in integration tests, and we can't do that if the time doesn't advance * clock: remove unused field * integration: use pending block time to get current time * clock: fix on-chain clock for hardhat Only use 'latest' block for updates Only update the first time you see a block * integration: do not start tests with a very outdated block * integration: allow for longer expiry period
This commit is contained in:
parent
1fc0510f2b
commit
d1658d7b77
|
@ -15,26 +15,39 @@ type
|
|||
provider: Provider
|
||||
subscription: Subscription
|
||||
offset: times.Duration
|
||||
blockNumber: UInt256
|
||||
started: bool
|
||||
newBlock: AsyncEvent
|
||||
lastBlockTime: UInt256
|
||||
|
||||
proc new*(_: type OnChainClock, provider: Provider): OnChainClock =
|
||||
OnChainClock(provider: provider, newBlock: newAsyncEvent())
|
||||
|
||||
proc update(clock: OnChainClock, blck: Block) =
|
||||
if number =? blck.number and number > clock.blockNumber:
|
||||
let blockTime = initTime(blck.timestamp.truncate(int64), 0)
|
||||
let computerTime = getTime()
|
||||
clock.offset = blockTime - computerTime
|
||||
clock.blockNumber = number
|
||||
trace "updated clock", blockTime=blck.timestamp, blockNumber=number, offset=clock.offset
|
||||
clock.newBlock.fire()
|
||||
|
||||
proc update(clock: OnChainClock) {.async.} =
|
||||
try:
|
||||
if latest =? (await clock.provider.getBlock(BlockTag.latest)):
|
||||
clock.update(latest)
|
||||
except CatchableError as error:
|
||||
debug "error updating clock: ", error=error.msg
|
||||
discard
|
||||
|
||||
method start*(clock: OnChainClock) {.async.} =
|
||||
if clock.started:
|
||||
return
|
||||
|
||||
proc onBlock(blck: Block) =
|
||||
let blockTime = initTime(blck.timestamp.truncate(int64), 0)
|
||||
let computerTime = getTime()
|
||||
clock.offset = blockTime - computerTime
|
||||
clock.lastBlockTime = blck.timestamp
|
||||
clock.newBlock.fire()
|
||||
proc onBlock(_: Block) =
|
||||
# ignore block parameter; hardhat may call this with pending blocks
|
||||
asyncSpawn clock.update()
|
||||
|
||||
if latestBlock =? (await clock.provider.getBlock(BlockTag.latest)):
|
||||
onBlock(latestBlock)
|
||||
await clock.update()
|
||||
|
||||
clock.subscription = await clock.provider.subscribe(onBlock)
|
||||
clock.started = true
|
||||
|
@ -47,22 +60,8 @@ method stop*(clock: OnChainClock) {.async.} =
|
|||
clock.started = false
|
||||
|
||||
method now*(clock: OnChainClock): SecondsSince1970 =
|
||||
when codex_use_hardhat:
|
||||
# hardhat's latest block.timestamp is usually 1s behind the block timestamp
|
||||
# in the newHeads event. When testing, always return the latest block.
|
||||
try:
|
||||
if queriedBlock =? (waitFor clock.provider.getBlock(BlockTag.latest)):
|
||||
trace "using last block timestamp for clock.now",
|
||||
lastBlockTimestamp = queriedBlock.timestamp.truncate(int64),
|
||||
cachedBlockTimestamp = clock.lastBlockTime.truncate(int64)
|
||||
return queriedBlock.timestamp.truncate(int64)
|
||||
except CatchableError as e:
|
||||
warn "failed to get latest block timestamp", error = e.msg
|
||||
return clock.lastBlockTime.truncate(int64)
|
||||
|
||||
else:
|
||||
doAssert clock.started, "clock should be started before calling now()"
|
||||
return toUnix(getTime() + clock.offset)
|
||||
doAssert clock.started, "clock should be started before calling now()"
|
||||
return toUnix(getTime() + clock.offset)
|
||||
|
||||
method waitUntil*(clock: OnChainClock, time: SecondsSince1970) {.async.} =
|
||||
while (let difference = time - clock.now(); difference > 0):
|
||||
|
|
|
@ -60,7 +60,8 @@ method proofDowntime*(market: OnChainMarket): Future[uint8] {.async.} =
|
|||
return config.proofs.downtime
|
||||
|
||||
method getPointer*(market: OnChainMarket, slotId: SlotId): Future[uint8] {.async.} =
|
||||
return await market.contract.getPointer(slotId)
|
||||
let overrides = CallOverrides(blockTag: some BlockTag.pending)
|
||||
return await market.contract.getPointer(slotId, overrides)
|
||||
|
||||
method myRequests*(market: OnChainMarket): Future[seq[RequestId]] {.async.} =
|
||||
return await market.contract.myRequests
|
||||
|
@ -88,7 +89,8 @@ method getRequest(market: OnChainMarket,
|
|||
method requestState*(market: OnChainMarket,
|
||||
requestId: RequestId): Future[?RequestState] {.async.} =
|
||||
try:
|
||||
return some await market.contract.requestState(requestId)
|
||||
let overrides = CallOverrides(blockTag: some BlockTag.pending)
|
||||
return some await market.contract.requestState(requestId, overrides)
|
||||
except ProviderError as e:
|
||||
if e.revertReason.contains("Unknown request"):
|
||||
return none RequestState
|
||||
|
@ -96,7 +98,8 @@ method requestState*(market: OnChainMarket,
|
|||
|
||||
method slotState*(market: OnChainMarket,
|
||||
slotId: SlotId): Future[SlotState] {.async.} =
|
||||
return await market.contract.slotState(slotId)
|
||||
let overrides = CallOverrides(blockTag: some BlockTag.pending)
|
||||
return await market.contract.slotState(slotId, overrides)
|
||||
|
||||
method getRequestEnd*(market: OnChainMarket,
|
||||
id: RequestId): Future[SecondsSince1970] {.async.} =
|
||||
|
@ -140,7 +143,8 @@ method withdrawFunds(market: OnChainMarket,
|
|||
method isProofRequired*(market: OnChainMarket,
|
||||
id: SlotId): Future[bool] {.async.} =
|
||||
try:
|
||||
return await market.contract.isProofRequired(id)
|
||||
let overrides = CallOverrides(blockTag: some BlockTag.pending)
|
||||
return await market.contract.isProofRequired(id, overrides)
|
||||
except ProviderError as e:
|
||||
if e.revertReason.contains("Slot is free"):
|
||||
return false
|
||||
|
@ -149,14 +153,16 @@ method isProofRequired*(market: OnChainMarket,
|
|||
method willProofBeRequired*(market: OnChainMarket,
|
||||
id: SlotId): Future[bool] {.async.} =
|
||||
try:
|
||||
return await market.contract.willProofBeRequired(id)
|
||||
let overrides = CallOverrides(blockTag: some BlockTag.pending)
|
||||
return await market.contract.willProofBeRequired(id, overrides)
|
||||
except ProviderError as e:
|
||||
if e.revertReason.contains("Slot is free"):
|
||||
return false
|
||||
raise e
|
||||
|
||||
method getChallenge*(market: OnChainMarket, id: SlotId): Future[ProofChallenge] {.async.} =
|
||||
return await market.contract.getChallenge(id)
|
||||
let overrides = CallOverrides(blockTag: some BlockTag.pending)
|
||||
return await market.contract.getChallenge(id, overrides)
|
||||
|
||||
method submitProof*(market: OnChainMarket,
|
||||
id: SlotId,
|
||||
|
|
|
@ -23,7 +23,7 @@ ethersuite "On-Chain Clock":
|
|||
let future = (getTime() + 42.years).toUnix
|
||||
discard await ethProvider.send("evm_setNextBlockTimestamp", @[%future])
|
||||
discard await ethProvider.send("evm_mine")
|
||||
check clock.now() == future
|
||||
check eventually clock.now() == future
|
||||
|
||||
test "can wait until a certain time is reached by the chain":
|
||||
let future = clock.now() + 42 # seconds
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import pkg/ethers
|
||||
|
||||
proc currentTime*(provider: Provider): Future[UInt256] {.async.} =
|
||||
return (!await provider.getBlock(BlockTag.latest)).timestamp
|
||||
return (!await provider.getBlock(BlockTag.pending)).timestamp
|
||||
|
||||
proc advanceTime*(provider: JsonRpcProvider, seconds: UInt256) {.async.} =
|
||||
discard await provider.send("evm_increaseTime", @[%("0x" & seconds.toHex)])
|
||||
|
|
|
@ -18,6 +18,8 @@ template ethersuite*(name, body) =
|
|||
setup:
|
||||
ethProvider = JsonRpcProvider.new("ws://localhost:8545")
|
||||
snapshot = await send(ethProvider, "evm_snapshot")
|
||||
# ensure that we have a recent block with a fresh timestamp
|
||||
discard await send(ethProvider, "evm_mine")
|
||||
accounts = await ethProvider.listAccounts()
|
||||
|
||||
teardown:
|
||||
|
|
|
@ -226,6 +226,8 @@ template multinodesuite*(name: string, body: untyped) =
|
|||
# reverted in the test teardown
|
||||
if nodeConfigs.hardhat.isNil:
|
||||
snapshot = await send(ethProvider, "evm_snapshot")
|
||||
# ensure that we have a recent block with a fresh timestamp
|
||||
discard await send(ethProvider, "evm_mine")
|
||||
accounts = await ethProvider.listAccounts()
|
||||
except CatchableError as e:
|
||||
fatal "failed to connect to hardhat", error = e.msg
|
||||
|
|
|
@ -28,7 +28,7 @@ marketplacesuite "Marketplace payouts":
|
|||
let reward = 400.u256
|
||||
let duration = 100.periods
|
||||
let collateral = 200.u256
|
||||
let expiry = 4.periods
|
||||
let expiry = 10.periods
|
||||
let datasetSizeInBlocks = 3
|
||||
let data = await RandomChunker.example(blocks=datasetSizeInBlocks)
|
||||
let client = clients()[0]
|
||||
|
|
|
@ -105,6 +105,7 @@ marketplacesuite "Simulate invalid proofs":
|
|||
|
||||
let purchaseId = await client0.requestStorage(
|
||||
cid,
|
||||
expiry=10.periods,
|
||||
duration=totalPeriods.periods,
|
||||
origDatasetSizeInBlocks=datasetSizeInBlocks)
|
||||
let requestId = client0.requestId(purchaseId).get
|
||||
|
@ -161,6 +162,7 @@ marketplacesuite "Simulate invalid proofs":
|
|||
|
||||
let purchaseId = await client0.requestStorage(
|
||||
cid,
|
||||
expiry=10.periods,
|
||||
duration=totalPeriods.periods,
|
||||
origDatasetSizeInBlocks=datasetSizeInBlocks)
|
||||
let requestId = client0.requestId(purchaseId).get
|
||||
|
|
Loading…
Reference in New Issue