mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-12 21:34:33 +00:00
7bbb0f4421
When running the import, currently blocks are loaded in batches into a `seq` then passed to the importer as such. In reality, blocks are still processed one by one, so the batching does not offer any performance advantage. It does however require that the client wastes memory, up to several GB, on the block sequence while they're waiting to be processed. This PR introduces a persister that accepts these potentially large blocks one by one and at the same time removes a number of redundant / unnecessary copies, assignments and resets that were slowing down the import process in general.
353 lines
12 KiB
Nim
353 lines
12 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
|
|
std/[options, sets, strformat],
|
|
stew/assign2,
|
|
../db/ledger,
|
|
../common/[common, evmforks],
|
|
./interpreter/[op_codes, gas_costs],
|
|
./types,
|
|
./evm_errors
|
|
|
|
func forkDeterminationInfoForVMState(vmState: BaseVMState): ForkDeterminationInfo =
|
|
forkDeterminationInfo(vmState.parent.number + 1, vmState.blockCtx.timestamp)
|
|
|
|
func determineFork(vmState: BaseVMState): EVMFork =
|
|
vmState.com.toEVMFork(vmState.forkDeterminationInfoForVMState)
|
|
|
|
proc init(
|
|
self: BaseVMState;
|
|
ac: LedgerRef,
|
|
parent: Header;
|
|
blockCtx: BlockContext;
|
|
com: CommonRef;
|
|
tracer: TracerRef,
|
|
flags: set[VMFlag] = self.flags) =
|
|
## Initialisation helper
|
|
# Take care to (re)set all fields since the VMState might be recycled
|
|
self.com = com
|
|
self.stateDB = ac
|
|
self.gasPool = blockCtx.gasLimit
|
|
assign(self.parent, parent)
|
|
assign(self.blockCtx, blockCtx)
|
|
const txCtx = default(TxContext)
|
|
assign(self.txCtx, txCtx)
|
|
self.flags = flags
|
|
self.fork = self.determineFork
|
|
self.tracer = tracer
|
|
self.receipts.setLen(0)
|
|
self.cumulativeGasUsed = 0
|
|
self.gasCosts = self.fork.forkToSchedule
|
|
self.blobGasUsed = 0'u64
|
|
self.allLogs.setLen(0)
|
|
self.gasRefunded = 0
|
|
|
|
func blockCtx(header: Header): BlockContext =
|
|
BlockContext(
|
|
timestamp : header.timestamp,
|
|
gasLimit : header.gasLimit,
|
|
baseFeePerGas: header.baseFeePerGas,
|
|
prevRandao : header.prevRandao,
|
|
difficulty : header.difficulty,
|
|
coinbase : header.coinbase,
|
|
excessBlobGas: header.excessBlobGas.get(0'u64),
|
|
parentHash : header.parentHash,
|
|
)
|
|
|
|
# --------------
|
|
|
|
proc `$`*(vmState: BaseVMState): string
|
|
{.gcsafe, raises: [].} =
|
|
if vmState.isNil:
|
|
result = "nil"
|
|
else:
|
|
result = &"VMState:"&
|
|
&"\n blockNumber: {vmState.parent.number + 1}"
|
|
|
|
proc new*(
|
|
T: type BaseVMState;
|
|
parent: Header; ## parent header, account sync position
|
|
blockCtx: BlockContext;
|
|
com: CommonRef; ## block chain config
|
|
tracer: TracerRef = nil,
|
|
storeSlotHash = false): T =
|
|
## Create a new `BaseVMState` descriptor from a parent block header. This
|
|
## function internally constructs a new account state cache rooted at
|
|
## `parent.stateRoot`
|
|
##
|
|
## This `new()` constructor and its variants (see below) provide a save
|
|
## `BaseVMState` environment where the account state cache is synchronised
|
|
## with the `parent` block header.
|
|
new result
|
|
result.init(
|
|
ac = LedgerRef.init(com.db, storeSlotHash),
|
|
parent = parent,
|
|
blockCtx = blockCtx,
|
|
com = com,
|
|
tracer = tracer)
|
|
|
|
proc reinit*(self: BaseVMState; ## Object descriptor
|
|
parent: Header; ## parent header, account sync pos.
|
|
blockCtx: BlockContext;
|
|
linear: bool
|
|
): bool =
|
|
## Re-initialise state descriptor. The `LedgerRef` database is
|
|
## re-initilaise only if its `getStateRoot()` doe not point to `parent.stateRoot`,
|
|
## already. Accumulated state data are reset. When linear, we assume that
|
|
## the state recently processed the parent block.
|
|
##
|
|
## This function returns `true` unless the `LedgerRef` database could be
|
|
## queries about its `getStateRoot()`, i.e. `isTopLevelClean` evaluated `true`. If
|
|
## this function returns `false`, the function argument `self` is left
|
|
## untouched.
|
|
if not self.stateDB.isTopLevelClean:
|
|
return false
|
|
|
|
let
|
|
tracer = self.tracer
|
|
com = self.com
|
|
db = com.db
|
|
ac = if linear or self.stateDB.getStateRoot() == parent.stateRoot: self.stateDB
|
|
else: LedgerRef.init(db, self.stateDB.storeSlotHash)
|
|
flags = self.flags
|
|
self.init(
|
|
ac = ac,
|
|
parent = parent,
|
|
blockCtx = blockCtx,
|
|
com = com,
|
|
tracer = tracer,
|
|
flags = flags)
|
|
true
|
|
|
|
proc reinit*(self: BaseVMState; ## Object descriptor
|
|
parent: Header; ## parent header, account sync pos.
|
|
header: Header; ## header with tx environment data fields
|
|
linear: bool
|
|
): bool =
|
|
## Variant of `reinit()`. The `parent` argument is used to sync the accounts
|
|
## cache and the `header` is used as a container to pass the `timestamp`,
|
|
## `gasLimit`, and `fee` values.
|
|
##
|
|
## It requires the `header` argument properly initalised so that for PoA
|
|
## networks, the miner address is retrievable via `ecRecover()`.
|
|
self.reinit(
|
|
parent = parent,
|
|
blockCtx = blockCtx(header),
|
|
linear = linear
|
|
)
|
|
|
|
proc init*(
|
|
self: BaseVMState; ## Object descriptor
|
|
parent: Header; ## parent header, account sync position
|
|
header: Header; ## header with tx environment data fields
|
|
com: CommonRef; ## block chain config
|
|
tracer: TracerRef = nil,
|
|
storeSlotHash = false) =
|
|
## Variant of `new()` constructor above for in-place initalisation. The
|
|
## `parent` argument is used to sync the accounts cache and the `header`
|
|
## is used as a container to pass the `timestamp`, `gasLimit`, and `fee`
|
|
## values.
|
|
##
|
|
## It requires the `header` argument properly initalised so that for PoA
|
|
## networks, the miner address is retrievable via `ecRecover()`.
|
|
self.init(
|
|
ac = LedgerRef.init(com.db, storeSlotHash),
|
|
parent = parent,
|
|
blockCtx = blockCtx(header),
|
|
com = com,
|
|
tracer = tracer)
|
|
|
|
proc new*(
|
|
T: type BaseVMState;
|
|
parent: Header; ## parent header, account sync position
|
|
header: Header; ## header with tx environment data fields
|
|
com: CommonRef; ## block chain config
|
|
tracer: TracerRef = nil,
|
|
storeSlotHash = false): T =
|
|
## This is a variant of the `new()` constructor above where the `parent`
|
|
## argument is used to sync the accounts cache and the `header` is used
|
|
## as a container to pass the `timestamp`, `gasLimit`, and `fee` values.
|
|
##
|
|
## It requires the `header` argument properly initalised so that for PoA
|
|
## networks, the miner address is retrievable via `ecRecover()`.
|
|
new result
|
|
result.init(
|
|
parent = parent,
|
|
header = header,
|
|
com = com,
|
|
tracer = tracer,
|
|
storeSlotHash = storeSlotHash)
|
|
|
|
proc new*(
|
|
T: type BaseVMState;
|
|
header: Header; ## header with tx environment data fields
|
|
com: CommonRef; ## block chain config
|
|
tracer: TracerRef = nil,
|
|
storeSlotHash = false): EvmResult[T] =
|
|
## This is a variant of the `new()` constructor above where the field
|
|
## `header.parentHash`, is used to fetch the `parent` Header to be
|
|
## used in the `new()` variant, above.
|
|
let parent = com.db.getBlockHeader(header.parentHash).valueOr:
|
|
return err(evmErr(EvmHeaderNotFound))
|
|
|
|
ok(BaseVMState.new(
|
|
parent = parent,
|
|
header = header,
|
|
com = com,
|
|
tracer = tracer,
|
|
storeSlotHash = storeSlotHash))
|
|
|
|
proc init*(
|
|
vmState: BaseVMState;
|
|
header: Header; ## header with tx environment data fields
|
|
com: CommonRef; ## block chain config
|
|
tracer: TracerRef = nil,
|
|
storeSlotHash = false): bool =
|
|
## Variant of `new()` which does not throw an exception on a dangling
|
|
## `Header` parent hash reference.
|
|
let parent = com.db.getBlockHeader(header.parentHash).valueOr:
|
|
return false
|
|
vmState.init(
|
|
parent = parent,
|
|
header = header,
|
|
com = com,
|
|
tracer = tracer,
|
|
storeSlotHash = storeSlotHash)
|
|
return true
|
|
|
|
func coinbase*(vmState: BaseVMState): Address =
|
|
vmState.blockCtx.coinbase
|
|
|
|
func blockNumber*(vmState: BaseVMState): BlockNumber =
|
|
# it should return current block number
|
|
# and not head.number
|
|
vmState.parent.number + 1
|
|
|
|
proc proofOfStake*(vmState: BaseVMState): bool =
|
|
vmState.com.proofOfStake(Header(
|
|
number: vmState.blockNumber,
|
|
parentHash: vmState.blockCtx.parentHash,
|
|
difficulty: vmState.blockCtx.difficulty,
|
|
))
|
|
|
|
proc difficultyOrPrevRandao*(vmState: BaseVMState): UInt256 =
|
|
if vmState.proofOfStake():
|
|
# EIP-4399/EIP-3675
|
|
UInt256.fromBytesBE(vmState.blockCtx.prevRandao.data)
|
|
else:
|
|
vmState.blockCtx.difficulty
|
|
|
|
func baseFeePerGas*(vmState: BaseVMState): UInt256 =
|
|
vmState.blockCtx.baseFeePerGas.get(0.u256)
|
|
|
|
method getAncestorHash*(
|
|
vmState: BaseVMState, blockNumber: BlockNumber): Hash32 {.gcsafe, base.} =
|
|
let db = vmState.com.db
|
|
let blockHash = db.getBlockHash(blockNumber).valueOr:
|
|
return default(Hash32)
|
|
blockHash
|
|
|
|
proc readOnlyStateDB*(vmState: BaseVMState): ReadOnlyStateDB {.inline.} =
|
|
ReadOnlyStateDB(vmState.stateDB)
|
|
|
|
template mutateStateDB*(vmState: BaseVMState, body: untyped) =
|
|
block:
|
|
var db {.inject.} = vmState.stateDB
|
|
body
|
|
|
|
proc getAndClearLogEntries*(vmState: BaseVMState): seq[Log] =
|
|
vmState.stateDB.getAndClearLogEntries()
|
|
|
|
proc status*(vmState: BaseVMState): bool =
|
|
ExecutionOK in vmState.flags
|
|
|
|
proc `status=`*(vmState: BaseVMState, status: bool) =
|
|
if status: vmState.flags.incl ExecutionOK
|
|
else: vmState.flags.excl ExecutionOK
|
|
|
|
proc collectWitnessData*(vmState: BaseVMState): bool =
|
|
CollectWitnessData in vmState.flags
|
|
|
|
proc `collectWitnessData=`*(vmState: BaseVMState, status: bool) =
|
|
if status: vmState.flags.incl CollectWitnessData
|
|
else: vmState.flags.excl CollectWitnessData
|
|
|
|
func tracingEnabled*(vmState: BaseVMState): bool =
|
|
vmState.tracer.isNil.not
|
|
|
|
proc captureTxStart*(vmState: BaseVMState, gasLimit: GasInt) =
|
|
if vmState.tracingEnabled:
|
|
vmState.tracer.captureTxStart(gasLimit)
|
|
|
|
proc captureTxEnd*(vmState: BaseVMState, restGas: GasInt) =
|
|
if vmState.tracingEnabled:
|
|
vmState.tracer.captureTxEnd(restGas)
|
|
|
|
proc captureStart*(vmState: BaseVMState, comp: Computation,
|
|
sender: Address, to: Address,
|
|
create: bool, input: openArray[byte],
|
|
gasLimit: GasInt, value: UInt256) =
|
|
if vmState.tracingEnabled:
|
|
vmState.tracer.captureStart(comp, sender, to, create, input, gasLimit, value)
|
|
|
|
proc captureEnd*(vmState: BaseVMState, comp: Computation, output: openArray[byte],
|
|
gasUsed: GasInt, error: Opt[string]) =
|
|
if vmState.tracingEnabled:
|
|
vmState.tracer.captureEnd(comp, output, gasUsed, error)
|
|
|
|
proc captureEnter*(vmState: BaseVMState, comp: Computation, op: Op,
|
|
sender: Address, to: Address,
|
|
input: openArray[byte], gasLimit: GasInt,
|
|
value: UInt256) =
|
|
if vmState.tracingEnabled:
|
|
vmState.tracer.captureEnter(comp, op, sender, to, input, gasLimit, value)
|
|
|
|
proc captureExit*(vmState: BaseVMState, comp: Computation, output: openArray[byte],
|
|
gasUsed: GasInt, error: Opt[string]) =
|
|
if vmState.tracingEnabled:
|
|
vmState.tracer.captureExit(comp, output, gasUsed, error)
|
|
|
|
proc captureOpStart*(vmState: BaseVMState, comp: Computation, pc: int,
|
|
op: Op, gas: GasInt,
|
|
depth: int): int =
|
|
if vmState.tracingEnabled:
|
|
let fixed = vmState.gasCosts[op].kind == GckFixed
|
|
result = vmState.tracer.captureOpStart(comp, fixed, pc, op, gas, depth)
|
|
|
|
proc captureGasCost*(vmState: BaseVMState,
|
|
comp: Computation,
|
|
op: Op, gasCost: GasInt, gasRemaining: GasInt,
|
|
depth: int) =
|
|
let fixed = vmState.gasCosts[op].kind == GckFixed
|
|
vmState.tracer.captureGasCost(comp, fixed, op, gasCost, gasRemaining, depth)
|
|
|
|
proc captureOpEnd*(vmState: BaseVMState, comp: Computation, pc: int,
|
|
op: Op, gas: GasInt, refund: int64,
|
|
rData: openArray[byte],
|
|
depth: int, opIndex: int) =
|
|
if vmState.tracingEnabled:
|
|
let fixed = vmState.gasCosts[op].kind == GckFixed
|
|
vmState.tracer.captureOpEnd(comp, fixed, pc, op, gas, refund, rData, depth, opIndex)
|
|
|
|
proc captureFault*(vmState: BaseVMState, comp: Computation, pc: int,
|
|
op: Op, gas: GasInt, refund: int64,
|
|
rData: openArray[byte],
|
|
depth: int, error: Opt[string]) =
|
|
if vmState.tracingEnabled:
|
|
let fixed = vmState.gasCosts[op].kind == GckFixed
|
|
vmState.tracer.captureFault(comp, fixed, pc, op, gas, refund, rData, depth, error)
|
|
|
|
proc capturePrepare*(vmState: BaseVMState, comp: Computation, depth: int) =
|
|
if vmState.tracingEnabled:
|
|
vmState.tracer.capturePrepare(comp, depth)
|