mirror of
https://github.com/status-im/nim-codex.git
synced 2025-01-09 18:36:29 +00:00
92a0eda79a
* adds a new overload of queryPastEvents allowing to query past events based on timestamp in the past * adds state restoration to validator * refactors a bit to get the tests back to work * replaces deprecated generic methods from Market with methods for specific event types * Refactors binary search * adds market tests for querying past SlotFilled events and binary search * Takes into account that <<earliest>> block available is not necessarily the genesis block * Adds more logging and makes testing earliest block boundary more reliable * adds validation tests for historical state restoration * adds mockprovider to simplify and improve testing of the edge conditions * adds slot reservation to the new tests after rebasing * adds validation groups and group index in logs of validator * adds integration test with two validators * adds comment on how to enable logging in integration test executable itself * testIntegration: makes list is running nodes injected and available in the body of the test * validation: adds integration test for historical state * adds more logging to validator * integration test: validator only looks 30 days back for historical state * adds logging of the slotState when removing slots during validation * review and refactor validator integration tests * adds validation to the set of integration tests * Fixes mistyped name of the mock provider module in testMarket * Fixes a typo in the name of the validation suite in integration tests * Makes validation unit test a bit easier to follow * better use of logScopes to reduce duplication * improves timing and clarifies the test conditions * uses http as default RPC provider for nodes running in integration tests as a workaround for dropped subscriptions * simplifies the validation integration tests by waiting for failed request instead of tracking slots * adds config option allowing selectively to set different provider url * Brings back the default settings for RPC provider in integration tests * use http RPC provider for clients in validation integration tests * fine-tune the tests * Makes validator integration test more robust - adds extra tracking * brings tracking of marketplace event back to validator integration test * refactors integration tests * deletes tmp file * adds <<return>> after forcing integration test to fail preliminarily * re-enables all integration tests and matrix * stops debug output in CI * allows to choose a different RPC provider for a given integration test suite * fixes signature of <<getBlock>> method in mockProvider * adds missing import which seem to be braking integration tests on windows * makes sure that clients, SPs, and validators use the same provider url * makes validator integration tests using http at 127.0.0.1:8545 * testvalidator: stop resubscribing as we are now using http polling as rpc provider * applying review comments * groups queryPastStorage overrides together (review comment) * groups the historical validation tests into a sub suite * removes the temporary extensions in marketplacesuite and multinodesuite allowing to specify provider url * simplifies validation integration tests * Removes debug logs when waiting for request to fail * Renaming waitForRequestFailed => waitForRequestToFail * renames blockNumberForBlocksAgo to pastBlockTag and makes it private * removes redundant debugging logs * refines logging in validation * removes dev logging from mockmarket * improves exception handling in provider helper procs and prepares for extraction to a separate module * Uses chronos instead of std/times for Duration * extracts provider and binary search helpers to a separate module * removes redundant log entry params from validator * unifies the notation to consistently use method call syntax * reuses ProviderError from nim-ethers in the provider extension * clarifies the comment in multinodesuite * uses == operator to check the predefined tags and raises exception when `BlockTag.pending` is requested. * when waiting for request to fail, we break on any request state that is not Started * removes tests that were moved to testProvider from testMarket * extracts tests that use MockProvider to a separate async suite * improves performance of the historical state restoration * removing redundant log messages in validator (groupIndex and groups) * adds testProvider to testContracts group * removes unused import in testMarket
127 lines
4.8 KiB
Nim
127 lines
4.8 KiB
Nim
import pkg/ethers/provider
|
|
import pkg/chronos
|
|
import pkg/questionable
|
|
|
|
import ../logutils
|
|
|
|
from ../clock import SecondsSince1970
|
|
|
|
logScope:
|
|
topics = "marketplace onchain provider"
|
|
|
|
proc raiseProviderError(message: string) {.raises: [ProviderError].} =
|
|
raise newException(ProviderError, message)
|
|
|
|
proc blockNumberAndTimestamp*(provider: Provider, blockTag: BlockTag):
|
|
Future[(UInt256, UInt256)] {.async: (raises: [ProviderError]).} =
|
|
without latestBlock =? await provider.getBlock(blockTag):
|
|
raiseProviderError("Could not get latest block")
|
|
|
|
without latestBlockNumber =? latestBlock.number:
|
|
raiseProviderError("Could not get latest block number")
|
|
|
|
return (latestBlockNumber, latestBlock.timestamp)
|
|
|
|
proc binarySearchFindClosestBlock(
|
|
provider: Provider,
|
|
epochTime: int,
|
|
low: UInt256,
|
|
high: UInt256): Future[UInt256] {.async: (raises: [ProviderError]).} =
|
|
let (_, lowTimestamp) =
|
|
await provider.blockNumberAndTimestamp(BlockTag.init(low))
|
|
let (_, highTimestamp) =
|
|
await provider.blockNumberAndTimestamp(BlockTag.init(high))
|
|
if abs(lowTimestamp.truncate(int) - epochTime) <
|
|
abs(highTimestamp.truncate(int) - epochTime):
|
|
return low
|
|
else:
|
|
return high
|
|
|
|
proc binarySearchBlockNumberForEpoch(
|
|
provider: Provider,
|
|
epochTime: UInt256,
|
|
latestBlockNumber: UInt256,
|
|
earliestBlockNumber: UInt256): Future[UInt256]
|
|
{.async: (raises: [ProviderError]).} =
|
|
var low = earliestBlockNumber
|
|
var high = latestBlockNumber
|
|
|
|
while low <= high:
|
|
if low == 0 and high == 0:
|
|
return low
|
|
let mid = (low + high) div 2
|
|
let (midBlockNumber, midBlockTimestamp) =
|
|
await provider.blockNumberAndTimestamp(BlockTag.init(mid))
|
|
|
|
if midBlockTimestamp < epochTime:
|
|
low = mid + 1
|
|
elif midBlockTimestamp > epochTime:
|
|
high = mid - 1
|
|
else:
|
|
return midBlockNumber
|
|
# NOTICE that by how the binary search is implemented, when it finishes
|
|
# low is always greater than high - this is why we use high, where
|
|
# intuitively we would use low:
|
|
await provider.binarySearchFindClosestBlock(
|
|
epochTime.truncate(int), low=high, high=low)
|
|
|
|
proc blockNumberForEpoch*(
|
|
provider: Provider,
|
|
epochTime: SecondsSince1970): Future[UInt256]
|
|
{.async: (raises: [ProviderError]).} =
|
|
let epochTimeUInt256 = epochTime.u256
|
|
let (latestBlockNumber, latestBlockTimestamp) =
|
|
await provider.blockNumberAndTimestamp(BlockTag.latest)
|
|
let (earliestBlockNumber, earliestBlockTimestamp) =
|
|
await provider.blockNumberAndTimestamp(BlockTag.earliest)
|
|
|
|
# Initially we used the average block time to predict
|
|
# the number of blocks we need to look back in order to find
|
|
# the block number corresponding to the given epoch time.
|
|
# This estimation can be highly inaccurate if block time
|
|
# was changing in the past or is fluctuating and therefore
|
|
# we used that information initially only to find out
|
|
# if the available history is long enough to perform effective search.
|
|
# It turns out we do not have to do that. There is an easier way.
|
|
#
|
|
# First we check if the given epoch time equals the timestamp of either
|
|
# the earliest or the latest block. If it does, we just return the
|
|
# block number of that block.
|
|
#
|
|
# Otherwise, if the earliest available block is not the genesis block,
|
|
# we should check the timestamp of that earliest block and if it is greater
|
|
# than the epoch time, we should issue a warning and return
|
|
# that earliest block number.
|
|
# In all other cases, thus when the earliest block is not the genesis
|
|
# block but its timestamp is not greater than the requested epoch time, or
|
|
# if the earliest available block is the genesis block,
|
|
# (which means we have the whole history available), we should proceed with
|
|
# the binary search.
|
|
#
|
|
# Additional benefit of this method is that we do not have to rely
|
|
# on the average block time, which not only makes the whole thing
|
|
# more reliable, but also easier to test.
|
|
|
|
# Are lucky today?
|
|
if earliestBlockTimestamp == epochTimeUInt256:
|
|
return earliestBlockNumber
|
|
if latestBlockTimestamp == epochTimeUInt256:
|
|
return latestBlockNumber
|
|
|
|
if earliestBlockNumber > 0 and earliestBlockTimestamp > epochTimeUInt256:
|
|
let availableHistoryInDays =
|
|
(latestBlockTimestamp - earliestBlockTimestamp) div
|
|
1.days.secs.u256
|
|
warn "Short block history detected.", earliestBlockTimestamp =
|
|
earliestBlockTimestamp, days = availableHistoryInDays
|
|
return earliestBlockNumber
|
|
|
|
return await provider.binarySearchBlockNumberForEpoch(
|
|
epochTimeUInt256, latestBlockNumber, earliestBlockNumber)
|
|
|
|
proc pastBlockTag*(provider: Provider,
|
|
blocksAgo: int):
|
|
Future[BlockTag] {.async: (raises: [ProviderError]).} =
|
|
let head = await provider.getBlockNumber()
|
|
return BlockTag.init(head - blocksAgo.abs.u256)
|