2022-08-04 08:04:30 +00:00
|
|
|
# Nimbus
|
2022-05-09 14:04:48 +00:00
|
|
|
#
|
|
|
|
# Copyright (c) 2021 Status Research & Development GmbH
|
|
|
|
# Licensed under either of
|
2022-05-13 16:30:10 +00:00
|
|
|
# * 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-05-09 14:04:48 +00:00
|
|
|
|
|
|
|
import
|
2022-08-04 08:04:30 +00:00
|
|
|
std/[hashes, math, options, sets],
|
2022-05-17 11:09:49 +00:00
|
|
|
chronicles,
|
|
|
|
chronos,
|
2022-08-04 08:04:30 +00:00
|
|
|
eth/[common/eth_types, p2p],
|
|
|
|
stew/[interval_set, keyed_queue],
|
2022-08-12 15:42:07 +00:00
|
|
|
../../db/select_backend,
|
2022-08-04 08:04:30 +00:00
|
|
|
".."/[protocol, sync_desc],
|
2022-09-16 07:24:12 +00:00
|
|
|
./worker/[accounts_db, fetch_accounts, ticker],
|
2022-08-04 08:04:30 +00:00
|
|
|
"."/[range_desc, worker_desc]
|
2022-05-24 08:07:39 +00:00
|
|
|
|
2022-09-16 07:24:12 +00:00
|
|
|
const
|
|
|
|
usePivot2ok = false or true
|
|
|
|
|
|
|
|
when usePivot2ok:
|
|
|
|
import ./worker/pivot2
|
|
|
|
else:
|
|
|
|
import ./worker/pivot
|
|
|
|
|
|
|
|
{.push raises: [Defect].}
|
|
|
|
|
2022-05-23 16:53:19 +00:00
|
|
|
logScope:
|
2022-08-04 08:04:30 +00:00
|
|
|
topics = "snap-sync"
|
2022-05-23 16:53:19 +00:00
|
|
|
|
2022-05-24 08:07:39 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Private helpers
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
2022-08-04 08:04:30 +00:00
|
|
|
proc hash(h: Hash256): Hash =
|
|
|
|
## Mixin for `Table` or `keyedQueue`
|
|
|
|
h.data.hash
|
2022-05-13 16:30:10 +00:00
|
|
|
|
2022-08-04 08:04:30 +00:00
|
|
|
proc meanStdDev(sum, sqSum: float; length: int): (float,float) =
|
|
|
|
if 0 < length:
|
|
|
|
result[0] = sum / length.float
|
|
|
|
result[1] = sqrt(sqSum / length.float - result[0] * result[0])
|
2022-05-17 11:09:49 +00:00
|
|
|
|
2022-08-24 13:44:18 +00:00
|
|
|
template noExceptionOops(info: static[string]; code: untyped) =
|
|
|
|
try:
|
|
|
|
code
|
|
|
|
except CatchableError as e:
|
|
|
|
raiseAssert "Inconveivable (" & info & ": name=" & $e.name & " msg=" & e.msg
|
|
|
|
except Defect as e:
|
|
|
|
raise e
|
|
|
|
except Exception as e:
|
|
|
|
raiseAssert "Ooops " & info & ": name=" & $e.name & " msg=" & e.msg
|
|
|
|
|
2022-05-17 11:09:49 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Private functions
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
2022-08-04 08:04:30 +00:00
|
|
|
proc rndNodeTag(buddy: SnapBuddyRef): NodeTag =
|
|
|
|
## Create random node tag
|
|
|
|
let
|
|
|
|
ctx = buddy.ctx
|
|
|
|
peer = buddy.peer
|
|
|
|
var data: array[32,byte]
|
|
|
|
ctx.data.rng[].generate(data)
|
|
|
|
UInt256.fromBytesBE(data).NodeTag
|
2022-06-06 13:42:08 +00:00
|
|
|
|
|
|
|
|
2022-08-04 08:04:30 +00:00
|
|
|
proc setPivotEnv(buddy: SnapBuddyRef; header: BlockHeader) =
|
|
|
|
## Activate environment for state root implied by `header` argument
|
|
|
|
let
|
|
|
|
ctx = buddy.ctx
|
|
|
|
key = header.stateRoot
|
|
|
|
rc = ctx.data.pivotTable.lruFetch(key)
|
|
|
|
if rc.isOk:
|
|
|
|
ctx.data.pivotEnv = rc.value
|
2022-06-06 13:42:08 +00:00
|
|
|
return
|
|
|
|
|
2022-08-04 08:04:30 +00:00
|
|
|
let env = SnapPivotRef(
|
|
|
|
stateHeader: header,
|
|
|
|
pivotAccount: buddy.rndNodeTag,
|
|
|
|
availAccounts: LeafRangeSet.init())
|
|
|
|
# Pre-filled with the largest possible interval
|
|
|
|
discard env.availAccounts.merge(low(NodeTag),high(NodeTag))
|
|
|
|
|
|
|
|
# Statistics
|
|
|
|
ctx.data.pivotCount.inc
|
|
|
|
|
2022-09-16 07:24:12 +00:00
|
|
|
# Activate per-state root environment (and hold previous one)
|
|
|
|
ctx.data.prevEnv = ctx.data.pivotEnv
|
2022-08-04 08:04:30 +00:00
|
|
|
ctx.data.pivotEnv = ctx.data.pivotTable.lruAppend(key, env, ctx.buddiesMax)
|
|
|
|
|
|
|
|
|
|
|
|
proc updatePivotEnv(buddy: SnapBuddyRef): bool =
|
|
|
|
## Update global state root environment from local `pivotHeader`. Choose the
|
|
|
|
## latest block number. Returns `true` if the environment was changed
|
2022-09-16 07:24:12 +00:00
|
|
|
when usePivot2ok:
|
|
|
|
let maybeHeader = buddy.data.pivot2Header
|
|
|
|
else:
|
|
|
|
let maybeHeader = buddy.data.pivotHeader
|
|
|
|
|
|
|
|
if maybeHeader.isSome:
|
2022-08-04 08:04:30 +00:00
|
|
|
let
|
2022-09-16 07:24:12 +00:00
|
|
|
peer = buddy.peer
|
2022-08-04 08:04:30 +00:00
|
|
|
ctx = buddy.ctx
|
2022-08-24 13:44:18 +00:00
|
|
|
env = ctx.data.pivotEnv
|
2022-09-16 07:24:12 +00:00
|
|
|
pivotHeader = maybeHeader.unsafeGet
|
|
|
|
newStateNumber = pivotHeader.blockNumber
|
2022-08-24 13:44:18 +00:00
|
|
|
stateNumber = if env.isNil: 0.toBlockNumber
|
|
|
|
else: env.stateHeader.blockNumber
|
2022-09-16 07:24:12 +00:00
|
|
|
stateWindow = stateNumber + maxPivotBlockWindow
|
|
|
|
|
|
|
|
block keepCurrent:
|
|
|
|
if env.isNil:
|
|
|
|
break keepCurrent # => new pivot
|
|
|
|
if stateNumber < newStateNumber:
|
|
|
|
when switchPivotAfterCoverage < 1.0:
|
|
|
|
if env.minCoverageReachedOk:
|
|
|
|
break keepCurrent # => new pivot
|
|
|
|
if stateWindow < newStateNumber:
|
|
|
|
break keepCurrent # => new pivot
|
|
|
|
if newStateNumber <= maxPivotBlockWindow:
|
|
|
|
break keepCurrent # => new pivot
|
|
|
|
# keep current
|
|
|
|
return false
|
|
|
|
|
|
|
|
# set new block
|
|
|
|
buddy.setPivotEnv(pivotHeader)
|
|
|
|
return true
|
2022-08-04 08:04:30 +00:00
|
|
|
|
|
|
|
|
|
|
|
proc tickerUpdate*(ctx: SnapCtxRef): TickerStatsUpdater =
|
|
|
|
result = proc: TickerStats =
|
|
|
|
var
|
2022-09-02 18:16:09 +00:00
|
|
|
aSum, aSqSum, uSum, uSqSum, sSum, sSqSum: float
|
2022-08-04 08:04:30 +00:00
|
|
|
count = 0
|
|
|
|
for kvp in ctx.data.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.availAccounts.freeFactor
|
|
|
|
uSum += fill
|
|
|
|
uSqSum += fill * fill
|
|
|
|
|
2022-09-02 18:16:09 +00:00
|
|
|
let sLen = kvp.data.nStorage.float
|
|
|
|
sSum += sLen
|
|
|
|
sSqSum += sLen * sLen
|
|
|
|
|
2022-08-04 08:04:30 +00:00
|
|
|
let
|
|
|
|
tabLen = ctx.data.pivotTable.len
|
|
|
|
pivotBlock = if ctx.data.pivotEnv.isNil: none(BlockNumber)
|
|
|
|
else: some(ctx.data.pivotEnv.stateHeader.blockNumber)
|
2022-08-24 13:44:18 +00:00
|
|
|
accCoverage = ctx.data.coveredAccounts.fullFactor
|
2022-09-02 18:16:09 +00:00
|
|
|
accFill = meanStdDev(uSum, uSqSum, count)
|
2022-08-24 13:44:18 +00:00
|
|
|
|
|
|
|
when snapAccountsDumpEnable:
|
|
|
|
if snapAccountsDumpCoverageStop < accCoverage:
|
|
|
|
trace " Snap proofs dump stop",
|
|
|
|
threshold=snapAccountsDumpCoverageStop, coverage=accCoverage.toPC
|
|
|
|
ctx.data.proofDumpOk = false
|
|
|
|
|
2022-08-04 08:04:30 +00:00
|
|
|
TickerStats(
|
|
|
|
pivotBlock: pivotBlock,
|
|
|
|
activeQueues: tabLen,
|
|
|
|
flushedQueues: ctx.data.pivotCount.int64 - tabLen,
|
2022-09-02 18:16:09 +00:00
|
|
|
nAccounts: meanStdDev(aSum, aSqSum, count),
|
|
|
|
nStorage: meanStdDev(sSum, sSqSum, count),
|
|
|
|
accountsFill: (accFill[0], accFill[1], accCoverage))
|
2022-06-06 13:42:08 +00:00
|
|
|
|
2022-09-16 07:24:12 +00:00
|
|
|
|
|
|
|
proc havePivot(buddy: SnapBuddyRef): bool =
|
|
|
|
## ...
|
|
|
|
if buddy.data.pivotHeader.isSome and
|
|
|
|
buddy.data.pivotHeader.get.blockNumber != 0:
|
|
|
|
|
|
|
|
# So there is a `ctx.data.pivotEnv`
|
|
|
|
when 1.0 <= switchPivotAfterCoverage:
|
|
|
|
return true
|
|
|
|
else:
|
|
|
|
let
|
|
|
|
ctx = buddy.ctx
|
|
|
|
env = ctx.data.pivotEnv
|
|
|
|
|
|
|
|
# Force fetching new pivot if coverage reached by returning `false`
|
|
|
|
if not env.minCoverageReachedOk:
|
|
|
|
|
|
|
|
# Not sure yet, so check whether coverage has been reached at all
|
|
|
|
let cov = env.availAccounts.freeFactor
|
|
|
|
if switchPivotAfterCoverage <= cov:
|
|
|
|
trace " Snap accounts coverage reached", peer,
|
|
|
|
threshold=switchPivotAfterCoverage, coverage=cov.toPC
|
|
|
|
|
|
|
|
# Need to reset pivot handlers
|
|
|
|
buddy.ctx.poolMode = true
|
|
|
|
buddy.ctx.data.runPoolHook = proc(b: SnapBuddyRef) =
|
|
|
|
b.ctx.data.pivotEnv.minCoverageReachedOk = true
|
|
|
|
when usePivot2ok:
|
|
|
|
b.pivot2Restart
|
|
|
|
else:
|
|
|
|
b.pivotRestart
|
|
|
|
return true
|
|
|
|
|
2022-06-06 13:42:08 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Public start/stop and admin functions
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
2022-08-04 08:04:30 +00:00
|
|
|
proc setup*(ctx: SnapCtxRef; tickerOK: bool): bool =
|
2022-06-06 13:42:08 +00:00
|
|
|
## Global set up
|
2022-08-04 08:04:30 +00:00
|
|
|
ctx.data.accountRangeMax = high(UInt256) div ctx.buddiesMax.u256
|
2022-08-17 07:30:11 +00:00
|
|
|
ctx.data.coveredAccounts = LeafRangeSet.init()
|
2022-08-12 15:42:07 +00:00
|
|
|
ctx.data.accountsDb =
|
|
|
|
if ctx.data.dbBackend.isNil: AccountsDbRef.init(ctx.chain.getTrieDB)
|
|
|
|
else: AccountsDbRef.init(ctx.data.dbBackend)
|
2022-08-04 08:04:30 +00:00
|
|
|
if tickerOK:
|
|
|
|
ctx.data.ticker = TickerRef.init(ctx.tickerUpdate)
|
|
|
|
else:
|
|
|
|
trace "Ticker is disabled"
|
2022-08-24 13:44:18 +00:00
|
|
|
result = true
|
|
|
|
|
|
|
|
# -----------------------
|
|
|
|
when snapAccountsDumpEnable:
|
2022-08-04 08:04:30 +00:00
|
|
|
doAssert ctx.data.proofDumpFile.open("./dump-stream.out", fmWrite)
|
|
|
|
ctx.data.proofDumpOk = true
|
|
|
|
|
|
|
|
proc release*(ctx: SnapCtxRef) =
|
2022-06-06 13:42:08 +00:00
|
|
|
## Global clean up
|
2022-08-04 08:04:30 +00:00
|
|
|
if not ctx.data.ticker.isNil:
|
|
|
|
ctx.data.ticker.stop()
|
|
|
|
ctx.data.ticker = nil
|
2022-06-16 08:58:50 +00:00
|
|
|
|
2022-08-04 08:04:30 +00:00
|
|
|
proc start*(buddy: SnapBuddyRef): bool =
|
|
|
|
## Initialise worker peer
|
|
|
|
let
|
|
|
|
ctx = buddy.ctx
|
|
|
|
peer = buddy.peer
|
|
|
|
if peer.supports(protocol.snap) and
|
|
|
|
peer.supports(protocol.eth) and
|
|
|
|
peer.state(protocol.eth).initialized:
|
2022-09-16 07:24:12 +00:00
|
|
|
when usePivot2ok:
|
|
|
|
buddy.pivot2Start()
|
|
|
|
else:
|
|
|
|
buddy.pivotStart()
|
2022-08-04 08:04:30 +00:00
|
|
|
if not ctx.data.ticker.isNil:
|
|
|
|
ctx.data.ticker.startBuddy()
|
2022-06-06 13:42:08 +00:00
|
|
|
return true
|
|
|
|
|
2022-08-04 08:04:30 +00:00
|
|
|
proc stop*(buddy: SnapBuddyRef) =
|
2022-06-06 13:42:08 +00:00
|
|
|
## Clean up this peer
|
2022-08-04 08:04:30 +00:00
|
|
|
let
|
|
|
|
ctx = buddy.ctx
|
|
|
|
peer = buddy.peer
|
|
|
|
buddy.ctrl.stopped = true
|
2022-09-16 07:24:12 +00:00
|
|
|
when usePivot2ok:
|
|
|
|
buddy.pivot2Stop()
|
|
|
|
else:
|
|
|
|
buddy.pivotStop()
|
2022-08-04 08:04:30 +00:00
|
|
|
if not ctx.data.ticker.isNil:
|
|
|
|
ctx.data.ticker.stopBuddy()
|
2022-06-06 13:42:08 +00:00
|
|
|
|
2022-05-17 11:09:49 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# Public functions
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
2022-08-04 08:04:30 +00:00
|
|
|
proc runSingle*(buddy: SnapBuddyRef) {.async.} =
|
|
|
|
## This peer worker is invoked if the peer-local flag `buddy.ctrl.multiOk`
|
|
|
|
## is set `false` which is the default mode. This flag is updated by the
|
|
|
|
## worker when deemed appropriate.
|
|
|
|
## * For all workers, there can be only one `runSingle()` function active
|
|
|
|
## simultaneously for all worker peers.
|
|
|
|
## * There will be no `runMulti()` function active for the same worker peer
|
|
|
|
## simultaneously
|
|
|
|
## * There will be no `runPool()` iterator active simultaneously.
|
2022-05-09 14:04:48 +00:00
|
|
|
##
|
2022-08-04 08:04:30 +00:00
|
|
|
## Note that this function runs in `async` mode.
|
2022-05-09 14:04:48 +00:00
|
|
|
##
|
2022-09-16 07:24:12 +00:00
|
|
|
when usePivot2ok:
|
|
|
|
#
|
|
|
|
# Run alternative pivot finder. This one harmonises difficulties of at
|
|
|
|
# least two peers. The can only be one instance active/unfinished of the
|
|
|
|
# `pivot2Exec()` functions.
|
|
|
|
#
|
|
|
|
let peer = buddy.peer
|
|
|
|
if not buddy.havePivot:
|
|
|
|
if await buddy.pivot2Exec():
|
|
|
|
discard buddy.updatePivotEnv()
|
|
|
|
else:
|
|
|
|
if not buddy.ctrl.stopped:
|
|
|
|
await sleepAsync(2.seconds)
|
|
|
|
return
|
|
|
|
|
|
|
|
buddy.ctrl.multiOk = true
|
|
|
|
|
|
|
|
trace "Snap pivot initialised", peer,
|
|
|
|
multiOk=buddy.ctrl.multiOk, runState=buddy.ctrl.state
|
|
|
|
else:
|
|
|
|
#
|
|
|
|
# The default pivot finder runs in multi mode. So there is nothing to do
|
|
|
|
# here.
|
|
|
|
#
|
|
|
|
buddy.ctrl.multiOk = true
|
2022-05-09 14:04:48 +00:00
|
|
|
|
|
|
|
|
2022-08-24 13:44:18 +00:00
|
|
|
proc runPool*(buddy: SnapBuddyRef, last: bool) =
|
2022-08-04 08:04:30 +00:00
|
|
|
## Ocne started, the function `runPool()` is called for all worker peers in
|
|
|
|
## a row (as the body of an iteration.) There will be no other worker peer
|
|
|
|
## functions activated simultaneously.
|
|
|
|
##
|
|
|
|
## This procedure is started if the global flag `buddy.ctx.poolMode` is set
|
|
|
|
## `true` (default is `false`.) It is the responsibility of the `runPool()`
|
|
|
|
## instance to reset the flag `buddy.ctx.poolMode`, typically at the first
|
2022-08-24 13:44:18 +00:00
|
|
|
## peer instance.
|
|
|
|
##
|
|
|
|
## The argument `last` is set `true` if the last entry is reached.
|
2022-08-04 08:04:30 +00:00
|
|
|
##
|
|
|
|
## Note that this function does not run in `async` mode.
|
|
|
|
##
|
2022-08-24 13:44:18 +00:00
|
|
|
let ctx = buddy.ctx
|
|
|
|
if ctx.poolMode:
|
|
|
|
ctx.poolMode = false
|
|
|
|
if not ctx.data.runPoolHook.isNil:
|
|
|
|
noExceptionOops("runPool"):
|
|
|
|
ctx.data.runPoolHook(buddy)
|
|
|
|
if last:
|
|
|
|
ctx.data.runPoolHook = nil
|
2022-05-09 14:04:48 +00:00
|
|
|
|
2022-05-17 11:09:49 +00:00
|
|
|
|
2022-08-04 08:04:30 +00:00
|
|
|
proc runMulti*(buddy: SnapBuddyRef) {.async.} =
|
|
|
|
## This peer worker is invoked if the `buddy.ctrl.multiOk` flag is set
|
|
|
|
## `true` which is typically done after finishing `runSingle()`. This
|
|
|
|
## instance can be simultaneously active for all peer workers.
|
|
|
|
##
|
|
|
|
let
|
|
|
|
ctx = buddy.ctx
|
|
|
|
peer = buddy.peer
|
2022-08-24 13:44:18 +00:00
|
|
|
|
2022-09-16 07:24:12 +00:00
|
|
|
when not usePivot2ok:
|
|
|
|
if not buddy.havePivot:
|
|
|
|
await buddy.pivotExec()
|
|
|
|
if not buddy.updatePivotEnv():
|
|
|
|
return
|
2022-05-24 08:07:39 +00:00
|
|
|
|
2022-08-24 13:44:18 +00:00
|
|
|
# Ignore rest if the pivot is still acceptably covered
|
|
|
|
when switchPivotAfterCoverage < 1.0:
|
|
|
|
if ctx.data.pivotEnv.minCoverageReachedOk:
|
|
|
|
await sleepAsync(50.milliseconds)
|
|
|
|
return
|
|
|
|
|
2022-09-16 07:24:12 +00:00
|
|
|
await buddy.fetchAccounts()
|
|
|
|
|
|
|
|
if ctx.data.pivotEnv.repairState == Done:
|
2022-08-04 08:04:30 +00:00
|
|
|
buddy.ctrl.multiOk = false
|
|
|
|
buddy.data.pivotHeader = none(BlockHeader)
|
2022-05-23 16:53:19 +00:00
|
|
|
|
2022-05-17 11:09:49 +00:00
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# End
|
|
|
|
# ------------------------------------------------------------------------------
|