nimbus-eth1/nimbus/vm/state.nim
jangko baf508f6ae
move stateDB from VMState to chainDB
previously, every time the VMState was created, it will also create
new stateDB, and this action will nullify the advantages of cached accounts.

the new changes will conserve the accounts cache if the executed blocks
are contiguous. if not the stateDB need to be reinited.

this changes also allow rpcCallEvm and rpcEstimateGas executed properly
using current stateDB instead of creating new one each time they are called.
2021-10-28 18:57:08 +07:00

199 lines
6.4 KiB
Nim

# Nimbus
# Copyright (c) 2018 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.
import
std/[json, macros, options, sets, strformat, tables],
../../stateless/[witness_from_tree, witness_types],
../chain_config,
../constants,
../db/[db_chain, accounts_cache],
../errors,
../forks,
../utils,
../utils/ec_recover,
./interpreter/gas_costs,
./transaction_tracer,
./types,
eth/[common, keys]
# Forward declaration
proc consensusEnginePoA*(vmState: BaseVMState): bool
proc getMinerAddress(vmState: BaseVMState): EthAddress =
if not vmState.consensusEnginePoA:
return vmState.blockHeader.coinbase
let account = vmState.blockHeader.ecRecover
if account.isErr:
let msg = "Could not recover account address: " & $account.error
raise newException(ValidationError, msg)
account.value
proc `$`*(vmState: BaseVMState): string =
if vmState.isNil:
result = "nil"
else:
result = &"VMState {vmState.name}:\n header: {vmState.blockHeader}\n chaindb: {vmState.chaindb}"
proc init*(self: BaseVMState, ac: AccountsCache, header: BlockHeader,
chainDB: BaseChainDB, tracerFlags: set[TracerFlags] = {}) =
self.prevHeaders = @[]
self.name = "BaseVM"
self.blockHeader = header
self.chaindb = chainDB
self.tracer.initTracer(tracerFlags)
self.logEntries = @[]
self.stateDB = ac
self.touchedAccounts = initHashSet[EthAddress]()
{.gcsafe.}:
self.minerAddress = self.getMinerAddress()
proc newBaseVMState*(ac: AccountsCache, header: BlockHeader,
chainDB: BaseChainDB, tracerFlags: set[TracerFlags] = {}): BaseVMState =
new result
result.init(ac, header, chainDB, tracerFlags)
proc setupTxContext*(vmState: BaseVMState, origin: EthAddress, gasPrice: GasInt, forkOverride=none(Fork)) =
## this proc will be called each time a new transaction
## is going to be executed
vmState.txOrigin = origin
vmState.txGasPrice = gasPrice
vmState.fork =
if forkOverride.isSome:
forkOverride.get
else:
vmState.chainDB.config.toFork(vmState.blockHeader.blockNumber)
vmState.gasCosts = vmState.fork.forkToSchedule
proc consensusEnginePoA*(vmState: BaseVMState): bool =
# PoA consensus engine have no reward for miner
# TODO: this need to be fixed somehow
# using `real` engine configuration
vmState.chainDB.config.poaEngine
proc updateBlockHeader*(vmState: BaseVMState, header: BlockHeader) =
vmState.blockHeader = header
vmState.touchedAccounts.clear()
vmState.selfDestructs.clear()
if EnableTracing in vmState.tracer.flags:
vmState.tracer.initTracer(vmState.tracer.flags)
vmState.logEntries = @[]
vmState.receipts = @[]
vmState.minerAddress = vmState.getMinerAddress()
vmState.cumulativeGasUsed = 0.GasInt
method blockhash*(vmState: BaseVMState): Hash256 {.base, gcsafe.} =
vmState.blockHeader.hash
method coinbase*(vmState: BaseVMState): EthAddress {.base, gcsafe.} =
vmState.minerAddress
method timestamp*(vmState: BaseVMState): EthTime {.base, gcsafe.} =
vmState.blockHeader.timestamp
method blockNumber*(vmState: BaseVMState): BlockNumber {.base, gcsafe.} =
# it should return current block number
# and not head.blockNumber
vmState.blockHeader.blockNumber
method difficulty*(vmState: BaseVMState): UInt256 {.base, gcsafe.} =
vmState.blockHeader.difficulty
method gasLimit*(vmState: BaseVMState): GasInt {.base, gcsafe.} =
vmState.blockHeader.gasLimit
method baseFee*(vmState: BaseVMState): UInt256 {.base, gcsafe.} =
vmState.blockHeader.baseFee
when defined(geth):
import db/geth_db
method getAncestorHash*(vmState: BaseVMState, blockNumber: BlockNumber): Hash256 {.base, gcsafe.} =
var ancestorDepth = vmState.blockHeader.blockNumber - blockNumber - 1
if ancestorDepth >= constants.MAX_PREV_HEADER_DEPTH:
return
if blockNumber >= vmState.blockHeader.blockNumber:
return
when defined(geth):
result = vmState.chainDB.headerHash(blockNumber.truncate(uint64))
else:
result = vmState.chainDB.getBlockHash(blockNumber)
#TODO: should we use deque here?
# someday we may revive this code when
# we already have working miner
when false:
let idx = ancestorDepth.toInt
if idx >= vmState.prevHeaders.len:
return
var header = vmState.prevHeaders[idx]
result = header.hash
proc readOnlyStateDB*(vmState: BaseVMState): ReadOnlyStateDB {.inline.} =
ReadOnlyStateDB(vmState.stateDB)
template mutateStateDB*(vmState: BaseVMState, body: untyped) =
block:
var db {.inject.} = vmState.stateDB
body
proc getTracingResult*(vmState: BaseVMState): JsonNode {.inline.} =
doAssert(EnableTracing in vmState.tracer.flags)
vmState.tracer.trace
proc getAndClearLogEntries*(vmState: BaseVMState): seq[Log] =
shallowCopy(result, vmState.logEntries)
vmState.logEntries = @[]
proc enableTracing*(vmState: BaseVMState) {.inline.} =
vmState.tracer.flags.incl EnableTracing
proc disableTracing*(vmState: BaseVMState) {.inline.} =
vmState.tracer.flags.excl EnableTracing
iterator tracedAccounts*(vmState: BaseVMState): EthAddress =
for acc in vmState.tracer.accounts:
yield acc
iterator tracedAccountsPairs*(vmState: BaseVMState): (int, EthAddress) =
var idx = 0
for acc in vmState.tracer.accounts:
yield (idx, acc)
inc idx
proc removeTracedAccounts*(vmState: BaseVMState, accounts: varargs[EthAddress]) =
for acc in accounts:
vmState.tracer.accounts.excl acc
proc status*(vmState: BaseVMState): bool {.inline.} =
ExecutionOK in vmState.flags
proc `status=`*(vmState: BaseVMState, status: bool) =
if status: vmState.flags.incl ExecutionOK
else: vmState.flags.excl ExecutionOK
proc generateWitness*(vmState: BaseVMState): bool {.inline.} =
GenerateWitness in vmState.flags
proc `generateWitness=`*(vmState: BaseVMState, status: bool) =
if status: vmState.flags.incl GenerateWitness
else: vmState.flags.excl GenerateWitness
proc buildWitness*(vmState: BaseVMState): seq[byte] =
let rootHash = vmState.stateDB.rootHash
let mkeys = vmState.stateDB.makeMultiKeys()
let flags = if vmState.fork >= FKSpurious: {wfEIP170} else: {}
# build witness from tree
var wb = initWitnessBuilder(vmState.chainDB.db, rootHash, flags)
result = wb.buildWitness(mkeys)