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
|
provider: Provider
|
||||||
subscription: Subscription
|
subscription: Subscription
|
||||||
offset: times.Duration
|
offset: times.Duration
|
||||||
|
blockNumber: UInt256
|
||||||
started: bool
|
started: bool
|
||||||
newBlock: AsyncEvent
|
newBlock: AsyncEvent
|
||||||
lastBlockTime: UInt256
|
|
||||||
|
|
||||||
proc new*(_: type OnChainClock, provider: Provider): OnChainClock =
|
proc new*(_: type OnChainClock, provider: Provider): OnChainClock =
|
||||||
OnChainClock(provider: provider, newBlock: newAsyncEvent())
|
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.} =
|
method start*(clock: OnChainClock) {.async.} =
|
||||||
if clock.started:
|
if clock.started:
|
||||||
return
|
return
|
||||||
|
|
||||||
proc onBlock(blck: Block) =
|
proc onBlock(_: Block) =
|
||||||
let blockTime = initTime(blck.timestamp.truncate(int64), 0)
|
# ignore block parameter; hardhat may call this with pending blocks
|
||||||
let computerTime = getTime()
|
asyncSpawn clock.update()
|
||||||
clock.offset = blockTime - computerTime
|
|
||||||
clock.lastBlockTime = blck.timestamp
|
|
||||||
clock.newBlock.fire()
|
|
||||||
|
|
||||||
if latestBlock =? (await clock.provider.getBlock(BlockTag.latest)):
|
await clock.update()
|
||||||
onBlock(latestBlock)
|
|
||||||
|
|
||||||
clock.subscription = await clock.provider.subscribe(onBlock)
|
clock.subscription = await clock.provider.subscribe(onBlock)
|
||||||
clock.started = true
|
clock.started = true
|
||||||
|
@ -47,20 +60,6 @@ method stop*(clock: OnChainClock) {.async.} =
|
||||||
clock.started = false
|
clock.started = false
|
||||||
|
|
||||||
method now*(clock: OnChainClock): SecondsSince1970 =
|
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()"
|
doAssert clock.started, "clock should be started before calling now()"
|
||||||
return toUnix(getTime() + clock.offset)
|
return toUnix(getTime() + clock.offset)
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,8 @@ method proofDowntime*(market: OnChainMarket): Future[uint8] {.async.} =
|
||||||
return config.proofs.downtime
|
return config.proofs.downtime
|
||||||
|
|
||||||
method getPointer*(market: OnChainMarket, slotId: SlotId): Future[uint8] {.async.} =
|
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.} =
|
method myRequests*(market: OnChainMarket): Future[seq[RequestId]] {.async.} =
|
||||||
return await market.contract.myRequests
|
return await market.contract.myRequests
|
||||||
|
@ -88,7 +89,8 @@ method getRequest(market: OnChainMarket,
|
||||||
method requestState*(market: OnChainMarket,
|
method requestState*(market: OnChainMarket,
|
||||||
requestId: RequestId): Future[?RequestState] {.async.} =
|
requestId: RequestId): Future[?RequestState] {.async.} =
|
||||||
try:
|
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:
|
except ProviderError as e:
|
||||||
if e.revertReason.contains("Unknown request"):
|
if e.revertReason.contains("Unknown request"):
|
||||||
return none RequestState
|
return none RequestState
|
||||||
|
@ -96,7 +98,8 @@ method requestState*(market: OnChainMarket,
|
||||||
|
|
||||||
method slotState*(market: OnChainMarket,
|
method slotState*(market: OnChainMarket,
|
||||||
slotId: SlotId): Future[SlotState] {.async.} =
|
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,
|
method getRequestEnd*(market: OnChainMarket,
|
||||||
id: RequestId): Future[SecondsSince1970] {.async.} =
|
id: RequestId): Future[SecondsSince1970] {.async.} =
|
||||||
|
@ -140,7 +143,8 @@ method withdrawFunds(market: OnChainMarket,
|
||||||
method isProofRequired*(market: OnChainMarket,
|
method isProofRequired*(market: OnChainMarket,
|
||||||
id: SlotId): Future[bool] {.async.} =
|
id: SlotId): Future[bool] {.async.} =
|
||||||
try:
|
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:
|
except ProviderError as e:
|
||||||
if e.revertReason.contains("Slot is free"):
|
if e.revertReason.contains("Slot is free"):
|
||||||
return false
|
return false
|
||||||
|
@ -149,14 +153,16 @@ method isProofRequired*(market: OnChainMarket,
|
||||||
method willProofBeRequired*(market: OnChainMarket,
|
method willProofBeRequired*(market: OnChainMarket,
|
||||||
id: SlotId): Future[bool] {.async.} =
|
id: SlotId): Future[bool] {.async.} =
|
||||||
try:
|
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:
|
except ProviderError as e:
|
||||||
if e.revertReason.contains("Slot is free"):
|
if e.revertReason.contains("Slot is free"):
|
||||||
return false
|
return false
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
method getChallenge*(market: OnChainMarket, id: SlotId): Future[ProofChallenge] {.async.} =
|
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,
|
method submitProof*(market: OnChainMarket,
|
||||||
id: SlotId,
|
id: SlotId,
|
||||||
|
|
|
@ -23,7 +23,7 @@ ethersuite "On-Chain Clock":
|
||||||
let future = (getTime() + 42.years).toUnix
|
let future = (getTime() + 42.years).toUnix
|
||||||
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 clock.now() == future
|
check eventually clock.now() == future
|
||||||
|
|
||||||
test "can wait until a certain time is reached by the chain":
|
test "can wait until a certain time is reached by the chain":
|
||||||
let future = clock.now() + 42 # seconds
|
let future = clock.now() + 42 # seconds
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import pkg/ethers
|
import pkg/ethers
|
||||||
|
|
||||||
proc currentTime*(provider: Provider): Future[UInt256] {.async.} =
|
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.} =
|
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)])
|
||||||
|
|
|
@ -18,6 +18,8 @@ template ethersuite*(name, body) =
|
||||||
setup:
|
setup:
|
||||||
ethProvider = JsonRpcProvider.new("ws://localhost:8545")
|
ethProvider = JsonRpcProvider.new("ws://localhost:8545")
|
||||||
snapshot = await send(ethProvider, "evm_snapshot")
|
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()
|
accounts = await ethProvider.listAccounts()
|
||||||
|
|
||||||
teardown:
|
teardown:
|
||||||
|
|
|
@ -226,6 +226,8 @@ template multinodesuite*(name: string, body: untyped) =
|
||||||
# reverted in the test teardown
|
# reverted in the test teardown
|
||||||
if nodeConfigs.hardhat.isNil:
|
if nodeConfigs.hardhat.isNil:
|
||||||
snapshot = await send(ethProvider, "evm_snapshot")
|
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()
|
accounts = await ethProvider.listAccounts()
|
||||||
except CatchableError as e:
|
except CatchableError as e:
|
||||||
fatal "failed to connect to hardhat", error = e.msg
|
fatal "failed to connect to hardhat", error = e.msg
|
||||||
|
|
|
@ -28,7 +28,7 @@ marketplacesuite "Marketplace payouts":
|
||||||
let reward = 400.u256
|
let reward = 400.u256
|
||||||
let duration = 100.periods
|
let duration = 100.periods
|
||||||
let collateral = 200.u256
|
let collateral = 200.u256
|
||||||
let expiry = 4.periods
|
let expiry = 10.periods
|
||||||
let datasetSizeInBlocks = 3
|
let datasetSizeInBlocks = 3
|
||||||
let data = await RandomChunker.example(blocks=datasetSizeInBlocks)
|
let data = await RandomChunker.example(blocks=datasetSizeInBlocks)
|
||||||
let client = clients()[0]
|
let client = clients()[0]
|
||||||
|
|
|
@ -105,6 +105,7 @@ marketplacesuite "Simulate invalid proofs":
|
||||||
|
|
||||||
let purchaseId = await client0.requestStorage(
|
let purchaseId = await client0.requestStorage(
|
||||||
cid,
|
cid,
|
||||||
|
expiry=10.periods,
|
||||||
duration=totalPeriods.periods,
|
duration=totalPeriods.periods,
|
||||||
origDatasetSizeInBlocks=datasetSizeInBlocks)
|
origDatasetSizeInBlocks=datasetSizeInBlocks)
|
||||||
let requestId = client0.requestId(purchaseId).get
|
let requestId = client0.requestId(purchaseId).get
|
||||||
|
@ -161,6 +162,7 @@ marketplacesuite "Simulate invalid proofs":
|
||||||
|
|
||||||
let purchaseId = await client0.requestStorage(
|
let purchaseId = await client0.requestStorage(
|
||||||
cid,
|
cid,
|
||||||
|
expiry=10.periods,
|
||||||
duration=totalPeriods.periods,
|
duration=totalPeriods.periods,
|
||||||
origDatasetSizeInBlocks=datasetSizeInBlocks)
|
origDatasetSizeInBlocks=datasetSizeInBlocks)
|
||||||
let requestId = client0.requestId(purchaseId).get
|
let requestId = client0.requestId(purchaseId).get
|
||||||
|
|
Loading…
Reference in New Issue