nimbus-eth1/execution_chain/core/chain/persist_blocks.nim
Jacek Sieka 03010dc558
Reduce ChainRef usage (#3093)
* Reduce ChainRef usage

It's now only used in the hive node, from which it probably also should
be removed

* lint
2025-02-19 10:04:22 +07:00

208 lines
6.7 KiB
Nim

# Nimbus
# Copyright (c) 2018-2025 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, types],
../../common,
../[executor, validate],
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
com: CommonRef
flags: PersistBlockFlags
vmState: BaseVMState
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()
txFrame = p.com.db.baseTxFrame.txFrameBegin()
parent = ?txFrame.getBlockHeader(header.parentHash)
doAssert txFrame.getSavedStateBlockNumber() == parent.number
vmState.init(parent, header, p.com, txFrame, storeSlotHash = storeSlotHash)
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):
return err("Could not update VMState for new block")
ok(p.vmState)
proc dispose*(p: var Persister) =
p.vmState.ledger.txFrame.dispose()
p.vmState = nil
proc init*(T: type Persister, com: CommonRef, flags: PersistBlockFlags): T =
T(com: com, flags: flags)
proc checkpoint*(p: var Persister): Result[void, string] =
if NoValidation notin p.flags:
let stateRoot = p.vmState.ledger.txFrame.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
)
# Move in-memory state to disk
p.com.db.persist(p.vmState.ledger.txFrame)
# Get a new frame since the DB assumes ownership
p.vmState.ledger.txFrame = p.com.db.baseTxFrame().txFrameBegin()
ok()
proc persistBlock*(p: var Persister, blk: Block): Result[void, string] =
template header(): Header =
blk.header
let com = p.com
# 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)
txFrame = vmState.ledger.txFrame
# 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:
?com.validateHeaderAndKinship(blk, vmState.parent, txFrame)
# 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 = com.taskpool,
)
if NoPersistHeader notin p.flags:
let blockHash = header.blockHash()
?txFrame.persistHeaderAndSetHead(blockHash, header, com.startOfHistory)
if NoPersistTransactions notin p.flags:
txFrame.persistTransactions(header.number, header.txRoot, blk.transactions)
if NoPersistReceipts notin p.flags:
txFrame.persistReceipts(header.receiptsRoot, vmState.receipts)
if NoPersistWithdrawals notin p.flags and blk.withdrawals.isSome:
txFrame.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
txFrame.checkpoint(header.number)
assign(p.parent, header)
ok()
proc persistBlocks*(
com: CommonRef, 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(com, 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
com.syncCurrent = p.parent.number
let res = p.checkpoint()
p.dispose()
res and ok(p.stats)
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------