Jordan Hrycaj c801a11190
Beacon sync mainentenance updates (#2958)
* Fix name after API change

why:
  Slipped through (debugging mode)

* Fine tuning error counters

why:
  Previous operating mode was quite blunt and considered some unnecessary
  condition. Error handling was invoked and the peer zombified where one
  could have continued working with that peer.

* Provide `kvt` table API bypassing `FC`

details:
  Not a full bypass yet

why:
  As discussed on Discord:
    Ideally, those would pass through fc as well, as thin wrappers around
    the db calls, for now - later, we probably see some policy involved
    here and at that point, fc will be responsible for arbitrage between
    sources (ie if a rpc source sends the block the syncer is syncing
    while the syncer is working, fc is there to referee


* Apply `kvt` API from `FC` to beacon sync

* No need to use extra table for persistent header cache state record

why:
  Slot zero can do. This allows deleting that table wholesale when needed
  once thatfeature is available.

* Logger updates

details:
  + Lifting main header/block op logs from `trace` to `debug`
  + Set metrics update before nano-sleep (for task switch)
2024-12-19 12:02:36 +00:00

207 lines
6.8 KiB
Nim

# Nimbus
# Copyright (c) 2023-2024 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at
# https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at
# https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed
# except according to those terms.
{.push raises:[].}
import
pkg/[chronicles, chronos],
pkg/eth/[common, rlp],
pkg/stew/[interval_set, sorted_set],
pkg/results,
"../../.."/[common, core/chain, db/storage_types],
../worker_desc,
./headers_unproc
let
LhcStateKey = 0.beaconHeaderKey
# ------------------------------------------------------------------------------
# Private helpers
# ------------------------------------------------------------------------------
template kvtNotAvailable(info: static[string]): string =
info & ": kvt table not available (locked by FC module)"
proc fetchSyncStateLayout(ctx: BeaconCtxRef): Opt[SyncStateLayout] =
let data = ctx.pool.chain.fcKvtGet(LhcStateKey.toOpenArray).valueOr:
return err()
try:
return ok(rlp.decode(data, SyncStateLayout))
except RlpError:
discard
err()
proc deleteStaleHeadersAndState(
ctx: BeaconCtxRef;
upTo: BlockNumber;
info: static[string];
) =
## Delete stale headers and state
let c = ctx.pool.chain
if not c.fcKvtAvailable():
trace kvtNotAvailable(info)
return
var bn = upTo
while 0 < bn and c.fcKvtHasKey(beaconHeaderKey(bn).toOpenArray):
discard c.fcKvtDel(beaconHeaderKey(bn).toOpenArray)
bn.dec
# Occasionallly persist the deleted headers (so that the internal DB cache
# does not grow extra large.) This will succeed if this function is called
# early enough after restart when there is no database transaction pending.
if (upTo - bn) mod 8192 == 0:
discard c.fcKvtPersistent()
# Delete persistent state record, there will be no use of it anymore
discard c.fcKvtDel(LhcStateKey.toOpenArray)
discard c.fcKvtPersistent()
if bn < upTo:
debug info & ": deleted stale sync headers", iv=BnRange.new(bn+1,upTo)
# ------------------------------------------------------------------------------
# Public functions
# ------------------------------------------------------------------------------
proc dbStoreSyncStateLayout*(ctx: BeaconCtxRef; info: static[string]) =
## Save chain layout to persistent db
let c = ctx.pool.chain
if c.fcKvtAvailable():
discard c.fcKvtPut(LhcStateKey.toOpenArray, rlp.encode(ctx.layout))
discard c.fcKvtPersistent()
else:
trace kvtNotAvailable(info)
proc dbLoadSyncStateLayout*(ctx: BeaconCtxRef; info: static[string]): bool =
## Restore chain layout from persistent db. It returns `true` if a previous
## state could be loaded, and `false` if a new state was created.
let
rc = ctx.fetchSyncStateLayout()
latest = ctx.chain.latestNumber()
# If there was a manual import after a previous sync, then saved state
# might be outdated.
if rc.isOk and
# The base number is the least record of the FCU chains/tree. So the
# finalised entry must not be smaller.
ctx.chain.baseNumber() <= rc.value.final and
# If the latest FCU number is not larger than the head, there is nothing
# to do (might also happen after a manual import.)
latest < rc.value.head and
# Can only resume a header download. Blocks need to be set up from scratch.
rc.value.lastState == collectingHeaders:
# Assign saved sync state
ctx.sst.layout = rc.value
# Add interval of unprocessed header range `(C,D)` from `README.md`
ctx.headersUnprocSet(ctx.layout.coupler+1, ctx.layout.dangling-1)
trace info & ": restored syncer state", L=latest.bnStr,
C=ctx.layout.coupler.bnStr, D=ctx.layout.dangling.bnStr,
H=ctx.layout.head.bnStr
true
else:
ctx.sst.layout = SyncStateLayout() # empty layout
if rc.isOk:
# Some stored headers might have become stale, so delete them. Even
# though it is not critical, stale headers just stay on the database
# forever occupying space without purpose. Also, delete the state record.
# After deleting headers, the state record becomes stale as well.
if rc.value.head <= latest:
# After manual import, the `latest` state might be ahead of the old
# `head` which leaves a gap `(rc.value.head,latest)` of missing headers.
# So the `deleteStaleHeadersAndState()` clean up routine needs to start
# at the `head` and work backwards.
ctx.deleteStaleHeadersAndState(rc.value.head, info)
else:
# Delete stale headers with block numbers starting at to `latest` while
# working backwards.
ctx.deleteStaleHeadersAndState(latest, info)
false
# ------------------
proc dbHeadersClear*(ctx: BeaconCtxRef) =
## Clear stashed in-memory headers
ctx.stash.clear
proc dbHeadersStash*(
ctx: BeaconCtxRef;
first: BlockNumber;
revBlobs: openArray[seq[byte]];
info: static[string];
) =
## Temporarily store header chain to persistent db (oblivious of the chain
## layout.) The headers should not be stashed if they are imepreted and
## executed on the database, already.
##
## The `revBlobs[]` arguments are passed in reverse order so that block
## numbers apply as
## ::
## #first -- revBlobs[^1]
## #(first+1) -- revBlobs[^2]
## ..
##
let
c = ctx.pool.chain
last = first + revBlobs.len.uint64 - 1
if not c.fcKvtAvailable():
# Need to cache it because FCU has blocked writing through to disk.
for n,data in revBlobs:
ctx.stash[last - n.uint64] = data
else:
for n,data in revBlobs:
let key = beaconHeaderKey(last - n.uint64)
discard c.fcKvtPut(key.toOpenArray, data)
proc dbHeaderPeek*(ctx: BeaconCtxRef; num: BlockNumber): Opt[Header] =
## Retrieve some stashed header.
# Try cache first
ctx.stash.withValue(num, val):
try:
return ok(rlp.decode(val[], Header))
except RlpError:
discard
# Use persistent storage next
let
key = beaconHeaderKey(num)
rc = ctx.pool.chain.fcKvtGet(key.toOpenArray)
if rc.isOk:
try:
return ok(rlp.decode(rc.value, Header))
except RlpError:
discard
err()
proc dbHeaderParentHash*(ctx: BeaconCtxRef; num: BlockNumber): Opt[Hash32] =
## Retrieve some stashed parent hash.
ok (? ctx.dbHeaderPeek num).parentHash
proc dbHeaderUnstash*(ctx: BeaconCtxRef; bn: BlockNumber) =
## Remove header from temporary DB list
ctx.stash.withValue(bn, _):
ctx.stash.del bn
return
discard ctx.pool.chain.fcKvtDel(beaconHeaderKey(bn).toOpenArray)
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------