nimbus-eth1/nimbus/core/chain/persist_blocks.nim

211 lines
6.6 KiB
Nim

# Nimbus
# Copyright (c) 2018-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
stew/assign2,
results,
../../evm/state,
../../evm/types,
../executor,
../validate,
./chain_desc,
chronicles,
stint
when not defined(release):
import
#../../tracer,
../../utils/utils
export results
type
PersistBlockFlag* = enum
NoValidation # Disable chunk state root validation
NoFullValidation # Disable per-block validation
NoPersistHeader
NoPersistTransactions
NoPersistUncles
NoPersistWithdrawals
NoPersistReceipts
NoPersistSlotHashes
PersistBlockFlags* = set[PersistBlockFlag]
Persister* = object
c: ChainRef
flags: PersistBlockFlags
vmState: BaseVMState
dbTx: CoreDbTxRef
stats*: PersistStats
parent: Header
PersistStats* = tuple[blocks: int, txs: int, gas: GasInt]
const NoPersistBodies* = {NoPersistTransactions, NoPersistUncles, NoPersistWithdrawals}
# ------------------------------------------------------------------------------
# Private
# ------------------------------------------------------------------------------
proc getVmState(
p: var Persister, header: Header, storeSlotHash = false
): Result[BaseVMState, string] =
if p.vmState == nil:
let vmState = BaseVMState()
if not vmState.init(header, p.c.com, storeSlotHash = storeSlotHash):
return err("Could not initialise VMState")
p.vmState = vmState
else:
if header.number != p.parent.number + 1:
return err("Only linear histories supported by Persister")
if not p.vmState.reinit(p.parent, header, linear = true):
return err("Could not update VMState for new block")
ok(p.vmState)
proc dispose*(p: var Persister) =
if p.dbTx != nil:
p.dbTx.dispose()
p.dbTx = nil
proc init*(T: type Persister, c: ChainRef, flags: PersistBlockFlags): T =
T(c: c, flags: flags)
proc checkpoint*(p: var Persister): Result[void, string] =
if NoValidation notin p.flags:
let stateRoot = p.c.db.ctx.getAccounts().getStateRoot().valueOr:
return err($$error)
if p.parent.stateRoot != stateRoot:
# TODO replace logging with better error
debug "wrong state root in block",
blockNumber = p.parent.number,
blockHash = p.parent.blockHash,
parentHash = p.parent.parentHash,
expected = p.parent.stateRoot,
actual = stateRoot
return err(
"stateRoot mismatch, expect: " & $p.parent.stateRoot & ", got: " & $stateRoot
)
if p.dbTx != nil:
p.dbTx.commit()
p.dbTx = nil
# Save and record the block number before the last saved block state.
p.c.db.persistent(p.parent.number).isOkOr:
return err("Failed to save state: " & $$error)
ok()
proc persistBlock*(p: var Persister, blk: Block): Result[void, string] =
template header(): Header =
blk.header
let c = p.c
if p.dbTx == nil:
p.dbTx = p.c.db.ctx.txFrameBegin()
# Full validation means validating the state root at every block and
# performing the more expensive hash computations on the block itself, ie
# verifying that the transaction and receipts roots are valid - when not
# doing full validation, we skip these expensive checks relying instead
# on the source of the data to have performed them previously or because
# the cost of failure is low.
# TODO Figure out the right balance for header fields - in particular, if
# we receive instruction from the CL while syncing that a block is
# CL-valid, do we skip validation while "far from head"? probably yes.
# This requires performing a header-chain validation from that CL-valid
# block which the current code doesn't express.
# Also, the potential avenues for corruption should be described with
# more rigor, ie if the txroot doesn't match but everything else does,
# can the state root of the last block still be correct? Dubious, but
# what would be the consequences? We would roll back the full set of
# blocks which is fairly low-cost.
let
skipValidation = NoValidation in p.flags
vmState = ?p.getVmState(header, storeSlotHash = NoPersistSlotHashes notin p.flags)
# TODO even if we're skipping validation, we should perform basic sanity
# checks on the block and header - that fields are sanely set for the
# given hard fork and similar path-independent checks - these same
# sanity checks should be performed early in the processing pipeline no
# matter their provenance.
if not skipValidation:
?c.com.validateHeaderAndKinship(blk, vmState.parent)
# Generate receipts for storage or validation but skip them otherwise
?vmState.processBlock(
blk,
skipValidation,
skipReceipts = skipValidation and NoPersistReceipts in p.flags,
skipUncles = NoPersistUncles in p.flags,
taskpool = c.com.taskpool,
)
if NoPersistHeader notin p.flags:
let blockHash = header.blockHash()
?c.db.persistHeaderAndSetHead(blockHash, header, c.com.startOfHistory)
if NoPersistTransactions notin p.flags:
c.db.persistTransactions(header.number, header.txRoot, blk.transactions)
if NoPersistReceipts notin p.flags:
c.db.persistReceipts(header.receiptsRoot, vmState.receipts)
if NoPersistWithdrawals notin p.flags and blk.withdrawals.isSome:
c.db.persistWithdrawals(
header.withdrawalsRoot.expect("WithdrawalsRoot should be verified before"),
blk.withdrawals.get,
)
p.stats.blocks += 1
p.stats.txs += blk.transactions.len
p.stats.gas += blk.header.gasUsed
assign(p.parent, header)
ok()
proc persistBlocks*(
c: ChainRef, blocks: openArray[Block], flags: PersistBlockFlags = {}
): Result[PersistStats, string] =
# Run the VM here
if blocks.len == 0:
debug "Nothing to do"
return ok(default(PersistStats)) # TODO not nice to return nil
var p = Persister.init(c, flags)
for blk in blocks:
p.persistBlock(blk).isOkOr:
p.dispose()
return err(error)
# update currentBlock *after* we persist it
# so the rpc return consistent result
# between eth_blockNumber and eth_syncing
c.com.syncCurrent = p.parent.number
let res = p.checkpoint()
p.dispose()
res and ok(p.stats)
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------