630 lines
23 KiB
Nim
630 lines
23 KiB
Nim
# 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.
|
|
|
|
{.push raises: [].}
|
|
|
|
import
|
|
std/[math, sets, sequtils],
|
|
chronicles,
|
|
chronos,
|
|
eth/p2p, # trie/trie_defs],
|
|
stew/[interval_set, keyed_queue, sorted_set],
|
|
"../.."/[sync_desc, types],
|
|
".."/[constants, range_desc, worker_desc],
|
|
./db/[hexary_error, snapdb_accounts, snapdb_contracts, snapdb_pivot],
|
|
./pivot/[heal_accounts, heal_storage_slots, range_fetch_accounts,
|
|
range_fetch_contracts, range_fetch_storage_slots,
|
|
storage_queue_helper],
|
|
./ticker
|
|
|
|
logScope:
|
|
topics = "snap-pivot"
|
|
|
|
const
|
|
extraTraceMessages = false or true
|
|
## Enabled additional logging noise
|
|
|
|
proc pivotMothball*(env: SnapPivotRef) {.gcsafe.}
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Private helpers, logging
|
|
# ------------------------------------------------------------------------------
|
|
|
|
template logTxt(info: static[string]): static[string] =
|
|
"Pivot " & info
|
|
|
|
template ignExceptionOops(info: static[string]; code: untyped) =
|
|
try:
|
|
code
|
|
except CatchableError as e:
|
|
trace logTxt "Ooops", `info`=info, name=($e.name), msg=(e.msg)
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Private helpers
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc accountsHealingOk(
|
|
env: SnapPivotRef; # Current pivot environment
|
|
ctx: SnapCtxRef; # Some global context
|
|
): bool =
|
|
## Returns `true` if accounts healing is enabled for this pivot.
|
|
not env.fetchAccounts.processed.isEmpty and
|
|
healAccountsCoverageTrigger <= ctx.pivotAccountsCoverage()
|
|
|
|
|
|
proc init(
|
|
T: type SnapRangeBatchRef; # Collection of sets of account ranges
|
|
ctx: SnapCtxRef; # Some global context
|
|
): T =
|
|
## Account ranges constructor
|
|
new result
|
|
result.unprocessed.init() # full range on the first set of the pair
|
|
result.processed = NodeTagRangeSet.init()
|
|
|
|
# Update coverage level roll over
|
|
ctx.pivotAccountsCoverage100PcRollOver()
|
|
|
|
# Initialise accounts range fetch batch, the pair of `fetchAccounts[]` range
|
|
# sets. Deprioritise already processed ranges by moving it to the second set.
|
|
for iv in ctx.pool.coveredAccounts.increasing:
|
|
discard result.unprocessed[0].reduce iv
|
|
discard result.unprocessed[1].merge iv
|
|
|
|
proc init(
|
|
T: type SnapPivotRef; # Privot descriptor type
|
|
ctx: SnapCtxRef; # Some global context
|
|
header: BlockHeader; # Header to generate new pivot from
|
|
): T =
|
|
## Pivot constructor.
|
|
result = T(
|
|
stateHeader: header,
|
|
fetchAccounts: SnapRangeBatchRef.init(ctx))
|
|
result.storageAccounts.init()
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public functions: pivot table related
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc beforeTopMostlyClean*(pivotTable: var SnapPivotTable) =
|
|
## Clean up pivot queues of the entry before the top one. The queues are
|
|
## the pivot data that need most of the memory. This cleaned pivot is not
|
|
## usable any more after cleaning but might be useful as historic record.
|
|
let rc = pivotTable.beforeLastValue
|
|
if rc.isOk:
|
|
rc.value.pivotMothball
|
|
|
|
|
|
proc topNumber*(pivotTable: var SnapPivotTable): BlockNumber =
|
|
## Return the block number of the top pivot entry, or zero if there is none.
|
|
let rc = pivotTable.lastValue
|
|
if rc.isOk:
|
|
return rc.value.stateHeader.blockNumber
|
|
|
|
|
|
proc reverseUpdate*(
|
|
pivotTable: var SnapPivotTable; # Pivot table
|
|
header: BlockHeader; # Header to generate new pivot from
|
|
ctx: SnapCtxRef; # Some global context
|
|
) =
|
|
## Activate environment for earlier state root implied by `header` argument.
|
|
##
|
|
## Note that the pivot table is assumed to be sorted by the block numbers of
|
|
## the pivot header.
|
|
##
|
|
# Append per-state root environment to LRU queue
|
|
discard pivotTable.prepend(
|
|
header.stateRoot, SnapPivotRef.init(ctx, header))
|
|
|
|
# Make sure that the LRU table does not grow too big.
|
|
if max(3, ctx.buddiesMax) < pivotTable.len:
|
|
# Delete second entry rather than the first which might currently
|
|
# be needed.
|
|
let rc = pivotTable.secondKey
|
|
if rc.isOk:
|
|
pivotTable.del rc.value
|
|
|
|
|
|
proc tickerStats*(
|
|
pivotTable: var SnapPivotTable; # Pivot table
|
|
ctx: SnapCtxRef; # Some global context
|
|
): TickerSnapStatsUpdater =
|
|
## This function returns a function of type `TickerStatsUpdater` that prints
|
|
## out pivot table statitics. The returned fuction is supposed to drive
|
|
## ticker` module.
|
|
proc meanStdDev(sum, sqSum: float; length: int): (float,float) =
|
|
if 0 < length:
|
|
result[0] = sum / length.float
|
|
let
|
|
sqSumAv = sqSum / length.float
|
|
rSq = result[0] * result[0]
|
|
if rSq < sqSumAv:
|
|
result[1] = sqrt(sqSum / length.float - result[0] * result[0])
|
|
|
|
result = proc: TickerSnapStats =
|
|
var
|
|
aSum, aSqSum, uSum, uSqSum, sSum, sSqSum, cSum, cSqSum: float
|
|
count = 0
|
|
for kvp in ctx.pool.pivotTable.nextPairs:
|
|
|
|
# Accounts mean & variance
|
|
let aLen = kvp.data.nAccounts.float
|
|
if 0 < aLen:
|
|
count.inc
|
|
aSum += aLen
|
|
aSqSum += aLen * aLen
|
|
|
|
# Fill utilisation mean & variance
|
|
let fill = kvp.data.fetchAccounts.processed.fullFactor
|
|
uSum += fill
|
|
uSqSum += fill * fill
|
|
|
|
let sLen = kvp.data.nSlotLists.float
|
|
sSum += sLen
|
|
sSqSum += sLen * sLen
|
|
|
|
# Lists of missing contracts
|
|
let cLen = kvp.data.nContracts.float
|
|
cSum += cLen
|
|
cSqSum += cLen * cLen
|
|
let
|
|
env = ctx.pool.pivotTable.lastValue.get(otherwise = nil)
|
|
accCoverage = (ctx.pool.coveredAccounts.fullFactor +
|
|
ctx.pool.covAccTimesFull.float)
|
|
accFill = meanStdDev(uSum, uSqSum, count)
|
|
var
|
|
beaconBlock = none(BlockNumber)
|
|
pivotBlock = none(BlockNumber)
|
|
stoQuLen = none(int)
|
|
ctraQuLen = none(int)
|
|
procChunks = 0
|
|
if not env.isNil:
|
|
pivotBlock = some(env.stateHeader.blockNumber)
|
|
procChunks = env.fetchAccounts.processed.chunks
|
|
stoQuLen = some(env.storageQueueTotal())
|
|
ctraQuLen = some(env.fetchContracts.len)
|
|
if 0 < ctx.pool.beaconHeader.blockNumber:
|
|
beaconBlock = some(ctx.pool.beaconHeader.blockNumber)
|
|
|
|
TickerSnapStats(
|
|
beaconBlock: beaconBlock,
|
|
pivotBlock: pivotBlock,
|
|
nQueues: ctx.pool.pivotTable.len,
|
|
nAccounts: meanStdDev(aSum, aSqSum, count),
|
|
nSlotLists: meanStdDev(sSum, sSqSum, count),
|
|
nContracts: meanStdDev(cSum, cSqSum, count),
|
|
accountsFill: (accFill[0], accFill[1], accCoverage),
|
|
nAccountStats: procChunks,
|
|
nStorageQueue: stoQuLen,
|
|
nContractQueue: ctraQuLen)
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public functions: particular pivot
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc pivotCompleteOk*(env: SnapPivotRef): bool =
|
|
## Returns `true` iff the pivot covers a complete set of accounts ans
|
|
## storage slots.
|
|
env.fetchAccounts.processed.isFull and
|
|
env.storageQueueTotal() == 0 and
|
|
env.fetchContracts.len == 0
|
|
|
|
|
|
proc pivotMothball*(env: SnapPivotRef) =
|
|
## Clean up most of this argument `env` pivot record and mark it `archived`.
|
|
## Note that archived pivots will be checked for swapping in already known
|
|
## accounts and storage slots.
|
|
env.fetchAccounts.unprocessed.init()
|
|
|
|
# Simplify storage slots queues by resolving partial slots into full list
|
|
for kvp in env.fetchStoragePart.nextPairs:
|
|
discard env.fetchStorageFull.append(
|
|
kvp.key, SnapSlotsQueueItemRef(acckey: kvp.data.accKey))
|
|
env.fetchStoragePart.clear()
|
|
|
|
# Provide index into `fetchStorageFull`
|
|
env.storageAccounts.clear()
|
|
for kvp in env.fetchStorageFull.nextPairs:
|
|
let rc = env.storageAccounts.insert(kvp.data.accKey.to(NodeTag))
|
|
# Note that `rc.isErr` should not exist as accKey => storageRoot
|
|
if rc.isOk:
|
|
rc.value.data = kvp.key
|
|
|
|
# Finally, mark that node `archived`
|
|
env.archived = true
|
|
|
|
|
|
proc execSnapSyncAction*(
|
|
env: SnapPivotRef; # Current pivot environment
|
|
buddy: SnapBuddyRef; # Worker peer
|
|
) {.async.} =
|
|
## Execute a synchronisation run.
|
|
let
|
|
ctx = buddy.ctx
|
|
|
|
if env.savedFullPivotOk:
|
|
return # no need to do anything
|
|
|
|
block:
|
|
# Clean up storage slots queue and contracts first it becomes too large
|
|
if storageSlotsQuPrioThresh < env.storageQueueAvail():
|
|
await buddy.rangeFetchStorageSlots(env)
|
|
if buddy.ctrl.stopped or env.archived:
|
|
return
|
|
if contractsQuPrioThresh < env.fetchContracts.len:
|
|
await buddy.rangeFetchContracts(env)
|
|
if buddy.ctrl.stopped or env.archived:
|
|
return
|
|
|
|
var rangeFetchOk = true
|
|
if not env.fetchAccounts.processed.isFull:
|
|
await buddy.rangeFetchAccounts(env)
|
|
|
|
# Update 100% accounting
|
|
ctx.pivotAccountsCoverage100PcRollOver()
|
|
|
|
# Run at least one round fetching storage slosts and contracts even if
|
|
# the `archived` flag is set in order to keep the batch queue small.
|
|
if buddy.ctrl.running:
|
|
await buddy.rangeFetchStorageSlots(env)
|
|
await buddy.rangeFetchContracts(env)
|
|
else:
|
|
rangeFetchOk = false
|
|
if env.archived or (buddy.ctrl.zombie and buddy.only.errors.peerDegraded):
|
|
return
|
|
|
|
# Uncconditonally try healing if enabled.
|
|
if env.accountsHealingOk(ctx):
|
|
# Let this procedure decide whether to ditch this peer (if any.) The idea
|
|
# is that the healing process might address different peer ressources
|
|
# than the fetch procedure. So that peer might still be useful unless
|
|
# physically disconnected.
|
|
buddy.ctrl.forceRun = true
|
|
await buddy.healAccounts(env)
|
|
if env.archived or (buddy.ctrl.zombie and buddy.only.errors.peerDegraded):
|
|
return
|
|
|
|
# Some additional storage slots and contracts might have been popped up
|
|
if rangeFetchOk:
|
|
await buddy.rangeFetchStorageSlots(env)
|
|
await buddy.rangeFetchContracts(env)
|
|
if env.archived:
|
|
return
|
|
|
|
# Don't bother with storage slots healing before accounts healing takes
|
|
# place. This saves communication bandwidth. The pivot might change soon,
|
|
# anyway.
|
|
if env.accountsHealingOk(ctx):
|
|
buddy.ctrl.forceRun = true
|
|
await buddy.healStorageSlots(env)
|
|
|
|
|
|
proc saveCheckpoint*(
|
|
env: SnapPivotRef; # Current pivot environment
|
|
ctx: SnapCtxRef; # Some global context
|
|
): Result[int,HexaryError] =
|
|
## Save current sync admin data. On success, the size of the data record
|
|
## saved is returned (e.g. for logging.)
|
|
##
|
|
if env.savedFullPivotOk:
|
|
return ok(0) # no need to do anything
|
|
|
|
let fa = env.fetchAccounts
|
|
if fa.processed.isEmpty:
|
|
return err(NoAccountsYet)
|
|
|
|
if saveAccountsProcessedChunksMax < fa.processed.chunks:
|
|
return err(TooManyChunksInAccountsQueue)
|
|
|
|
if saveStorageSlotsMax < env.storageQueueTotal():
|
|
return err(TooManyQueuedStorageSlots)
|
|
|
|
if saveContactsMax < env.fetchContracts.len:
|
|
return err(TooManyQueuedContracts)
|
|
|
|
result = ctx.pool.snapDb.pivotSaveDB SnapDbPivotRegistry(
|
|
header: env.stateHeader,
|
|
nAccounts: env.nAccounts,
|
|
nSlotLists: env.nSlotLists,
|
|
processed: toSeq(env.fetchAccounts.processed.increasing)
|
|
.mapIt((it.minPt,it.maxPt)),
|
|
slotAccounts: (toSeq(env.fetchStorageFull.nextKeys) &
|
|
toSeq(env.fetchStoragePart.nextKeys)).mapIt(it.to(NodeKey)) &
|
|
toSeq(env.parkedStorage.items),
|
|
ctraAccounts: (toSeq(env.fetchContracts.nextValues)))
|
|
|
|
if result.isOk and env.pivotCompleteOk():
|
|
env.savedFullPivotOk = true
|
|
|
|
|
|
proc pivotRecoverFromCheckpoint*(
|
|
env: SnapPivotRef; # Current pivot environment
|
|
ctx: SnapCtxRef; # Global context (containing save state)
|
|
topLevel: bool; # Full data set on top level only
|
|
) =
|
|
## Recover some pivot variables and global list `coveredAccounts` from
|
|
## checkpoint data. If the argument `toplevel` is set `true`, also the
|
|
## `processed`, `unprocessed`, and the `fetchStorageFull` lists are
|
|
## initialised.
|
|
##
|
|
let recov = ctx.pool.recovery
|
|
if recov.isNil:
|
|
return
|
|
|
|
env.nAccounts = recov.state.nAccounts
|
|
env.nSlotLists = recov.state.nSlotLists
|
|
|
|
# Import processed interval
|
|
for (minPt,maxPt) in recov.state.processed:
|
|
if topLevel:
|
|
env.fetchAccounts.unprocessed.reduce NodeTagRange.new(minPt, maxPt)
|
|
discard env.fetchAccounts.processed.merge(minPt, maxPt)
|
|
discard ctx.pool.coveredAccounts.merge(minPt, maxPt)
|
|
ctx.pivotAccountsCoverage100PcRollOver() # update coverage level roll over
|
|
|
|
# Handle storage slots
|
|
let stateRoot = recov.state.header.stateRoot
|
|
for w in recov.state.slotAccounts:
|
|
let pt = NodeTagRange.new(w.to(NodeTag),w.to(NodeTag)) # => `pt.len == 1`
|
|
|
|
if 0 < env.fetchAccounts.processed.covered(pt):
|
|
# Ignoring slots that have accounts to be downloaded, anyway
|
|
let rc = ctx.pool.snapDb.getAccountsData(stateRoot, w)
|
|
if rc.isErr:
|
|
# Oops, how did that account get lost?
|
|
discard env.fetchAccounts.processed.reduce pt
|
|
env.fetchAccounts.unprocessed.merge pt
|
|
elif rc.value.storageRoot != EMPTY_ROOT_HASH:
|
|
env.storageQueueAppendFull(rc.value.storageRoot, w)
|
|
|
|
# Handle contracts
|
|
for w in recov.state.ctraAccounts:
|
|
let pt = NodeTagRange.new(w.to(NodeTag),w.to(NodeTag)) # => `pt.len == 1`
|
|
|
|
if 0 < env.fetchAccounts.processed.covered(pt):
|
|
# Ignoring contracts that have accounts to be downloaded, anyway
|
|
let rc = ctx.pool.snapDb.getAccountsData(stateRoot, w)
|
|
if rc.isErr:
|
|
# Oops, how did that account get lost?
|
|
discard env.fetchAccounts.processed.reduce pt
|
|
env.fetchAccounts.unprocessed.merge pt
|
|
elif rc.value.codeHash != EMPTY_CODE_HASH:
|
|
env.fetchContracts[rc.value.codeHash] = w
|
|
|
|
# Handle mothballed pivots for swapping in (see `pivotMothball()`)
|
|
if topLevel:
|
|
env.savedFullPivotOk = env.pivotCompleteOk()
|
|
when extraTraceMessages:
|
|
trace logTxt "recovered top level record",
|
|
pivot=env.stateHeader.blockNumber.toStr,
|
|
savedFullPivotOk=env.savedFullPivotOk,
|
|
processed=env.fetchAccounts.processed.fullPC3,
|
|
nStoQ=env.storageQueueTotal()
|
|
else:
|
|
for kvp in env.fetchStorageFull.nextPairs:
|
|
let rc = env.storageAccounts.insert(kvp.data.accKey.to(NodeTag))
|
|
if rc.isOk:
|
|
rc.value.data = kvp.key
|
|
env.archived = true
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public function, manage new peer and pivot update
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc pivotApprovePeer*(buddy: SnapBuddyRef) {.async.} =
|
|
## Approve peer and update pivot. On failure, the `buddy` will be stopped so
|
|
## it will not proceed to the next scheduler task.
|
|
let
|
|
ctx = buddy.ctx
|
|
beaconHeader = ctx.pool.beaconHeader
|
|
var
|
|
pivotHeader: BlockHeader
|
|
|
|
block:
|
|
let rc = ctx.pool.pivotTable.lastValue
|
|
if rc.isOk:
|
|
pivotHeader = rc.value.stateHeader
|
|
|
|
# Check whether the pivot needs to be updated
|
|
if pivotHeader.blockNumber+pivotBlockDistanceMin <= beaconHeader.blockNumber:
|
|
# If the entry before the previous entry is unused, then run a pool mode
|
|
# based session (which should enable a pivot table purge).
|
|
block:
|
|
let rc = ctx.pool.pivotTable.beforeLast
|
|
if rc.isOk and rc.value.data.fetchAccounts.processed.isEmpty:
|
|
ctx.poolMode = true
|
|
|
|
when extraTraceMessages:
|
|
trace logTxt "new pivot from beacon chain", peer=buddy.peer,
|
|
pivot=pivotHeader.blockNumber.toStr,
|
|
beacon=beaconHeader.blockNumber.toStr, poolMode=ctx.poolMode
|
|
|
|
discard ctx.pool.pivotTable.lruAppend(
|
|
beaconHeader.stateRoot, SnapPivotRef.init(ctx, beaconHeader),
|
|
pivotTableLruEntriesMax)
|
|
|
|
pivotHeader = beaconHeader
|
|
|
|
# Not ready yet?
|
|
if pivotHeader.blockNumber == 0:
|
|
buddy.ctrl.stopped = true
|
|
|
|
|
|
proc pivotUpdateBeaconHeaderCB*(ctx: SnapCtxRef): SyncReqNewHeadCB =
|
|
## Update beacon header. This function is intended as a call back function
|
|
## for the RPC module.
|
|
result = proc(h: BlockHeader) {.gcsafe.} =
|
|
if ctx.pool.beaconHeader.blockNumber < h.blockNumber:
|
|
# when extraTraceMessages:
|
|
# trace logTxt "external beacon info update", header=h.blockNumber.toStr
|
|
ctx.pool.beaconHeader = h
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Public function, debugging
|
|
# ------------------------------------------------------------------------------
|
|
|
|
import
|
|
db/[hexary_desc, hexary_inspect, hexary_nearby, hexary_paths,
|
|
snapdb_storage_slots]
|
|
|
|
const
|
|
pivotVerifyExtraBlurb = false # or true
|
|
inspectSuspendAfter = 10_000
|
|
inspectExtraNap = 100.milliseconds
|
|
|
|
proc pivotVerifyComplete*(
|
|
env: SnapPivotRef; # Current pivot environment
|
|
ctx: SnapCtxRef; # Some global context
|
|
inspectAccountsTrie = false; # Check for dangling links
|
|
walkAccountsDB = true; # Walk accounts db
|
|
inspectSlotsTries = true; # Check dangling links (if `walkAccountsDB`)
|
|
verifyContracts = true; # Verify that code hashes are in database
|
|
): Future[bool]
|
|
{.async,discardable.} =
|
|
## Check the database whether the pivot is complete -- not advidsed on a
|
|
## production system as the process takes a lot of ressources.
|
|
let
|
|
rootKey = env.stateHeader.stateRoot.to(NodeKey)
|
|
accFn = ctx.pool.snapDb.getAccountFn
|
|
ctraFn = ctx.pool.snapDb.getContractsFn
|
|
|
|
# Verify consistency of accounts trie database. This should not be needed
|
|
# if `walkAccountsDB` is set. In case that there is a dangling link that would
|
|
# have been detected by `hexaryInspectTrie()`, the `hexaryNearbyRight()`
|
|
# function should fail at that point as well.
|
|
if inspectAccountsTrie:
|
|
var
|
|
stats = accFn.hexaryInspectTrie(rootKey,
|
|
suspendAfter=inspectSuspendAfter,
|
|
maxDangling=1)
|
|
nVisited = stats.count
|
|
nRetryCount = 0
|
|
while stats.dangling.len == 0 and not stats.resumeCtx.isNil:
|
|
when pivotVerifyExtraBlurb:
|
|
trace logTxt "accounts db inspect ..", nVisited, nRetryCount
|
|
await sleepAsync inspectExtraNap
|
|
nRetryCount.inc
|
|
stats = accFn.hexaryInspectTrie(rootKey,
|
|
resumeCtx=stats.resumeCtx,
|
|
suspendAfter=inspectSuspendAfter,
|
|
maxDangling=1)
|
|
nVisited += stats.count
|
|
# End while
|
|
|
|
if stats.dangling.len != 0:
|
|
error logTxt "accounts trie has danglig links", nVisited, nRetryCount
|
|
return false
|
|
trace logTxt "accounts trie ok", nVisited, nRetryCount
|
|
# End `if inspectAccountsTrie`
|
|
|
|
# Visit accounts and make sense of storage slots
|
|
if walkAccountsDB:
|
|
var
|
|
nAccounts = 0
|
|
nStorages = 0
|
|
nContracts = 0
|
|
nRetryTotal = 0
|
|
nodeTag = low(NodeTag)
|
|
while true:
|
|
if (nAccounts mod inspectSuspendAfter) == 0 and 0 < nAccounts:
|
|
when pivotVerifyExtraBlurb:
|
|
trace logTxt "accounts db walk ..",
|
|
nAccounts, nStorages, nContracts, nRetryTotal,
|
|
inspectSlotsTries, verifyContracts
|
|
await sleepAsync inspectExtraNap
|
|
|
|
# Find next account key => `nodeTag`
|
|
let rc = nodeTag.hexaryPath(rootKey,accFn).hexaryNearbyRight(accFn)
|
|
if rc.isErr:
|
|
if rc.error == NearbyBeyondRange:
|
|
break # No more accounts
|
|
error logTxt "accounts db problem", nodeTag,
|
|
nAccounts, nStorages, nContracts, nRetryTotal,
|
|
inspectSlotsTries, verifyContracts, error=rc.error
|
|
return false
|
|
nodeTag = rc.value.getPartialPath.convertTo(NodeKey).to(NodeTag)
|
|
nAccounts.inc
|
|
|
|
# Decode accounts data
|
|
var accData: Account
|
|
try:
|
|
accData = rc.value.leafData.decode(Account)
|
|
except RlpError as e:
|
|
error logTxt "account data problem", nodeTag,
|
|
nAccounts, nStorages, nContracts, nRetryTotal,
|
|
inspectSlotsTries, verifyContracts, name=($e.name), msg=(e.msg)
|
|
return false
|
|
|
|
# Check for storage slots for this account
|
|
if accData.storageRoot != EMPTY_ROOT_HASH:
|
|
nStorages.inc
|
|
if inspectSlotsTries:
|
|
let
|
|
slotFn = ctx.pool.snapDb.getStorageSlotsFn(nodeTag.to(NodeKey))
|
|
stoKey = accData.storageRoot.to(NodeKey)
|
|
var
|
|
stats = slotFn.hexaryInspectTrie(stoKey,
|
|
suspendAfter=inspectSuspendAfter,
|
|
maxDangling=1)
|
|
nVisited = stats.count
|
|
nRetryCount = 0
|
|
while stats.dangling.len == 0 and not stats.resumeCtx.isNil:
|
|
when pivotVerifyExtraBlurb:
|
|
trace logTxt "storage slots inspect ..", nodeTag,
|
|
nAccounts, nStorages, nContracts, nRetryTotal,
|
|
inspectSlotsTries, verifyContracts, nVisited, nRetryCount
|
|
await sleepAsync inspectExtraNap
|
|
nRetryCount.inc
|
|
nRetryTotal.inc
|
|
stats = accFn.hexaryInspectTrie(stoKey,
|
|
resumeCtx=stats.resumeCtx,
|
|
suspendAfter=inspectSuspendAfter,
|
|
maxDangling=1)
|
|
nVisited += stats.count
|
|
|
|
if stats.dangling.len != 0:
|
|
error logTxt "storage slots trie has dangling link", nodeTag,
|
|
nAccounts, nStorages, nContracts, nRetryTotal,
|
|
inspectSlotsTries, nVisited, nRetryCount
|
|
return false
|
|
if nVisited == 0:
|
|
error logTxt "storage slots trie is empty", nodeTag,
|
|
nAccounts, nStorages, nContracts, nRetryTotal,
|
|
inspectSlotsTries, verifyContracts, nVisited, nRetryCount
|
|
return false
|
|
|
|
# Check for contract codes for this account
|
|
if accData.codeHash != EMPTY_CODE_HASH:
|
|
nContracts.inc
|
|
if verifyContracts:
|
|
let codeKey = accData.codeHash.to(NodeKey)
|
|
if codeKey.to(Blob).ctraFn.len == 0:
|
|
error logTxt "Contract code missing", nodeTag,
|
|
codeKey=codeKey.to(NodeTag),
|
|
nAccounts, nStorages, nContracts, nRetryTotal,
|
|
inspectSlotsTries, verifyContracts
|
|
return false
|
|
|
|
# Set up next node key for looping
|
|
if nodeTag == high(NodeTag):
|
|
break
|
|
nodeTag = nodeTag + 1.u256
|
|
# End while
|
|
|
|
trace logTxt "accounts db walk ok",
|
|
nAccounts, nStorages, nContracts, nRetryTotal, inspectSlotsTries
|
|
# End `if walkAccountsDB`
|
|
|
|
return true
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# End
|
|
# ------------------------------------------------------------------------------
|