211 lines
6.6 KiB
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
|
|
# ------------------------------------------------------------------------------
|