nimbus-eth1/nimbus/evm/state.nim
Jacek Sieka 43d93bcdab
Don't write slot hashes on import (#2564)
The reverse slot hash mechanism causes quite a bit of database traffic
but is broadly not useful except for iterating the storage of an
account, something that a validator never does (it's used by the
tracers).

This flag adds one more thing that is not stored in the database, to be
explored more comprehensively when designing full, validator and archive
modes with different pruning options in the future.

`ldb` says this is 60gb of data (!):
```
ldb --db=. --ignore_unknown_options --column_family=KvtGen approxsize
--hex --from=0x05
--to=0x05ffffffffffffffffffffffffffffffffffffffffffffff
66488353954
```
2024-08-16 08:22:51 +02:00

359 lines
13 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,
eth/keys,
../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: BlockHeader;
blockCtx: BlockContext;
com: CommonRef;
tracer: TracerRef,
flags: set[VMFlag] = self.flags) =
## Initialisation helper
assign(self.parent, parent)
self.blockCtx = blockCtx
self.gasPool = blockCtx.gasLimit
self.com = com
self.tracer = tracer
self.stateDB = ac
self.flags = flags
self.blobGasUsed = 0'u64
self.fork = self.determineFork
self.gasCosts = self.fork.forkToSchedule
func blockCtx(com: CommonRef, header: BlockHeader):
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),
)
# --------------
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: BlockHeader; ## 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, parent.stateRoot, storeSlotHash),
parent = parent,
blockCtx = blockCtx,
com = com,
tracer = tracer)
proc reinit*(self: BaseVMState; ## Object descriptor
parent: BlockHeader; ## parent header, account sync pos.
blockCtx: BlockContext;
linear: bool
): bool =
## Re-initialise state descriptor. The `LedgerRef` database is
## re-initilaise only if its `rootHash` 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 `rootHash`, i.e. `isTopLevelClean` evaluated `true`. If
## this function returns `false`, the function argument `self` is left
## untouched.
if self.stateDB.isTopLevelClean:
let
tracer = self.tracer
com = self.com
db = com.db
ac = if linear or self.stateDB.rootHash == parent.stateRoot: self.stateDB
else: LedgerRef.init(db, parent.stateRoot, self.stateDB.ac.storeSlotHash)
flags = self.flags
self[].reset
self.init(
ac = ac,
parent = parent,
blockCtx = blockCtx,
com = com,
tracer = tracer,
flags = flags)
return true
# else: false
proc reinit*(self: BaseVMState; ## Object descriptor
parent: BlockHeader; ## parent header, account sync pos.
header: BlockHeader; ## 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 = self.com.blockCtx(header),
linear = linear
)
proc reinit*(self: BaseVMState; ## Object descriptor
header: BlockHeader; ## header with tx environment data fields
): bool =
## This is a variant of the `reinit()` function above where the field
## `header.parentHash`, is used to fetch the `parent` BlockHeader to be
## used in the `update()` variant, above.
var parent: BlockHeader
self.com.db.getBlockHeader(header.parentHash, parent) and
self.reinit(
parent = parent,
header = header,
linear = false)
proc init*(
self: BaseVMState; ## Object descriptor
parent: BlockHeader; ## parent header, account sync position
header: BlockHeader; ## 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, parent.stateRoot, storeSlotHash),
parent = parent,
blockCtx = com.blockCtx(header),
com = com,
tracer = tracer)
proc new*(
T: type BaseVMState;
parent: BlockHeader; ## parent header, account sync position
header: BlockHeader; ## 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: BlockHeader; ## 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` BlockHeader to be
## used in the `new()` variant, above.
var parent: BlockHeader
if com.db.getBlockHeader(header.parentHash, parent):
ok(BaseVMState.new(
parent = parent,
header = header,
com = com,
tracer = tracer,
storeSlotHash = storeSlotHash))
else:
err(evmErr(EvmHeaderNotFound))
proc init*(
vmState: BaseVMState;
header: BlockHeader; ## 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
## `BlockHeader` parent hash reference.
var parent: BlockHeader
if com.db.getBlockHeader(header.parentHash, parent):
vmState.init(
parent = parent,
header = header,
com = com,
tracer = tracer,
storeSlotHash = storeSlotHash)
return true
func coinbase*(vmState: BaseVMState): EthAddress =
vmState.blockCtx.coinbase
func blockNumber*(vmState: BaseVMState): BlockNumber =
# it should return current block number
# and not head.number
vmState.parent.number + 1
func difficultyOrPrevRandao*(vmState: BaseVMState): UInt256 =
if vmState.com.consensus == ConsensusType.POS:
# 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): Hash256 {.gcsafe, base.} =
let db = vmState.com.db
try:
var blockHash: Hash256
if db.getBlockHash(blockNumber, blockHash):
blockHash
else:
Hash256()
except RlpError:
Hash256()
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: EthAddress, to: EthAddress,
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: EthAddress, to: EthAddress,
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)