2022-10-08 17:20:50 +00:00
|
|
|
# Nimbus
|
|
|
|
# Copyright (c) 2021 Status Research & Development GmbH
|
|
|
|
# Licensed under either of
|
|
|
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0)
|
|
|
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
|
|
|
# http://opensource.org/licenses/MIT)
|
|
|
|
# at your option. This file may not be copied, modified, or distributed
|
|
|
|
# except according to those terms.
|
|
|
|
|
2022-11-01 15:07:44 +00:00
|
|
|
## Fetch account ranges
|
|
|
|
## ====================
|
2022-10-08 17:20:50 +00:00
|
|
|
##
|
2022-11-01 15:07:44 +00:00
|
|
|
## Acccount ranges not on the database yet are organised in the set
|
|
|
|
## `env.fetchAccounts.unprocessed` of intervals (of account hashes.)
|
2022-10-08 17:20:50 +00:00
|
|
|
##
|
2022-11-01 15:07:44 +00:00
|
|
|
## When processing, the followin happens.
|
|
|
|
##
|
|
|
|
## * Some interval `iv` is removed from the `env.fetchAccounts.unprocessed`
|
|
|
|
## set. This interval set might then be safely accessed and manipulated by
|
|
|
|
## other worker instances.
|
|
|
|
##
|
|
|
|
## * The data points in the interval `iv` (aka ccount hashes) are fetched from
|
|
|
|
## another peer over the network.
|
|
|
|
##
|
|
|
|
## * The received data points of the interval `iv` are verified and merged
|
|
|
|
## into the persistent database.
|
|
|
|
##
|
|
|
|
## * Data points in `iv` that were invalid or not recevied from the network
|
|
|
|
## are merged back it the set `env.fetchAccounts.unprocessed`.
|
2022-10-08 17:20:50 +00:00
|
|
|
##
|
|
|
|
|
|
|
|
import
|
|
|
|
chronicles,
|
|
|
|
chronos,
|
2022-10-20 16:59:54 +00:00
|
|
|
eth/[common, p2p],
|
2022-10-08 17:20:50 +00:00
|
|
|
stew/[interval_set, keyed_queue],
|
|
|
|
stint,
|
|
|
|
../../sync_desc,
|
2022-11-08 18:56:04 +00:00
|
|
|
".."/[constants, range_desc, worker_desc],
|
2022-10-08 17:20:50 +00:00
|
|
|
./com/[com_error, get_account_range],
|
2022-10-14 16:40:32 +00:00
|
|
|
./db/snapdb_accounts
|
2022-10-08 17:20:50 +00:00
|
|
|
|
|
|
|
{.push raises: [Defect].}
|
|
|
|
|
|
|
|
logScope:
|
Prep for full sync after snap make 4 (#1282)
* Re-arrange fetching storage slots in batch module
why;
Previously, fetching partial slot ranges first has a chance of
terminating the worker peer 9due to network error) while there were
many inheritable storage slots on the queue.
Now, inheritance is checked first, then full slot ranges and finally
partial ranges.
* Update logging
* Bundled node information for healing into single object `NodeSpecs`
why:
Previously, partial paths and node keys were kept in separate variables.
This approach was error prone due to copying/reassembling function
argument objects.
As all partial paths, keys, and node data types are more or less handled
as `Blob`s over the network (using Eth/6x, or Snap/1) it makes sense to
hold these `Blob`s as named field in a single object (even if not all
fields are active for the current purpose.)
* For good housekeeping, using `NodeKey` type only for account keys
why:
previously, a mixture of `NodeKey` and `Hash256` was used. Now, only
state or storage root keys use the `Hash256` type.
* Always accept latest pivot (and not a slightly older one)
why;
For testing it was tried to use a slightly older pivot state root than
available. Some anecdotal tests seemed to suggest an advantage so that
more peers are willing to serve on that older pivot. But this could not
be confirmed in subsequent tests (still anecdotal, though.)
As a side note, the distance of the latest pivot to its predecessor is
at least 128 (or whatever the constant `minPivotBlockDistance` is
assigned to.)
* Reshuffle name components for some file and function names
why:
Clarifies purpose:
"storages" becomes: "storage slots"
"store" becomes: "range fetch"
* Stash away currently unused modules in sub-folder named "notused"
2022-10-27 13:49:28 +00:00
|
|
|
topics = "snap-range"
|
2022-10-08 17:20:50 +00:00
|
|
|
|
|
|
|
const
|
|
|
|
extraTraceMessages = false or true
|
|
|
|
## Enabled additional logging noise
|
|
|
|
|
Prep for full sync after snap make 4 (#1282)
* Re-arrange fetching storage slots in batch module
why;
Previously, fetching partial slot ranges first has a chance of
terminating the worker peer 9due to network error) while there were
many inheritable storage slots on the queue.
Now, inheritance is checked first, then full slot ranges and finally
partial ranges.
* Update logging
* Bundled node information for healing into single object `NodeSpecs`
why:
Previously, partial paths and node keys were kept in separate variables.
This approach was error prone due to copying/reassembling function
argument objects.
As all partial paths, keys, and node data types are more or less handled
as `Blob`s over the network (using Eth/6x, or Snap/1) it makes sense to
hold these `Blob`s as named field in a single object (even if not all
fields are active for the current purpose.)
* For good housekeeping, using `NodeKey` type only for account keys
why:
previously, a mixture of `NodeKey` and `Hash256` was used. Now, only
state or storage root keys use the `Hash256` type.
* Always accept latest pivot (and not a slightly older one)
why;
For testing it was tried to use a slightly older pivot state root than
available. Some anecdotal tests seemed to suggest an advantage so that
more peers are willing to serve on that older pivot. But this could not
be confirmed in subsequent tests (still anecdotal, though.)
As a side note, the distance of the latest pivot to its predecessor is
at least 128 (or whatever the constant `minPivotBlockDistance` is
assigned to.)
* Reshuffle name components for some file and function names
why:
Clarifies purpose:
"storages" becomes: "storage slots"
"store" becomes: "range fetch"
* Stash away currently unused modules in sub-folder named "notused"
2022-10-27 13:49:28 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Private logging helpers
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
template logTxt(info: static[string]): static[string] =
|
|
|
|
"Accounts range " & info
|
|
|
|
|
2022-11-08 18:56:04 +00:00
|
|
|
proc dumpUnprocessed(
|
2022-10-08 17:20:50 +00:00
|
|
|
buddy: SnapBuddyRef;
|
2022-11-08 18:56:04 +00:00
|
|
|
env: SnapPivotRef;
|
|
|
|
): string =
|
|
|
|
## Debugging ...
|
|
|
|
let
|
|
|
|
peer = buddy.peer
|
|
|
|
pivot = "#" & $env.stateHeader.blockNumber # for logging
|
|
|
|
moan = proc(overlap: UInt256; iv: NodeTagRange) =
|
|
|
|
trace logTxt "unprocessed => overlap", peer, pivot, overlap, iv
|
|
|
|
|
|
|
|
env.fetchAccounts.unprocessed.dump(moan, 5)
|
2022-10-08 17:20:50 +00:00
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Private functions
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
2022-11-08 18:56:04 +00:00
|
|
|
proc getUnprocessed(
|
|
|
|
buddy: SnapBuddyRef;
|
|
|
|
env: SnapPivotRef;
|
|
|
|
): Result[NodeTagRange,void] =
|
2022-10-08 17:20:50 +00:00
|
|
|
## Fetch an interval from one of the account range lists.
|
2022-11-08 18:56:04 +00:00
|
|
|
let accountRangeMax = high(UInt256) div buddy.ctx.buddiesMax.u256
|
2022-10-08 17:20:50 +00:00
|
|
|
|
2022-11-08 18:56:04 +00:00
|
|
|
env.fetchAccounts.unprocessed.fetch accountRangeMax
|
2022-10-08 17:20:50 +00:00
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
2022-11-01 15:07:44 +00:00
|
|
|
# Private functions: do the account fetching for one round
|
2022-10-08 17:20:50 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
2022-11-08 18:56:04 +00:00
|
|
|
proc accountsRangefetchImpl(
|
|
|
|
buddy: SnapBuddyRef;
|
|
|
|
env: SnapPivotRef;
|
|
|
|
): Future[bool] {.async.} =
|
2022-11-01 15:07:44 +00:00
|
|
|
## Fetch accounts and store them in the database. Returns true while more
|
|
|
|
## data can probably be fetched.
|
2022-10-08 17:20:50 +00:00
|
|
|
let
|
|
|
|
ctx = buddy.ctx
|
|
|
|
peer = buddy.peer
|
|
|
|
stateRoot = env.stateHeader.stateRoot
|
2022-11-01 15:07:44 +00:00
|
|
|
pivot = "#" & $env.stateHeader.blockNumber # for logging
|
2022-10-08 17:20:50 +00:00
|
|
|
|
|
|
|
# Get a range of accounts to fetch from
|
|
|
|
let iv = block:
|
2022-11-08 18:56:04 +00:00
|
|
|
let rc = buddy.getUnprocessed(env)
|
2022-10-08 17:20:50 +00:00
|
|
|
if rc.isErr:
|
2022-10-14 16:40:32 +00:00
|
|
|
when extraTraceMessages:
|
2022-11-01 15:07:44 +00:00
|
|
|
trace logTxt "currently all processed", peer, pivot
|
2022-10-08 17:20:50 +00:00
|
|
|
return
|
|
|
|
rc.value
|
|
|
|
|
|
|
|
# Process received accounts and stash storage slots to fetch later
|
|
|
|
let dd = block:
|
2022-11-01 15:07:44 +00:00
|
|
|
let rc = await buddy.getAccountRange(stateRoot, iv, pivot)
|
2022-10-08 17:20:50 +00:00
|
|
|
if rc.isErr:
|
2022-11-08 18:56:04 +00:00
|
|
|
env.fetchAccounts.unprocessed.merge iv # fail => interval back to pool
|
2022-10-08 17:20:50 +00:00
|
|
|
let error = rc.error
|
|
|
|
if await buddy.ctrl.stopAfterSeriousComError(error, buddy.data.errors):
|
|
|
|
when extraTraceMessages:
|
2022-11-01 15:07:44 +00:00
|
|
|
trace logTxt "fetch error => stop", peer, pivot, reqLen=iv.len, error
|
2022-10-08 17:20:50 +00:00
|
|
|
return
|
|
|
|
rc.value
|
|
|
|
|
2022-11-01 15:07:44 +00:00
|
|
|
# Reset error counts for detecting repeated timeouts, network errors, etc.
|
|
|
|
buddy.data.errors.resetComError()
|
|
|
|
|
2022-10-08 17:20:50 +00:00
|
|
|
let
|
2022-10-19 10:04:06 +00:00
|
|
|
gotAccounts = dd.data.accounts.len
|
|
|
|
gotStorage = dd.withStorage.len
|
|
|
|
|
2022-11-01 15:07:44 +00:00
|
|
|
#when extraTraceMessages:
|
|
|
|
# trace logTxt "fetched", peer, gotAccounts, gotStorage,
|
|
|
|
# pivot, reqLen=iv.len, gotLen=dd.consumed.len
|
2022-10-08 17:20:50 +00:00
|
|
|
|
2022-11-08 18:56:04 +00:00
|
|
|
# Now, as we fully own the scheduler and the original interval can savely be
|
|
|
|
# placed back for a moment -- to be corrected below.
|
|
|
|
env.fetchAccounts.unprocessed.merge iv
|
|
|
|
|
|
|
|
# Processed accounts hashes are set up as a set of intervals which is needed
|
|
|
|
# if the data range returned from the network contains holes.
|
|
|
|
let processed = NodeTagRangeSet.init()
|
|
|
|
if 0 < dd.data.accounts.len:
|
|
|
|
discard processed.merge(iv.minPt, dd.data.accounts[^1].accKey.to(NodeTag))
|
|
|
|
else:
|
|
|
|
discard processed.merge iv
|
|
|
|
|
|
|
|
let dangling = block:
|
|
|
|
# No left boundary check needed. If there is a gap, the partial path for
|
|
|
|
# that gap is returned by the import function to be registered, below.
|
|
|
|
let rc = ctx.data.snapDb.importAccounts(
|
|
|
|
peer, stateRoot, iv.minPt, dd.data, noBaseBoundCheck = true)
|
2022-10-08 17:20:50 +00:00
|
|
|
if rc.isErr:
|
|
|
|
# Bad data, just try another peer
|
|
|
|
buddy.ctrl.zombie = true
|
|
|
|
when extraTraceMessages:
|
Prep for full sync after snap make 4 (#1282)
* Re-arrange fetching storage slots in batch module
why;
Previously, fetching partial slot ranges first has a chance of
terminating the worker peer 9due to network error) while there were
many inheritable storage slots on the queue.
Now, inheritance is checked first, then full slot ranges and finally
partial ranges.
* Update logging
* Bundled node information for healing into single object `NodeSpecs`
why:
Previously, partial paths and node keys were kept in separate variables.
This approach was error prone due to copying/reassembling function
argument objects.
As all partial paths, keys, and node data types are more or less handled
as `Blob`s over the network (using Eth/6x, or Snap/1) it makes sense to
hold these `Blob`s as named field in a single object (even if not all
fields are active for the current purpose.)
* For good housekeeping, using `NodeKey` type only for account keys
why:
previously, a mixture of `NodeKey` and `Hash256` was used. Now, only
state or storage root keys use the `Hash256` type.
* Always accept latest pivot (and not a slightly older one)
why;
For testing it was tried to use a slightly older pivot state root than
available. Some anecdotal tests seemed to suggest an advantage so that
more peers are willing to serve on that older pivot. But this could not
be confirmed in subsequent tests (still anecdotal, though.)
As a side note, the distance of the latest pivot to its predecessor is
at least 128 (or whatever the constant `minPivotBlockDistance` is
assigned to.)
* Reshuffle name components for some file and function names
why:
Clarifies purpose:
"storages" becomes: "storage slots"
"store" becomes: "range fetch"
* Stash away currently unused modules in sub-folder named "notused"
2022-10-27 13:49:28 +00:00
|
|
|
trace logTxt "import failed => stop", peer, gotAccounts, gotStorage,
|
2022-11-08 18:56:04 +00:00
|
|
|
pivot, reqLen=iv.len, gotLen=processed.total, error=rc.error
|
2022-10-08 17:20:50 +00:00
|
|
|
return
|
2022-11-08 18:56:04 +00:00
|
|
|
rc.value
|
2022-10-08 17:20:50 +00:00
|
|
|
|
|
|
|
# Statistics
|
2022-10-19 10:04:06 +00:00
|
|
|
env.nAccounts.inc(gotAccounts)
|
2022-10-08 17:20:50 +00:00
|
|
|
|
2022-11-08 18:56:04 +00:00
|
|
|
# Punch holes into the reproted range from the network if it contains holes.
|
|
|
|
for w in dangling:
|
|
|
|
discard processed.reduce(
|
|
|
|
w.partialPath.min(NodeKey).to(NodeTag),
|
|
|
|
w.partialPath.max(NodeKey).to(Nodetag))
|
|
|
|
|
|
|
|
# Update book keeping
|
|
|
|
for w in processed.increasing:
|
|
|
|
# Remove the processed range from the batch of unprocessed ones.
|
|
|
|
env.fetchAccounts.unprocessed.reduce w
|
|
|
|
# Register consumed intervals on the accumulator over all state roots.
|
|
|
|
discard buddy.ctx.data.coveredAccounts.merge w
|
|
|
|
|
|
|
|
# Register accounts with storage slots on the storage TODO list.
|
|
|
|
env.fetchStorageFull.merge dd.withStorage
|
|
|
|
|
|
|
|
when extraTraceMessages:
|
|
|
|
trace logTxt "request done", peer, pivot,
|
|
|
|
nCheckNodes=env.fetchAccounts.checkNodes.len,
|
|
|
|
nMissingNodes=env.fetchAccounts.missingNodes.len,
|
|
|
|
imported=processed.dump(), unprocessed=buddy.dumpUnprocessed(env)
|
2022-10-08 17:20:50 +00:00
|
|
|
|
2022-11-01 15:07:44 +00:00
|
|
|
return true
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Public functions
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
proc rangeFetchAccounts*(buddy: SnapBuddyRef) {.async.} =
|
|
|
|
## Fetch accounts and store them in the database.
|
2022-11-08 18:56:04 +00:00
|
|
|
let env = buddy.data.pivotEnv
|
|
|
|
if not env.fetchAccounts.unprocessed.isEmpty():
|
2022-11-01 15:07:44 +00:00
|
|
|
let
|
|
|
|
ctx = buddy.ctx
|
|
|
|
peer = buddy.peer
|
|
|
|
pivot = "#" & $env.stateHeader.blockNumber # for logging
|
|
|
|
|
|
|
|
when extraTraceMessages:
|
2022-11-08 18:56:04 +00:00
|
|
|
trace logTxt "start", peer, pivot
|
2022-11-01 15:07:44 +00:00
|
|
|
|
|
|
|
var nFetchAccounts = 0
|
2022-11-08 18:56:04 +00:00
|
|
|
while not env.fetchAccounts.unprocessed.isEmpty() and
|
|
|
|
buddy.ctrl.running and
|
|
|
|
env == buddy.data.pivotEnv:
|
2022-11-01 15:07:44 +00:00
|
|
|
nFetchAccounts.inc
|
2022-11-08 18:56:04 +00:00
|
|
|
if not await buddy.accountsRangefetchImpl(env):
|
|
|
|
break
|
|
|
|
|
|
|
|
# Clean up storage slots queue first it it becomes too large
|
|
|
|
let nStoQu = env.fetchStorageFull.len + env.fetchStoragePart.len
|
|
|
|
if snapAccountsBuddyStoragesSlotsQuPrioThresh < nStoQu:
|
|
|
|
break
|
2022-11-01 15:07:44 +00:00
|
|
|
|
|
|
|
when extraTraceMessages:
|
2022-11-08 18:56:04 +00:00
|
|
|
trace logTxt "done", peer, pivot, nFetchAccounts,
|
|
|
|
runState=buddy.ctrl.state
|
2022-10-08 17:20:50 +00:00
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# End
|
|
|
|
# ------------------------------------------------------------------------------
|