214 lines
7.2 KiB
Nim
214 lines
7.2 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
|
|
|
|
const
|
|
LhcStateKey = 1.beaconStateKey
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Private helpers
|
|
# ------------------------------------------------------------------------------
|
|
|
|
proc fetchSyncStateLayout(ctx: BeaconCtxRef): Opt[SyncStateLayout] =
|
|
let data = ctx.db.ctx.getKvt().get(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
|
|
kvt = ctx.db.ctx.getKvt()
|
|
stateNum = ctx.db.getSavedStateBlockNumber() # for persisting
|
|
|
|
var bn = upTo
|
|
while 0 < bn and kvt.hasKey(beaconHeaderKey(bn).toOpenArray):
|
|
discard kvt.del(beaconHeaderKey(bn).toOpenArray)
|
|
bn.dec
|
|
|
|
# Occasionallly persist the deleted headers. 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:
|
|
ctx.db.persistent(stateNum).isOkOr:
|
|
debug info & ": cannot persist deleted sync headers", error=($$error)
|
|
# So be it, stop here.
|
|
return
|
|
|
|
# Delete persistent state, there will be no use of it anymore
|
|
discard kvt.del(LhcStateKey.toOpenArray)
|
|
ctx.db.persistent(stateNum).isOkOr:
|
|
debug info & ": cannot persist deleted sync headers", error=($$error)
|
|
return
|
|
|
|
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 data = rlp.encode(ctx.layout)
|
|
ctx.db.ctx.getKvt().put(LhcStateKey.toOpenArray, data).isOkOr:
|
|
raiseAssert info & " put() failed: " & $$error
|
|
|
|
# While executing blocks there are frequent save cycles. Otherwise, an
|
|
# extra save request might help to pick up an interrupted sync session.
|
|
if ctx.db.level() == 0 and ctx.stash.len == 0:
|
|
let number = ctx.db.getSavedStateBlockNumber()
|
|
ctx.db.persistent(number).isOkOr:
|
|
raiseAssert info & " persistent() failed: " & $$error
|
|
|
|
|
|
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
|
|
txLevel = ctx.db.level()
|
|
last = first + revBlobs.len.uint64 - 1
|
|
if 0 < txLevel:
|
|
# Need to cache it because FCU has blocked writing through to disk.
|
|
for n,data in revBlobs:
|
|
ctx.stash[last - n.uint64] = data
|
|
else:
|
|
let kvt = ctx.db.ctx.getKvt()
|
|
for n,data in revBlobs:
|
|
let key = beaconHeaderKey(last - n.uint64)
|
|
kvt.put(key.toOpenArray, data).isOkOr:
|
|
raiseAssert info & ": put() failed: " & $$error
|
|
|
|
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.db.ctx.getKvt().get(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.db.ctx.getKvt().del(beaconHeaderKey(bn).toOpenArray)
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# End
|
|
# ------------------------------------------------------------------------------
|