Jordan Hrycaj ea268e81ff
Beacon sync activation control update (#2782)
* Clarifying/commenting FCU setup condition & small fixes, comments etc.

* Update some logging

* Reorg metrics updater and activation

* Better `async` responsiveness

why:
  Block import does not allow `async` task activation while
  executing. So allow potential switch after each imported
  block (rather than a group of 32 blocks.)

* Handle resuming after previous sync followed by import

why:
  In this case the ledger state is more recent than the saved
  sync state. So this is considered a pristine sync where any
  previous sync state is forgotten.

  This fixes some assert thrown because of inconsistent internal
  state at some point.

* Provide option for clearing saved beacon sync state before starting syncer

why:
  It would resume with the last state otherwise which might be undesired
  sometimes.

  Without RPC available, the syncer typically stops and terminates with
  the canonical head larger than the base/finalised head. The latter one
  will be saved as database/ledger state and the canonical head as syncer
  target. Resuming syncing here will repeat itself.

  So clearing the syncer state can prevent from starting the syncer
  unnecessarily avoiding useless actions.

* Allow workers to request syncer shutdown from within

why:
  In one-trick-pony mode (after resuming without RPC support) the
  syncer can be stopped from within soavoiding unnecessary polling.
  In that case, the syncer can (theoretically) be restarted externally
  with `startSync()`.

* Terminate beacon sync after a single run target is reached

why:
  Stops doing useless polling (typically when there is no RPC available)

* Remove crufty comments

* Tighten state reload condition when resuming

why:
  Some pathological case might apply if the syncer is stopped while the
  distance between finalised block and head is very large and the FCU
  base becomes larger than the locked finalised state.

* Verify that finalised number from CL is at least FCU base number

why:
  The FCU base number is determined by the database, non zero if
  manually imported. The finalised number is passed via RPC by the CL
  node and will increase over time. Unless fully synced, this number
  will be pretty low.

  On the other hand, the FCU call `forkChoice()` will eventually fail
  if the `finalizedHash` argument refers to something outside the
  internal chain starting at the FCU base block.

* Remove support for completing interrupted sync without RPC support

why:
  Simplifies start/stop logic

* Rmove unused import
2024-10-28 16:22:04 +00:00

162 lines
5.3 KiB
Nim

# Nimbus - Fetch account and storage states from peers efficiently
#
# Copyright (c) 2021-2024 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/strutils,
pkg/[chronos, chronicles, eth/common, stint],
../../../../utils/prettify,
../helpers
logScope:
topics = "beacon ticker"
type
TickerStatsUpdater* = proc: TickerStats {.gcsafe, raises: [].}
## Full sync state update function
TickerStats* = object
## Full sync state (see `TickerFullStatsUpdater`)
stored*: BlockNumber
base*: BlockNumber
latest*: BlockNumber
coupler*: BlockNumber
dangling*: BlockNumber
final*: BlockNumber
head*: BlockNumber
headOk*: bool
target*: BlockNumber
targetOk*: bool
hdrUnprocTop*: BlockNumber
nHdrUnprocessed*: uint64
nHdrUnprocFragm*: int
nHdrStaged*: int
hdrStagedTop*: BlockNumber
blkUnprocTop*: BlockNumber
nBlkUnprocessed*: uint64
nBlkUnprocFragm*: int
nBlkStaged*: int
blkStagedBottom*: BlockNumber
reorg*: int
nBuddies*: int
TickerRef* = ref object
## Ticker descriptor object
started: Moment
visited: Moment
prettyPrint: proc(t: TickerRef) {.gcsafe, raises: [].}
statsCb: TickerStatsUpdater
lastStats: TickerStats
const
tickerStartDelay = chronos.milliseconds(100)
tickerLogInterval = chronos.seconds(1)
tickerLogSuppressMax = chronos.seconds(100)
# ------------------------------------------------------------------------------
# Private functions: printing ticker messages
# ------------------------------------------------------------------------------
proc tickerLogger(t: TickerRef) {.gcsafe.} =
let
data = t.statsCb()
now = Moment.now()
if data != t.lastStats or
tickerLogSuppressMax < (now - t.visited):
let
B = if data.base == data.latest: "L" else: data.base.bnStr
L = if data.latest == data.coupler: "C" else: data.latest.bnStr
C = if data.coupler == data.dangling: "D" else: data.coupler.bnStr
D = if data.dangling == data.final: "F"
elif data.dangling == data.head: "H"
else: data.dangling.bnStr
F = if data.final == data.head: "H" else: data.final.bnStr
H = if data.headOk:
if data.head == data.target: "T" else: data.head.bnStr
else:
if data.head == data.target: "?T" else: "?" & $data.head
T = if data.targetOk: data.target.bnStr else: "?" & $data.target
hS = if data.nHdrStaged == 0: "n/a"
else: data.hdrStagedTop.bnStr & "(" & $data.nHdrStaged & ")"
hU = if data.nHdrUnprocFragm == 0 and data.nHdrUnprocessed == 0: "n/a"
else: data.hdrUnprocTop.bnStr & "(" &
data.nHdrUnprocessed.toSI & "," & $data.nHdrUnprocFragm & ")"
bS = if data.nBlkStaged == 0: "n/a"
else: data.blkStagedBottom.bnStr & "(" & $data.nBlkStaged & ")"
bU = if data.nBlkUnprocFragm == 0 and data.nBlkUnprocessed == 0: "n/a"
else: data.blkUnprocTop.bnStr & "(" &
data.nBlkUnprocessed.toSI & "," & $data.nBlkUnprocFragm & ")"
rrg = data.reorg
peers = data.nBuddies
# With `int64`, there are more than 29*10^10 years range for seconds
up = (now - t.started).seconds.uint64.toSI
mem = getTotalMem().uint.toSI
t.lastStats = data
t.visited = now
if data.stored == data.base:
debug "Sync state", up, peers,
B, L, C, D, F, H, T, hS, hU, bS, bU, rrg, mem
else:
debug "Sync state", up, peers,
S=data.stored.bnStr,
B, L, C, D, F, H, T, hS, hU, bS, bU, rrg, mem
# ------------------------------------------------------------------------------
# Private functions: ticking log messages
# ------------------------------------------------------------------------------
proc setLogTicker(t: TickerRef; at: Moment) {.gcsafe.}
proc runLogTicker(t: TickerRef) {.gcsafe.} =
if not t.statsCb.isNil:
t.prettyPrint(t)
t.setLogTicker(Moment.fromNow(tickerLogInterval))
proc setLogTicker(t: TickerRef; at: Moment) =
if t.statsCb.isNil:
debug "Ticker stopped"
else:
# Store the `runLogTicker()` in a closure to avoid some garbage collection
# memory corruption issues that might occur otherwise.
discard setTimer(at, proc(ign: pointer) = runLogTicker(t))
# ------------------------------------------------------------------------------
# Public constructor and start/stop functions
# ------------------------------------------------------------------------------
proc init*(T: type TickerRef; cb: TickerStatsUpdater): T =
## Constructor
result = TickerRef(
prettyPrint: tickerLogger,
statsCb: cb,
started: Moment.now())
result.setLogTicker Moment.fromNow(tickerStartDelay)
proc destroy*(t: TickerRef) =
## Stop ticker unconditionally
if not t.isNil:
t.statsCb = TickerStatsUpdater(nil)
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------