Redesign of BaseVMState descriptor (#923)

* Redesign of BaseVMState descriptor

why:
  BaseVMState provides an environment for executing transactions. The
  current descriptor also provides data that cannot generally be known
  within the execution environment, e.g. the total gasUsed which is
  available not before after all transactions have finished.

  Also, the BaseVMState constructor has been replaced by a constructor
  that does not need pre-initialised input of the account database.

also:
  Previous constructor and some fields are provided with a deprecated
  annotation (producing a lot of noise.)

* Replace legacy directives in production sources

* Replace legacy directives in unit test sources

* fix CI (missing premix update)

* Remove legacy directives

* chase CI problem

* rebased

* Re-introduce 'AccountsCache' constructor optimisation for 'BaseVmState' re-initialisation

why:
  Constructing a new 'AccountsCache' descriptor can be avoided sometimes
  when the current state root is properly positioned already. Such a
  feature existed already as the update function 'initStateDB()' for the
  'BaseChanDB' where the accounts cache was linked into this desctiptor.

  The function 'initStateDB()' was removed and re-implemented into the
  'BaseVmState' constructor without optimisation. The old version was of
  restricted use as a wrong accounts cache state would unconditionally
  throw an exception rather than conceptually ask for a remedy.

  The optimised 'BaseVmState' re-initialisation has been implemented for
  the 'persistBlocks()' function.

also:
  moved some test helpers to 'test/replay' folder

* Remove unused & undocumented fields from Chain descriptor

why:
  Reduces attack surface in general & improves reading the code.
This commit is contained in:
Jordan Hrycaj 2022-01-18 16:19:32 +00:00 committed by GitHub
parent 14dd763900
commit 261c0b51a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 732 additions and 320 deletions

View File

@ -215,7 +215,9 @@ jobs:
uses: actions/cache@v2
with:
path: external/dlls-${{ matrix.target.cpu }}
key: 'dlls-v2-${{ matrix.target.cpu }}'
# according to docu, idle caches are kept for up to 7 days
# so change dlls# to force new cache contents (for some number #)
key: dlls0-${{ matrix.target.cpu }}
- name: Install DLLs dependencies (Windows)
if: >

View File

@ -82,6 +82,10 @@ proc rootHash*(ac: AccountsCache): KeccakHash =
doAssert(ac.isDirty == false)
ac.trie.rootHash
proc isTopLevelClean*(ac: AccountsCache): bool =
## Getter, returns `true` if all pending data have been commited.
not ac.isDirty and ac.savePoint.parentSavePoint.isNil
proc beginSavepoint*(ac: var AccountsCache): SavePoint =
new result
result.cache = initTable[EthAddress, RefAccount]()
@ -371,28 +375,28 @@ proc isDeadAccount*(ac: AccountsCache, address: EthAddress): bool =
else:
result = acc.isEmpty()
proc setBalance*(ac: var AccountsCache, address: EthAddress, balance: UInt256) =
proc setBalance*(ac: AccountsCache, address: EthAddress, balance: UInt256) =
let acc = ac.getAccount(address)
acc.flags.incl {IsTouched, IsAlive}
if acc.account.balance != balance:
ac.makeDirty(address).account.balance = balance
proc addBalance*(ac: var AccountsCache, address: EthAddress, delta: UInt256) {.inline.} =
proc addBalance*(ac: AccountsCache, address: EthAddress, delta: UInt256) {.inline.} =
ac.setBalance(address, ac.getBalance(address) + delta)
proc subBalance*(ac: var AccountsCache, address: EthAddress, delta: UInt256) {.inline.} =
proc subBalance*(ac: AccountsCache, address: EthAddress, delta: UInt256) {.inline.} =
ac.setBalance(address, ac.getBalance(address) - delta)
proc setNonce*(ac: var AccountsCache, address: EthAddress, nonce: AccountNonce) =
proc setNonce*(ac: AccountsCache, address: EthAddress, nonce: AccountNonce) =
let acc = ac.getAccount(address)
acc.flags.incl {IsTouched, IsAlive}
if acc.account.nonce != nonce:
ac.makeDirty(address).account.nonce = nonce
proc incNonce*(ac: var AccountsCache, address: EthAddress) {.inline.} =
proc incNonce*(ac: AccountsCache, address: EthAddress) {.inline.} =
ac.setNonce(address, ac.getNonce(address) + 1)
proc setCode*(ac: var AccountsCache, address: EthAddress, code: seq[byte]) =
proc setCode*(ac: AccountsCache, address: EthAddress, code: seq[byte]) =
let acc = ac.getAccount(address)
acc.flags.incl {IsTouched, IsAlive}
let codeHash = keccakHash(code)
@ -402,7 +406,7 @@ proc setCode*(ac: var AccountsCache, address: EthAddress, code: seq[byte]) =
acc.code = code
acc.flags.incl CodeChanged
proc setStorage*(ac: var AccountsCache, address: EthAddress, slot, value: UInt256) =
proc setStorage*(ac: AccountsCache, address: EthAddress, slot, value: UInt256) =
let acc = ac.getAccount(address)
acc.flags.incl {IsTouched, IsAlive}
let oldValue = acc.storageValue(slot, ac.db)
@ -411,20 +415,20 @@ proc setStorage*(ac: var AccountsCache, address: EthAddress, slot, value: UInt25
acc.overlayStorage[slot] = value
acc.flags.incl StorageChanged
proc clearStorage*(ac: var AccountsCache, address: EthAddress) =
proc clearStorage*(ac: AccountsCache, address: EthAddress) =
let acc = ac.getAccount(address)
acc.flags.incl {IsTouched, IsAlive}
if acc.account.storageRoot != emptyRlpHash:
# there is no point to clone the storage since we want to remove it
ac.makeDirty(address, cloneStorage = false).account.storageRoot = emptyRlpHash
proc deleteAccount*(ac: var AccountsCache, address: EthAddress) =
proc deleteAccount*(ac: AccountsCache, address: EthAddress) =
# make sure all savepoints already committed
doAssert(ac.savePoint.parentSavePoint.isNil)
let acc = ac.getAccount(address)
acc.kill()
proc persist*(ac: var AccountsCache, clearCache: bool = true) =
proc persist*(ac: AccountsCache, clearCache: bool = true) =
# make sure all savepoint already committed
doAssert(ac.savePoint.parentSavePoint.isNil)
var cleanAccounts = initHashSet[EthAddress]()

View File

@ -10,7 +10,7 @@ import
stew/[byteutils], eth/trie/[hexary, db],
eth/[common, rlp, p2p], chronicles,
".."/[errors, constants, utils, chain_config],
"."/[storage_types, accounts_cache]
"."/storage_types
type
BaseChainDB* = ref object
@ -19,7 +19,6 @@ type
networkId*: NetworkId
config* : ChainConfig
genesis* : Genesis
stateDB* : AccountsCache
# startingBlock, currentBlock, and highestBlock
# are progress indicator
@ -50,11 +49,6 @@ proc `$`*(db: BaseChainDB): string =
proc networkParams*(db: BaseChainDB): NetworkParams =
NetworkParams(config: db.config, genesis: db.genesis)
proc initStateDB*(db: BaseChainDB, stateRoot: Hash256) =
if db.stateDB.isNil.not and db.stateDB.rootHash == stateRoot:
return
db.stateDB = AccountsCache.init(db.db, stateRoot, db.pruneTrie)
proc exists*(self: BaseChainDB, hash: Hash256): bool =
self.db.contains(hash.data)

View File

@ -65,3 +65,6 @@ type
StaticContextError* = object of VMError
## State changes not allowed in static call context
VmStateError* = object of VMError
## VM state error relay

View File

@ -44,9 +44,9 @@ type
Chain* = ref object of AbstractChainDB
db: BaseChainDB
forkIds: array[ChainFork, ForkID]
blockZeroHash: KeccakHash
lastBlockHash: KeccakHash
parentStateRoot: KeccakHash
blockZeroHash: KeccakHash ##\
## Overload cache for `genesisHash()` method
extraValidation: bool ##\
## Trigger extra validation, currently within `persistBlocks()`
@ -228,14 +228,6 @@ proc verifyFrom*(c: Chain): BlockNumber =
## Getter
c.verifyFrom
proc lastBlockHash*(c: Chain): KeccakHash =
## Getter
c.lastBlockHash
proc parentStateRoot*(c: Chain): KeccakHash =
## Getter
c.parentStateRoot
proc currentBlock*(c: Chain): BlockHeader
{.gcsafe, raises: [Defect,CatchableError].} =
## currentBlock retrieves the current head block of the canonical chain.
@ -262,14 +254,6 @@ proc `verifyFrom=`*(c: Chain; verifyFrom: uint64) =
## Variant of `verifyFrom=`
c.verifyFrom = verifyFrom.u256
proc `lastBlockHash=`*(c: Chain; blockHash: KeccakHash) =
## Setter.
c.lastBlockHash = blockHash
proc `parentStateRoot=`*(c: Chain; stateRoot: KeccakHash) =
## Setter.
c.parentStateRoot = stateRoot
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------

View File

@ -11,6 +11,7 @@
import
../../db/db_chain,
../../vm_state,
../../vm_types,
../clique,
../executor,
../validate,
@ -47,19 +48,20 @@ proc persistBlocksImpl(c: Chain; headers: openarray[BlockHeader];
var cliqueState = c.clique.cliqueSave
defer: c.clique.cliqueRestore(cliqueState)
# Note that `0 < headers.len`, assured when called from `persistBlocks()`
var vmState = BaseVMState.new(headers[0], c.db)
for i in 0 ..< headers.len:
let (header, body) = (headers[i], bodies[i])
let
(header, body) = (headers[i], bodies[i])
if header.parentHash != c.lastBlockHash:
let parent = c.db.getBlockHeader(header.parentHash)
c.parentStateRoot = parent.stateRoot
# initStateDB will return the last known state
# if the stateRoot is match
c.db.initStateDB(c.parentStateRoot)
if not vmState.reinit(header):
debug "Cannot update VmState",
blockNumber = header.blockNumber,
item = i
return ValidationResult.Error
let
vmState = newBaseVMState(c.db.stateDB, header, c.db)
let
validationResult = vmState.processBlock(c.clique, header, body)
when not defined(release):
@ -102,8 +104,6 @@ proc persistBlocksImpl(c: Chain; headers: openarray[BlockHeader];
# so the rpc return consistent result
# between eth_blockNumber and eth_syncing
c.db.currentBlock = header.blockNumber
c.lastBlockHash = header.blockHash
c.parentStateRoot = header.stateRoot
transaction.commit()

View File

@ -145,24 +145,6 @@ proc processTransaction*(
fork = vmState.getForkUnsafe
vmState.processTransaction(tx, sender, header, fork)
#[
proc processTransaction*(tx: Transaction; sender: EthAddress;
vmState: BaseVMState; fork: Fork):
Result[GasInt,void]
{.gcsafe, raises: [Defect,CatchableError].} =
## Legacy variant of `processTransaction()` with `*header* derived
## from the `vmState` argument.
vmState.processTransaction(tx, sender, vmState.blockHeader, fork)
proc processTransaction*(tx: Transaction; sender: EthAddress;
vmState: BaseVMState):
Result[GasInt,void]
{.gcsafe, raises: [Defect,CatchableError].} =
## Legacy variant of `processTransaction()` with `*header* and *fork* derived
## from the `vmState` argument.
vmState.processTransaction(tx, sender, vmState.blockHeader)
#]#
# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------

View File

@ -89,10 +89,8 @@ proc traceTransaction*(chainDB: BaseChainDB, header: BlockHeader,
captureTrieDB = trieDB captureDB
networkParams = chainDB.networkParams
captureChainDB = newBaseChainDB(captureTrieDB, false, chainDB.networkId, networkParams) # prune or not prune?
captureChainDB.initStateDB(parent.stateRoot)
let
vmState = newBaseVMState(captureChainDB.stateDB, header, captureChainDB, tracerFlags + {EnableAccount})
captureFlags = tracerFlags + {EnableAccount}
vmState = BaseVMState.new(header, captureChainDB, captureFlags)
var stateDb = vmState.stateDB
@ -162,10 +160,8 @@ proc dumpBlockState*(db: BaseChainDB, header: BlockHeader, body: BlockBody, dump
networkParams = db.networkParams
captureChainDB = newBaseChainDB(captureTrieDB, false, db.networkId, networkParams)
# we only need stack dump if we want to scan for internal transaction address
captureChainDB.initStateDB(parent.stateRoot)
let
vmState = newBaseVMState(captureChainDB.stateDB, header, captureChainDB, {EnableTracing, DisableMemory, DisableStorage, EnableAccount})
captureFlags = {EnableTracing, DisableMemory, DisableStorage, EnableAccount}
vmState = BaseVMState.new(header, captureChainDB, captureFlags)
miner = vmState.coinbase()
var
@ -222,10 +218,8 @@ proc traceBlock*(chainDB: BaseChainDB, header: BlockHeader, body: BlockBody, tra
captureTrieDB = trieDB captureDB
networkParams = chainDB.networkParams
captureChainDB = newBaseChainDB(captureTrieDB, false, chainDB.networkId, networkParams)
captureChainDB.initStateDB(parent.stateRoot)
let
vmState = newBaseVMState(captureChainDB.stateDB, header, captureChainDB, tracerFlags + {EnableTracing})
captureFlags = tracerFlags + {EnableTracing}
vmState = BaseVMState.new(header, captureChainDB, captureFlags)
if header.txRoot == BLANK_ROOT_HASH: return newJNull()
doAssert(body.transactions.calcTxRoot == header.txRoot)

View File

@ -7,6 +7,7 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
std/times,
eth/common/eth_types, stint, options, stew/byteutils, chronicles,
".."/[vm_types, vm_state, vm_gas_costs, forks, constants],
".."/[db/db_chain, db/accounts_cache, transaction], eth/trie/db,
@ -72,8 +73,12 @@ proc toCallParams(vmState: BaseVMState, cd: RpcCallData,
proc rpcCallEvm*(call: RpcCallData, header: BlockHeader, chainDB: BaseChainDB): CallResult =
const globalGasCap = 0 # TODO: globalGasCap should configurable by user
let stateDB = AccountsCache.init(chainDB.db, header.stateRoot)
let vmState = newBaseVMState(stateDB, header, chainDB)
let topHeader = BlockHeader(
parentHash: header.blockHash,
timestamp: getTime().utc.toTime,
gasLimit: 0.GasInt, ## ???
fee: Uint256.none()) ## ???
let vmState = BaseVMState.new(topHeader, chainDB)
let params = toCallParams(vmState, call, globalGasCap, header.fee)
var dbTx = chainDB.db.beginTransaction()
@ -83,8 +88,12 @@ proc rpcCallEvm*(call: RpcCallData, header: BlockHeader, chainDB: BaseChainDB):
proc rpcEstimateGas*(cd: RpcCallData, header: BlockHeader, chainDB: BaseChainDB, gasCap: GasInt): GasInt =
# Binary search the gas requirement, as it may be higher than the amount used
let stateDB = AccountsCache.init(chainDB.db, header.stateRoot)
let vmState = newBaseVMState(stateDB, header, chainDB)
let topHeader = BlockHeader(
parentHash: header.blockHash,
timestamp: getTime().utc.toTime,
gasLimit: 0.GasInt, ## ???
fee: Uint256.none()) ## ???
let vmState = BaseVMState.new(topHeader, chainDB)
let fork = chainDB.config.toFork(header.blockNumber)
let txGas = gasFees[fork][GasTransaction] # txGas always 21000, use constants?
var params = toCallParams(vmState, cd, gasCap, header.fee)

View File

@ -54,16 +54,16 @@ proc setupTxContext(host: TransactionHost) =
# vmState.coinbase now unused
host.txContext.block_coinbase = vmState.minerAddress.toEvmc
# vmState.blockNumber now unused
host.txContext.block_number = (vmState.blockHeader.blockNumber
host.txContext.block_number = (vmState.blockNumber
.truncate(typeof(host.txContext.block_number)))
# vmState.timestamp now unused
host.txContext.block_timestamp = vmState.blockHeader.timestamp.toUnix
host.txContext.block_timestamp = vmState.timestamp.toUnix
# vmState.gasLimit now unused
host.txContext.block_gas_limit = vmState.blockHeader.gasLimit
host.txContext.block_gas_limit = vmState.gasLimit
# vmState.difficulty now unused
host.txContext.block_difficulty = vmState.blockHeader.difficulty.toEvmc
host.txContext.block_difficulty = vmState.difficulty.toEvmc
host.txContext.chain_id = vmState.chaindb.config.chainId.uint.u256.toEvmc
host.txContext.block_base_fee = vmState.blockHeader.baseFee.toEvmc
host.txContext.block_base_fee = vmState.baseFee.toEvmc
# Most host functions do `flip256` in `evmc_host_glue`, but due to this
# result being cached, it's better to do `flip256` when filling the cache.

View File

@ -229,7 +229,8 @@ proc setError*(c: Computation, msg: string, burnsGas = false) {.inline.} =
proc writeContract*(c: Computation) =
template withExtra(tracer: untyped, args: varargs[untyped]) =
tracer args, newContract=($c.msg.contractAddress),
blockNumber=c.vmState.blockNumber, blockHash=($c.vmState.blockHash)
blockNumber=c.vmState.blockNumber,
parentHash=($c.vmState.parent.blockHash)
# In each check below, they are guarded by `len > 0`. This includes writing
# out the code, because the account already has zero-length code to handle

View File

@ -15,10 +15,10 @@ proc hostGetTxContextImpl(ctx: Computation): nimbus_tx_context {.cdecl.} =
result.block_coinbase = vmstate.coinbase
result.block_number = vmstate.blockNumber.truncate(int64)
result.block_timestamp = vmstate.timestamp.toUnix()
result.block_gas_limit = int64(vmstate.blockHeader.gasLimit)
result.block_gas_limit = int64(vmstate.gasLimit)
result.block_difficulty = toEvmc(vmstate.difficulty)
result.chain_id = toEvmc(vmstate.chaindb.config.chainId.uint.u256)
result.block_base_fee = toEvmc(vmstate.blockHeader.baseFee)
result.block_base_fee = toEvmc(vmstate.baseFee)
proc hostGetBlockHashImpl(ctx: Computation, number: int64): Hash256 {.cdecl.} =
ctx.vmState.getAncestorHash(number.u256)

View File

@ -16,50 +16,260 @@ import
../db/[db_chain, accounts_cache],
../errors,
../forks,
../utils,
../utils/ec_recover,
../utils/[difficulty, ec_recover],
./interpreter/gas_costs,
./transaction_tracer,
./types,
eth/[common, keys]
# Forward declaration
proc consensusEnginePoA*(vmState: BaseVMState): bool
{.push raises: [Defect].}
proc getMinerAddress(vmState: BaseVMState): EthAddress =
if not vmState.consensusEnginePoA:
return vmState.blockHeader.coinbase
const
nilHash = block:
var rc: Hash256
rc
let account = vmState.blockHeader.ecRecover
template safeExecutor(info: string; code: untyped) =
try:
code
except CatchableError as e:
raise (ref CatchableError)(msg: e.msg)
except Defect as e:
raise (ref Defect)(msg: e.msg)
except:
let e = getCurrentException()
raise newException(VmStateError, info & "(): " & $e.name & " -- " & e.msg)
proc getMinerAddress(chainDB: BaseChainDB; header: BlockHeader): EthAddress
{.gcsafe, raises: [Defect,CatchableError].} =
if not chainDB.config.poaEngine:
return header.coinbase
let account = header.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] = {}) =
proc init(
self: BaseVMState;
ac: AccountsCache;
parent: BlockHeader;
timestamp: EthTime;
gasLimit: GasInt;
fee: Option[Uint256];
miner: EthAddress;
chainDB: BaseChainDB;
tracer: TransactionTracer)
{.gcsafe, raises: [Defect,CatchableError].} =
## Initialisation helper
self.prevHeaders = @[]
self.name = "BaseVM"
self.blockHeader = header
self.parent = parent
self.timestamp = timestamp
self.gasLimit = gasLimit
self.fee = fee
self.chaindb = chainDB
self.tracer.initTracer(tracerFlags)
self.tracer = tracer
self.logEntries = @[]
self.stateDB = ac
self.touchedAccounts = initHashSet[EthAddress]()
{.gcsafe.}:
self.minerAddress = self.getMinerAddress()
self.minerAddress = miner
proc newBaseVMState*(ac: AccountsCache, header: BlockHeader,
chainDB: BaseChainDB, tracerFlags: set[TracerFlags] = {}): BaseVMState =
proc init(
self: BaseVMState;
ac: AccountsCache;
parent: BlockHeader;
timestamp: EthTime;
gasLimit: GasInt;
fee: Option[Uint256];
miner: EthAddress;
chainDB: BaseChainDB;
tracerFlags: set[TracerFlags])
{.gcsafe, raises: [Defect,CatchableError].} =
var tracer: TransactionTracer
tracer.initTracer(tracerFlags)
self.init(
ac = ac,
parent = parent,
timestamp = timestamp,
gasLimit = gasLimit,
fee = fee,
miner = miner,
chainDB = chainDB,
tracer = tracer)
# --------------
proc `$`*(vmState: BaseVMState): string
{.gcsafe, raises: [Defect,ValueError].} =
if vmState.isNil:
result = "nil"
else:
result = &"VMState {vmState.name}:"&
&"\n blockNumber: {vmState.parent.blockNumber + 1}"&
&"\n chaindb: {vmState.chaindb}"
proc new*(
T: type BaseVMState;
parent: BlockHeader; ## parent header, account sync position
timestamp: EthTime; ## tx env: time stamp
gasLimit: GasInt; ## tx env: gas limit
fee: Option[Uint256]; ## tx env: optional base fee
miner: EthAddress; ## tx env: coinbase(PoW) or signer(PoA)
chainDB: BaseChainDB; ## block chain database
tracerFlags: set[TracerFlags] = {};
pruneTrie: bool = true): T
{.gcsafe, raises: [Defect,CatchableError].} =
## 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, header, chainDB, tracerFlags)
result.init(
ac = AccountsCache.init(chainDB.db, parent.stateRoot, pruneTrie),
parent = parent,
timestamp = timestamp,
gasLimit = gasLimit,
fee = fee,
miner = miner,
chainDB = chainDB,
tracerFlags = tracerFlags)
proc reinit*(self: BaseVMState; ## Object descriptor
parent: BlockHeader; ## parent header, account sync pos.
timestamp: EthTime; ## tx env: time stamp
gasLimit: GasInt; ## tx env: gas limit
fee: Option[Uint256]; ## tx env: optional base fee
miner: EthAddress; ## tx env: coinbase(PoW) or signer(PoA)
pruneTrie: bool = true): bool
{.gcsafe, raises: [Defect,CatchableError].} =
## Re-initialise state descriptor. The `AccountsCache` database is
## re-initilaise only if its `rootHash` doe not point to `parent.stateRoot`,
## already. Accumulated state data are reset.
##
## This function returns `true` unless the `AccountsCache` 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
db = self.chainDB
ac = if self.stateDB.rootHash == parent.stateRoot: self.stateDB
else: AccountsCache.init(db.db, parent.stateRoot, pruneTrie)
self[].reset
self.init(
ac = ac,
parent = parent,
timestamp = timestamp,
gasLimit = gasLimit,
fee = fee,
miner = miner,
chainDB = db,
tracer = tracer)
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
pruneTrie: bool = true): bool
{.gcsafe, raises: [Defect,CatchableError].} =
## 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,
timestamp = header.timestamp,
gasLimit = header.gasLimit,
fee = header.fee,
miner = self.chainDB.getMinerAddress(header),
pruneTrie = pruneTrie)
proc reinit*(self: BaseVMState; ## Object descriptor
header: BlockHeader; ## header with tx environment data fields
pruneTrie: bool = true): bool
{.gcsafe, raises: [Defect,CatchableError].} =
## 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.
self.reinit(
parent = self.chainDB.getBlockHeader(header.parentHash),
header = header,
pruneTrie = pruneTrie)
proc init*(
self: BaseVMState; ## Object descriptor
parent: BlockHeader; ## parent header, account sync position
header: BlockHeader; ## header with tx environment data fields
chainDB: BaseChainDB; ## block chain database
tracerFlags: set[TracerFlags] = {},
pruneTrie: bool = true)
{.gcsafe, raises: [Defect,CatchableError].} =
## 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(AccountsCache.init(chainDB.db, parent.stateRoot, pruneTrie),
parent,
header.timestamp,
header.gasLimit,
header.fee,
chainDB.getMinerAddress(header),
chainDB,
tracerFlags)
proc new*(
T: type BaseVMState;
parent: BlockHeader; ## parent header, account sync position
header: BlockHeader; ## header with tx environment data fields
chainDB: BaseChainDB; ## block chain database
tracerFlags: set[TracerFlags] = {},
pruneTrie: bool = true): T
{.gcsafe, raises: [Defect,CatchableError].} =
## 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,
chainDB = chainDB,
tracerFlags = tracerFlags,
pruneTrie = pruneTrie)
proc new*(
T: type BaseVMState;
header: BlockHeader; ## header with tx environment data fields
chainDB: BaseChainDB; ## block chain database
tracerFlags: set[TracerFlags] = {};
pruneTrie: bool = true): T
{.gcsafe, raises: [Defect,CatchableError].} =
## 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.
BaseVMState.new(
parent = chainDB.getBlockHeader(header.parentHash),
header = header,
chainDB = chainDB,
tracerFlags = tracerFlags,
pruneTrie = pruneTrie)
proc setupTxContext*(vmState: BaseVMState, origin: EthAddress, gasPrice: GasInt, forkOverride=none(Fork)) =
## this proc will be called each time a new transaction
@ -70,7 +280,7 @@ proc setupTxContext*(vmState: BaseVMState, origin: EthAddress, gasPrice: GasInt,
if forkOverride.isSome:
forkOverride.get
else:
vmState.chainDB.config.toFork(vmState.blockHeader.blockNumber)
vmState.chainDB.config.toFork(vmState.parent.blockNumber + 1)
vmState.gasCosts = vmState.fork.forkToSchedule
proc consensusEnginePoA*(vmState: BaseVMState): bool =
@ -79,48 +289,31 @@ proc consensusEnginePoA*(vmState: BaseVMState): bool =
# 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
vmState.parent.blockNumber + 1
method difficulty*(vmState: BaseVMState): UInt256 {.base, gcsafe.} =
vmState.blockHeader.difficulty
method gasLimit*(vmState: BaseVMState): GasInt {.base, gcsafe.} =
vmState.blockHeader.gasLimit
vmState.chainDB.config.calcDifficulty(vmState.timestamp, vmState.parent)
method baseFee*(vmState: BaseVMState): UInt256 {.base, gcsafe.} =
vmState.blockHeader.baseFee
if vmState.fee.isSome:
vmState.fee.get
else:
0.u256
when defined(geth):
import db/geth_db
method getAncestorHash*(vmState: BaseVMState, blockNumber: BlockNumber): Hash256 {.base, gcsafe.} =
var ancestorDepth = vmState.blockHeader.blockNumber - blockNumber - 1
method getAncestorHash*(vmState: BaseVMState, blockNumber: BlockNumber): Hash256 {.base, gcsafe, raises: [Defect,CatchableError].} =
var ancestorDepth = vmState.blockNumber - blockNumber - 1
if ancestorDepth >= constants.MAX_PREV_HEADER_DEPTH:
return
if blockNumber >= vmState.blockHeader.blockNumber:
if blockNumber >= vmState.blockNumber:
return
when defined(geth):
@ -154,10 +347,10 @@ proc getAndClearLogEntries*(vmState: BaseVMState): seq[Log] =
shallowCopy(result, vmState.logEntries)
vmState.logEntries = @[]
proc enableTracing*(vmState: BaseVMState) {.inline.} =
proc enableTracing*(vmState: BaseVMState) =
vmState.tracer.flags.incl EnableTracing
proc disableTracing*(vmState: BaseVMState) {.inline.} =
proc disableTracing*(vmState: BaseVMState) =
vmState.tracer.flags.excl EnableTracing
iterator tracedAccounts*(vmState: BaseVMState): EthAddress =
@ -174,25 +367,27 @@ proc removeTracedAccounts*(vmState: BaseVMState, accounts: varargs[EthAddress])
for acc in accounts:
vmState.tracer.accounts.excl acc
proc status*(vmState: BaseVMState): bool {.inline.} =
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 generateWitness*(vmState: BaseVMState): bool {.inline.} =
proc generateWitness*(vmState: BaseVMState): bool =
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] =
proc buildWitness*(vmState: BaseVMState): seq[byte]
{.raises: [Defect, CatchableError].} =
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)
safeExecutor("buildWitness"):
result = wb.buildWitness(mkeys)

View File

@ -34,7 +34,10 @@ type
BaseVMState* = ref object of RootObj
prevHeaders* : seq[BlockHeader]
chaindb* : BaseChainDB
blockHeader* : BlockHeader
parent* : BlockHeader
timestamp* : EthTime
gasLimit* : GasInt
fee* : Option[Uint256]
name* : string
flags* : set[VMFlag]
tracer* : TransactionTracer

View File

@ -166,7 +166,8 @@ proc setError*(c: Computation, msg: string, burnsGas = false) {.inline.} =
proc writeContract*(c: Computation) =
template withExtra(tracer: untyped, args: varargs[untyped]) =
tracer args, newContract=($c.msg.contractAddress),
blockNumber=c.vmState.blockNumber, blockHash=($c.vmState.blockHash)
blockNumber=c.vmState.blockNumber,
parentHash=($c.vmState.parent.blockHash)
# In each check below, they are guarded by `len > 0`. This includes writing
# out the code, because the account already has zero-length code to handle

View File

@ -16,49 +16,259 @@ import
../db/[db_chain, accounts_cache],
../errors,
../forks,
../utils,
../utils/ec_recover,
../utils/[difficulty, ec_recover],
./transaction_tracer,
./types,
eth/[common, keys]
# Forward declaration
proc consensusEnginePoA*(vmState: BaseVMState): bool
{.push raises: [Defect].}
proc getMinerAddress(vmState: BaseVMState): EthAddress =
if not vmState.consensusEnginePoA:
return vmState.blockHeader.coinbase
const
nilHash = block:
var rc: Hash256
rc
let account = vmState.blockHeader.ecRecover
template safeExecutor(info: string; code: untyped) =
try:
code
except CatchableError as e:
raise (ref CatchableError)(msg: e.msg)
except Defect as e:
raise (ref Defect)(msg: e.msg)
except:
let e = getCurrentException()
raise newException(VmStateError, info & "(): " & $e.name & " -- " & e.msg)
proc getMinerAddress(chainDB: BaseChainDB; header: BlockHeader): EthAddress
{.gcsafe, raises: [Defect,CatchableError].} =
if not chainDB.config.poaEngine:
return header.coinbase
let account = header.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] = {}) =
proc init(
self: BaseVMState;
ac: AccountsCache;
parent: BlockHeader;
timestamp: EthTime;
gasLimit: GasInt;
fee: Option[Uint256];
miner: EthAddress;
chainDB: BaseChainDB;
tracer: TransactionTracer)
{.gcsafe, raises: [Defect,CatchableError].} =
## Initialisation helper
self.prevHeaders = @[]
self.name = "BaseVM"
self.blockHeader = header
self.parent = parent
self.timestamp = timestamp
self.gasLimit = gasLimit
self.fee = fee
self.chaindb = chainDB
self.tracer.initTracer(tracerFlags)
self.tracer = tracer
self.logEntries = @[]
self.stateDB = ac
self.touchedAccounts = initHashSet[EthAddress]()
{.gcsafe.}:
self.minerAddress = self.getMinerAddress()
self.minerAddress = miner
proc newBaseVMState*(ac: AccountsCache, header: BlockHeader,
chainDB: BaseChainDB, tracerFlags: set[TracerFlags] = {}): BaseVMState =
proc init(
self: BaseVMState;
ac: AccountsCache;
parent: BlockHeader;
timestamp: EthTime;
gasLimit: GasInt;
fee: Option[Uint256];
miner: EthAddress;
chainDB: BaseChainDB;
tracerFlags: set[TracerFlags])
{.gcsafe, raises: [Defect,CatchableError].} =
var tracer: TransactionTracer
tracer.initTracer(tracerFlags)
self.init(
ac = ac,
parent = parent,
timestamp = timestamp,
gasLimit = gasLimit,
fee = fee,
miner = miner,
chainDB = chainDB,
tracer = tracer)
# --------------
proc `$`*(vmState: BaseVMState): string
{.gcsafe, raises: [Defect,ValueError].} =
if vmState.isNil:
result = "nil"
else:
result = &"VMState {vmState.name}:"&
&"\n blockNumber: {vmState.parent.blockNumber + 1}"&
&"\n chaindb: {vmState.chaindb}"
proc new*(
T: type BaseVMState;
parent: BlockHeader; ## parent header, account sync position
timestamp: EthTime; ## tx env: time stamp
gasLimit: GasInt; ## tx env: gas limit
fee: Option[Uint256]; ## tx env: optional base fee
miner: EthAddress; ## tx env: coinbase(PoW) or signer(PoA)
chainDB: BaseChainDB; ## block chain database
tracerFlags: set[TracerFlags] = {};
pruneTrie: bool = true): T
{.gcsafe, raises: [Defect,CatchableError].} =
## 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, header, chainDB, tracerFlags)
result.init(
ac = AccountsCache.init(chainDB.db, parent.stateRoot, pruneTrie),
parent = parent,
timestamp = timestamp,
gasLimit = gasLimit,
fee = fee,
miner = miner,
chainDB = chainDB,
tracerFlags = tracerFlags)
proc reinit*(self: BaseVMState; ## Object descriptor
parent: BlockHeader; ## parent header, account sync pos.
timestamp: EthTime; ## tx env: time stamp
gasLimit: GasInt; ## tx env: gas limit
fee: Option[Uint256]; ## tx env: optional base fee
miner: EthAddress; ## tx env: coinbase(PoW) or signer(PoA)
pruneTrie: bool = true): bool
{.gcsafe, raises: [Defect,CatchableError].} =
## Re-initialise state descriptor. The `AccountsCache` database is
## re-initilaise only if its `rootHash` doe not point to `parent.stateRoot`,
## already. Accumulated state data are reset.
##
## This function returns `true` unless the `AccountsCache` 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
db = self.chainDB
ac = if self.stateDB.rootHash == parent.stateRoot: self.stateDB
else: AccountsCache.init(db.db, parent.stateRoot, pruneTrie)
self[].reset
self.init(
ac = ac,
parent = parent,
timestamp = timestamp,
gasLimit = gasLimit,
fee = fee,
miner = miner,
chainDB = db,
tracer = tracer)
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
pruneTrie: bool = true): bool
{.gcsafe, raises: [Defect,CatchableError].} =
## 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,
timestamp = header.timestamp,
gasLimit = header.gasLimit,
fee = header.fee,
miner = self.chainDB.getMinerAddress(header),
pruneTrie = pruneTrie)
proc reinit*(self: BaseVMState; ## Object descriptor
header: BlockHeader; ## header with tx environment data fields
pruneTrie: bool = true): bool
{.gcsafe, raises: [Defect,CatchableError].} =
## 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.
self.reinit(
parent = self.chainDB.getBlockHeader(header.parentHash),
header = header,
pruneTrie = pruneTrie)
proc init*(
self: BaseVMState; ## Object descriptor
parent: BlockHeader; ## parent header, account sync position
header: BlockHeader; ## header with tx environment data fields
chainDB: BaseChainDB; ## block chain database
tracerFlags: set[TracerFlags] = {},
pruneTrie: bool = true)
{.gcsafe, raises: [Defect,CatchableError].} =
## 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(AccountsCache.init(chainDB.db, parent.stateRoot, pruneTrie),
parent,
header.timestamp,
header.gasLimit,
header.fee,
chainDB.getMinerAddress(header),
chainDB,
tracerFlags)
proc new*(
T: type BaseVMState;
parent: BlockHeader; ## parent header, account sync position
header: BlockHeader; ## header with tx environment data fields
chainDB: BaseChainDB; ## block chain database
tracerFlags: set[TracerFlags] = {},
pruneTrie: bool = true): T
{.gcsafe, raises: [Defect,CatchableError].} =
## 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,
chainDB = chainDB,
tracerFlags = tracerFlags,
pruneTrie = pruneTrie)
proc new*(
T: type BaseVMState;
header: BlockHeader; ## header with tx environment data fields
chainDB: BaseChainDB; ## block chain database
tracerFlags: set[TracerFlags] = {};
pruneTrie: bool = true): T
{.gcsafe, raises: [Defect,CatchableError].} =
## 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.
BaseVMState.new(
parent = chainDB.getBlockHeader(header.parentHash),
header = header,
chainDB = chainDB,
tracerFlags = tracerFlags,
pruneTrie = pruneTrie)
proc consensusEnginePoA*(vmState: BaseVMState): bool =
# PoA consensus engine have no reward for miner
@ -66,48 +276,31 @@ proc consensusEnginePoA*(vmState: BaseVMState): bool =
# 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
vmState.parent.blockNumber + 1
method difficulty*(vmState: BaseVMState): UInt256 {.base, gcsafe.} =
vmState.blockHeader.difficulty
method gasLimit*(vmState: BaseVMState): GasInt {.base, gcsafe.} =
vmState.blockHeader.gasLimit
vmState.chainDB.config.calcDifficulty(vmState.timestamp, vmState.parent)
method baseFee*(vmState: BaseVMState): UInt256 {.base, gcsafe.} =
vmState.blockHeader.baseFee
if vmState.fee.isSome:
vmState.fee.get
else:
0.u256
when defined(geth):
import db/geth_db
method getAncestorHash*(vmState: BaseVMState, blockNumber: BlockNumber): Hash256 {.base, gcsafe.} =
var ancestorDepth = vmState.blockHeader.blockNumber - blockNumber - 1
method getAncestorHash*(vmState: BaseVMState, blockNumber: BlockNumber): Hash256 {.base, gcsafe, raises: [Defect,CatchableError].} =
var ancestorDepth = vmState.blockNumber - blockNumber - 1
if ancestorDepth >= constants.MAX_PREV_HEADER_DEPTH:
return
if blockNumber >= vmState.blockHeader.blockNumber:
if blockNumber >= vmState.blockNumber:
return
when defined(geth):
@ -141,10 +334,10 @@ proc getAndClearLogEntries*(vmState: BaseVMState): seq[Log] =
shallowCopy(result, vmState.logEntries)
vmState.logEntries = @[]
proc enableTracing*(vmState: BaseVMState) {.inline.} =
proc enableTracing*(vmState: BaseVMState) =
vmState.tracer.flags.incl EnableTracing
proc disableTracing*(vmState: BaseVMState) {.inline.} =
proc disableTracing*(vmState: BaseVMState) =
vmState.tracer.flags.excl EnableTracing
iterator tracedAccounts*(vmState: BaseVMState): EthAddress =
@ -161,25 +354,27 @@ proc removeTracedAccounts*(vmState: BaseVMState, accounts: varargs[EthAddress])
for acc in accounts:
vmState.tracer.accounts.excl acc
proc status*(vmState: BaseVMState): bool {.inline.} =
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 generateWitness*(vmState: BaseVMState): bool {.inline.} =
proc generateWitness*(vmState: BaseVMState): bool =
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] =
proc buildWitness*(vmState: BaseVMState): seq[byte]
{.raises: [Defect, CatchableError].} =
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)
safeExecutor("buildWitness"):
result = wb.buildWitness(mkeys)

View File

@ -45,7 +45,7 @@ proc setupTxContext*(vmState: BaseVMState, origin: EthAddress, gasPrice: GasInt,
if forkOverride.isSome:
forkOverride.get
else:
vmState.chainDB.config.toFork(vmState.blockHeader.blockNumber)
vmState.chainDB.config.toFork(vmState.blockNumber)
vmState.gasCosts = vmState.fork.forkToSchedule

View File

@ -25,7 +25,10 @@ type
BaseVMState* = ref object of RootObj
prevHeaders* : seq[BlockHeader]
chaindb* : BaseChainDB
blockHeader* : BlockHeader
parent* : BlockHeader
timestamp* : EthTime
gasLimit* : GasInt
fee* : Option[Uint256]
name* : string
flags* : set[VMFlag]
tracer* : TransactionTracer

View File

@ -24,14 +24,12 @@ else:
export
vms.`$`,
vms.blockNumber,
vms.blockhash,
vms.buildWitness,
vms.coinbase,
vms.consensusEnginePoA,
vms.difficulty,
vms.disableTracing,
vms.enableTracing,
vms.gasLimit,
vms.baseFee,
vms.generateWitness,
vms.`generateWitness=`,
@ -40,14 +38,13 @@ export
vms.getTracingResult,
vms.init,
vms.mutateStateDB,
vms.newBaseVMState,
vms.new,
vms.reinit,
vms.readOnlyStateDB,
vms.removeTracedAccounts,
vms.status,
vms.`status=`,
vms.timestamp,
vms.tracedAccounts,
vms.tracedAccountsPairs,
vms.updateBlockHeader
vms.tracedAccountsPairs
# End

View File

@ -1,6 +1,6 @@
import
json, os, stint, eth/trie/db, stew/byteutils, eth/common,
../nimbus/db/[db_chain], chronicles, ../nimbus/vm_state,
../nimbus/db/[db_chain], chronicles, ../nimbus/[vm_state, vm_types],
../nimbus/p2p/executor, premixcore, prestate, ../nimbus/tracer
proc prepareBlockEnv(node: JsonNode, memoryDB: TrieDatabaseRef) =
@ -22,9 +22,8 @@ proc executeBlock(blockEnv: JsonNode, memoryDB: TrieDatabaseRef, blockNumber: Ui
let transaction = memoryDB.beginTransaction()
defer: transaction.dispose()
chainDB.initStateDB(parent.stateRoot)
let
vmState = newBaseVMState(chainDB.stateDB, header, chainDB)
vmState = BaseVMState.new(parent, header, chainDB)
validationResult = vmState.processBlockNotPoA(header, body)
if validationResult != ValidationResult.OK:

View File

@ -7,7 +7,7 @@ import
configuration, stint, eth/common,
../nimbus/db/[db_chain, select_backend, capturedb],
eth/trie/[hexary, db], ../nimbus/p2p/executor,
../nimbus/[tracer, vm_state]
../nimbus/[tracer, vm_state, vm_types]
proc dumpDebug(chainDB: BaseChainDB, blockNumber: Uint256) =
var
@ -26,10 +26,7 @@ proc dumpDebug(chainDB: BaseChainDB, blockNumber: Uint256) =
header = captureChainDB.getBlockHeader(blockNumber)
headerHash = header.blockHash
body = captureChainDB.getBlockBody(headerHash)
captureChainDB.initStateDB(parent.stateRoot)
let
vmState = newBaseVMState(captureChainDB.stateDB, header, captureChainDB)
vmState = BaseVMState.new(parent, header, captureChainDB)
captureChainDB.setHead(parent, true)
discard vmState.processBlockNotPoA(header, body)

View File

@ -5,7 +5,7 @@ import
stint, stew/byteutils, chronicles,
../nimbus/[tracer, vm_state, utils, vm_types],
../nimbus/db/[db_chain, state_db, accounts_cache],
../nimbus/db/[db_chain, state_db],
../nimbus/p2p/executor, premixcore,
"."/configuration, downloader, parser
@ -66,9 +66,9 @@ type
proc hash*(x: Uint256): Hash =
result = hash(x.toByteArrayBE)
proc newHunterVMState(ac: AccountsCache, header: BlockHeader, chainDB: BaseChainDB): HunterVMState =
proc new(T: type HunterVMState; parent, header: BlockHeader, chainDB: BaseChainDB): T =
new result
result.init(ac, header, chainDB)
result.init(parent, header, chainDB)
result.headers = initTable[BlockNumber, BlockHeader]()
method getAncestorHash*(vmState: HunterVMState, blockNumber: BlockNumber): Hash256 {.gcsafe.} =
@ -96,12 +96,11 @@ proc huntProblematicBlock(blockNumber: Uint256): ValidationResult =
chainDB = newBaseChainDB(memoryDB, false)
chainDB.setHead(parentBlock.header, true)
chainDB.initStateDB(parentBlock.header.stateRoot)
let transaction = memoryDB.beginTransaction()
defer: transaction.dispose()
let
vmState = newHunterVMState(chainDB.stateDB, thisBlock.header, chainDB)
vmState = HunterVMState.new(parentBlock.header, thisBlock.header, chainDB)
validationResult = vmState.processBlockNotPoA(thisBlock.header, thisBlock.body)
if validationResult != ValidationResult.OK:

View File

@ -5,7 +5,7 @@ import
import
../nimbus/db/[db_chain, select_backend],
../nimbus/vm_state,
../nimbus/[vm_state, vm_types],
../nimbus/p2p/executor
const
@ -26,13 +26,12 @@ proc validateBlock(chainDB: BaseChainDB, blockNumber: BlockNumber): BlockNumber
let transaction = chainDB.db.beginTransaction()
defer: transaction.dispose()
chainDB.initStateDB(parent.stateRoot)
for i in 0 ..< numBlocks:
stdout.write blockNumber + i.u256
stdout.write "\r"
let
vmState = newBaseVMState(chainDB.stateDB, headers[i], chainDB)
vmState = BaseVMState.new(parent, headers[i], chainDB)
validationResult = vmState.processBlockNotPoA(headers[i], bodies[i])
if validationResult != ValidationResult.OK:

View File

@ -213,9 +213,7 @@ proc initDatabase*(networkId = MainNet): (BaseVMState, BaseChainDB) =
difficulty: db.config.calcDifficulty(timestamp, parent),
gasLimit: 100_000
)
db.initStateDB(parent.stateRoot)
let vmState = newBaseVMState(db.stateDB, header, db)
vmState = BaseVMState.new(header, db)
(vmState, db)

View File

@ -13,6 +13,10 @@ import
stew/results,
zlib
const
lineBufStrLen = 512
outBufSize = 2048
type
GUnzip = object
mz: ZStream
@ -20,7 +24,7 @@ type
# fields used in explode()
inCache: string
inCount: uint
outBuf: array[4096,char]
outBuf: array[outBufSize,char]
outCount: uint
outDoneOK: bool
@ -107,7 +111,7 @@ proc open*(state: var GUnzip; fileName: string):
state.reset
var
strBuf = 1024.newString
strBuf = lineBufStrLen.newString
start = 10
rc = state.mz.inflateInit2(Z_RAW_DEFLATE)
doAssert rc == Z_OK

View File

@ -63,6 +63,17 @@ proc dumpGroupBeginNl*(db: BaseChainDB;
proc dumpGroupNl*(db: BaseChainDB; headers: openArray[BlockHeader];
bodies: openArray[BlockBody]): string =
## Add this below the line `transaction.commit()` in the function
## `p2p/chain.persist_blocks.persistBlocksImpl()`:
## ::
## dumpStream.write c.db.dumpGroupNl(headers,bodies)
##
## where `dumpStream` is some stream (think of `stdout`) of type `File`
## that could be initialised with
## ::
## var dumpStream: File
## dumpStream.open("./dump-stream.out", fmWrite)
##
db.dumpGroupBeginNl(headers) &
toSeq(countup(0, headers.len-1))
.mapIt(dumpGroupBlockNl(headers[it], bodies[it]))

View File

@ -16,7 +16,7 @@ import
../nimbus/transaction,
../nimbus/vm_state,
../nimbus/vm_types,
./test_clique/undump,
./replay/undump,
eth/[common, p2p, trie/db],
unittest2
@ -33,8 +33,7 @@ const
goerliCapture: CaptureSpecs = (
network: GoerliNet,
# file: "goerli68161.txt.gz",
file: "goerli51840.txt.gz",
file: "goerli68161.txt.gz",
numBlocks: 5500, # unconditionally load blocks
numTxs: 10) # txs following (not in block chain)
@ -96,10 +95,7 @@ proc importBlocks(cdb: BaseChainDB; h: seq[BlockHeader]; b: seq[BlockBody]) =
raiseAssert "persistBlocks() failed at block #" & $h[0].blockNumber
proc getVmState(cdb: BaseChainDB; number: BlockNumber): BaseVMState =
let
topHeader = cdb.getBlockHeader(number)
accounts = AccountsCache.init(cdb.db, topHeader.stateRoot, cdb.pruneTrie)
result = accounts.newBaseVMState(topHeader, cdb)
BaseVMState.new(cdb.getBlockHeader(number), cdb)
# ------------------------------------------------------------------------------
# Crash test function, finding out about how the transaction framework works ..

View File

@ -240,11 +240,13 @@ proc importBlock(tester: var Tester, chainDB: BaseChainDB,
)
deepCopy(result, preminedBlock)
# we need to reinit the state if the stateRoot appear to be different
# with the one in stateDB
chainDB.initStateDB(parentHeader.stateRoot)
let tracerFlags: set[TracerFlags] = if tester.trace: {TracerFlags.EnableTracing} else : {}
tester.vmState = newBaseVMState(chainDB.stateDB, baseHeaderForImport, chainDB, tracerFlags)
tester.vmState = BaseVMState.new(
parentHeader,
baseHeaderForImport,
chainDB,
(if tester.trace: {TracerFlags.EnableTracing} else: {}),
chainDB.pruneTrie)
let body = BlockBody(
transactions: result.txs,
@ -386,12 +388,16 @@ proc testFixture(node: JsonNode, testStatusIMPL: var TestStatus, debugMode = fal
continue
var tester = parseTester(fixture, testStatusIMPL)
var chainDB = newBaseChainDB(newMemoryDb(), pruneTrie = test_config.getConfiguration().pruning)
chainDB.initStateDB(emptyRlpHash)
setupStateDB(fixture["pre"], chainDB.stateDB)
chainDB.stateDB.persist()
check chainDB.stateDB.rootHash == tester.genesisHeader.stateRoot
let
pruneTrie = test_config.getConfiguration().pruning
chainDB = newBaseChainDB(newMemoryDb(), pruneTrie)
stateDB = AccountsCache.init(chainDB.db, emptyRlpHash, chainDB.pruneTrie)
setupStateDB(fixture["pre"], stateDB)
stateDB.persist()
check stateDB.rootHash == tester.genesisHeader.stateRoot
tester.debugMode = debugMode
tester.trace = trace

View File

@ -20,13 +20,17 @@ import
],
../nimbus/utils/ec_recover,
../nimbus/[config, utils, constants, context],
./test_clique/[pool, undump],
./test_clique/pool,
./replay/undump,
eth/[common, keys],
stint, stew/byteutils,
stint,
unittest2
const
goerliCapture = "test_clique" / "goerli51840.txt.gz"
baseDir = [".", "tests", ".." / "tests", $DirSep] # path containg repo
repoDir = ["test_clique", "replay", "status"] # alternative repos
goerliCapture = "goerli68161.txt.gz"
groupReplayTransactions = 7
# ------------------------------------------------------------------------------
@ -50,6 +54,14 @@ proc ppRow(elapsed: Duration): string =
let ms = elapsed.inMilliSeconds + 500
"x".repeat(ms div 1000)
proc findFilePath(file: string): string =
result = "?unknown?" / file
for dir in baseDir:
for repo in repoDir:
let path = dir / repo / file
if path.fileExists:
return path
# ------------------------------------------------------------------------------
# Test Runners
# ------------------------------------------------------------------------------
@ -114,7 +126,7 @@ proc runCliqueSnapshot(noisy = true; postProcessOk = false; testId: int) =
proc runGoerliReplay(noisy = true; showElapsed = false,
dir = "tests"; captureFile = goerliCapture,
captureFile = goerliCapture,
startAtBlock = 0u64; stopAfterBlock = 0u64) =
var
pool = newVoterPool()
@ -122,15 +134,19 @@ proc runGoerliReplay(noisy = true; showElapsed = false,
cInx = 0
stoppedOk = false
let
fileInfo = captureFile.splitFile.name.split(".")[0]
filePath = captureFile.findFilePath
pool.debug = noisy
pool.verifyFrom = startAtBlock
let stopThreshold = if stopAfterBlock == 0u64: uint64.high.u256
else: stopAfterBlock.u256
suite "Replay Goerli Chain":
suite &"Replay Goerli chain from {fileInfo} capture":
for w in (dir / captureFile).undumpNextGroup:
for w in filePath.undumpNextGroup:
if w[0][0].blockNumber == 0.u256:
# Verify Genesis
@ -197,7 +213,7 @@ proc runGoerliReplay(noisy = true; showElapsed = false,
proc runGoerliBaybySteps(noisy = true;
dir = "tests"; captureFile = goerliCapture,
captureFile = goerliCapture,
stopAfterBlock = 0u64) =
var
pool = newVoterPool()
@ -205,12 +221,15 @@ proc runGoerliBaybySteps(noisy = true;
pool.debug = noisy
let stopThreshold = if stopAfterBlock == 0u64: 20.u256
else: stopAfterBlock.u256
let
fileInfo = captureFile.splitFile.name.split(".")[0]
filePath = captureFile.findFilePath
stopThreshold = if stopAfterBlock == 0u64: 20.u256
else: stopAfterBlock.u256
suite "Replay Goerli Chain Transactions Single Blockwise":
suite &"Replay Goerli chain from {fileInfo} capture, single blockwise":
for w in (dir / captureFile).undumpNextGroup:
for w in filePath.undumpNextGroup:
if stoppedOk:
break
if w[0][0].blockNumber == 0.u256:
@ -238,13 +257,14 @@ proc runGoerliBaybySteps(noisy = true;
discard
proc cliqueMiscTests() =
let
prvKeyFile = "private.key".findFilePath
suite "clique misc":
test "signer func":
const
engineSigner = "658bdf435d810c91414ec09147daa6db62406379"
privateKey = "tests" / "test_clique" / "private.key"
let
engineSigner = "658bdf435d810c91414ec09147daa6db62406379"
privateKey = prvKeyFile
conf = makeConfig(@["--engine-signer:" & engineSigner, "--import-key:" & privateKey])
ctx = newEthContext()
@ -292,24 +312,32 @@ when isMainModule:
# `test_clique/indiump.dumpGroupNl()`
# placed at the end of
# `p2p/chain/persist_blocks.persistBlocks()`.
captureFile = "goerli504192.txt.gz"
captureFile = goerliCapture
#captureFile = "dump-stream.out.gz"
proc goerliReplay(noisy = true; showElapsed = true;
dir = "/status"; captureFile = captureFile;
startAtBlock = 0u64; stopAfterBlock = 0u64) =
proc goerliReplay(noisy = true;
showElapsed = true;
captureFile = captureFile;
startAtBlock = 0u64;
stopAfterBlock = 0u64) =
runGoerliReplay(
noisy = noisy, showElapsed = showElapsed,
dir = dir, captureFile = captureFile,
startAtBlock = startAtBlock, stopAfterBlock = stopAfterBlock)
noisy = noisy,
showElapsed = showElapsed,
captureFile = captureFile,
startAtBlock = startAtBlock,
stopAfterBlock = stopAfterBlock)
# local path is: nimbus-eth1/tests
let noisy = defined(debug)
#[let noisy = defined(debug)
noisy.runCliqueSnapshot(true)
noisy.runCliqueSnapshot(false)
noisy.runGoerliBaybySteps(dir = ".")
noisy.runGoerliReplay(dir = ".", startAtBlock = 31100u64)]#
noisy.runGoerliBaybySteps
noisy.runGoerliReplay(startAtBlock = 31100u64)
#noisy.goerliReplay(startAtBlock = 31100u64)
#noisy.goerliReplay(startAtBlock = 194881u64, stopAfterBlock = 198912u64)
cliqueMiscTests()
# ------------------------------------------------------------------------------

View File

@ -33,17 +33,11 @@ type
trace: bool
index: int
GST_VMState = ref object of BaseVMState
proc toBytes(x: string): seq[byte] =
result = newSeq[byte](x.len)
for i in 0..<x.len: result[i] = x[i].byte
proc newGST_VMState(ac: AccountsCache, header: BlockHeader, chainDB: BaseChainDB, tracerFlags: set[TracerFlags]): GST_VMState =
new result
result.init(ac, header, chainDB, tracerFlags)
method getAncestorHash*(vmState: GST_VMState, blockNumber: BlockNumber): Hash256 {.gcsafe.} =
method getAncestorHash*(vmState: BaseVMState; blockNumber: BlockNumber): Hash256 {.gcsafe.} =
if blockNumber >= vmState.blockNumber:
return
elif blockNumber < 0:
@ -90,11 +84,15 @@ proc dumpDebugData(tester: Tester, vmState: BaseVMState, sender: EthAddress, gas
writeFile("debug_" & tester.name & "_" & $tester.index & status & ".json", debugData.pretty())
proc testFixtureIndexes(tester: Tester, testStatusIMPL: var TestStatus) =
var tracerFlags: set[TracerFlags] = if tester.trace: {TracerFlags.EnableTracing} else : {}
let
chainDB = newBaseChainDB(newMemoryDb(), getConfiguration().pruning)
vmState = BaseVMState.new(
parent = BlockHeader(stateRoot: emptyRlpHash),
header = tester.header,
chainDB = chainDB,
tracerFlags = (if tester.trace: {TracerFlags.EnableTracing} else: {}),
pruneTrie = chainDB.pruneTrie)
var chainDB = newBaseChainDB(newMemoryDb(), pruneTrie = getConfiguration().pruning)
chainDB.initStateDB(emptyRlpHash)
var vmState = newGST_VMState(chainDB.stateDB, tester.header, chainDB, tracerFlags)
var gasUsed: GasInt
let sender = tester.tx.getSender()

View File

@ -143,7 +143,7 @@ func getHexadecimalInt*(j: JsonNode): int64 =
data = fromHex(StUInt[64], j.getStr)
result = cast[int64](data)
proc setupStateDB*(wantedState: JsonNode, stateDB: var AccountsCache) =
proc setupStateDB*(wantedState: JsonNode, stateDB: AccountsCache) =
for ac, accountData in wantedState:
let account = ethAddressFromHex(ac)
for slot, value in accountData{"storage"}:

View File

@ -10,7 +10,7 @@
import
std/[os, sequtils, strformat, strutils, times],
./test_clique/gunzip,
./replay/gunzip,
../nimbus/utils/[pow, pow/pow_cache, pow/pow_dataset],
eth/[common],
unittest2

View File

@ -12,6 +12,7 @@ import
../nimbus/[vm_computation,
vm_state,
vm_types,
forks,
constants,
vm_precompiles,
@ -24,7 +25,7 @@ import
proc initAddress(i: byte): EthAddress = result[19] = i
template doTest(fixture: JsonNode, fork: Fork, address: PrecompileAddresses): untyped =
template doTest(fixture: JsonNode; vmState: BaseVMState; fork: Fork, address: PrecompileAddresses): untyped =
for test in fixture:
let
expectedErr = test.hasKey("ExpectedError")
@ -63,33 +64,32 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) =
fork = parseEnum[Fork](fixtures["fork"].getStr.toLowerAscii)
data = fixtures["data"]
privateKey = PrivateKey.fromHex("7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d")[]
header = BlockHeader(blockNumber: 1.u256)
chainDB = newBaseChainDB(newMemoryDb())
chainDB.initStateDB(header.stateRoot)
let vmState = newBaseVMState(chainDB.stateDB, header, chainDB)
vmState = BaseVMState.new(
BlockHeader(blockNumber: 1.u256),
BlockHeader(),
newBaseChainDB(newMemoryDb()))
case toLowerAscii(label)
of "ecrecover": data.doTest(fork, paEcRecover)
of "sha256" : data.doTest(fork, paSha256)
of "ripemd" : data.doTest(fork, paRipeMd160)
of "identity" : data.doTest(fork, paIdentity)
of "modexp" : data.doTest(fork, paModExp)
of "bn256add" : data.doTest(fork, paEcAdd)
of "bn256mul" : data.doTest(fork, paEcMul)
of "ecpairing": data.doTest(fork, paPairing)
of "blake2f" : data.doTest(fork, paBlake2bf)
of "ecrecover": data.doTest(vmState, fork, paEcRecover)
of "sha256" : data.doTest(vmState, fork, paSha256)
of "ripemd" : data.doTest(vmState, fork, paRipeMd160)
of "identity" : data.doTest(vmState, fork, paIdentity)
of "modexp" : data.doTest(vmState, fork, paModExp)
of "bn256add" : data.doTest(vmState, fork, paEcAdd)
of "bn256mul" : data.doTest(vmState, fork, paEcMul)
of "ecpairing": data.doTest(vmState, fork, paPairing)
of "blake2f" : data.doTest(vmState, fork, paBlake2bf)
# EIP 2537: disabled
# reason: not included in berlin
#of "blsg1add" : data.doTest(fork, paBlsG1Add)
#of "blsg1mul" : data.doTest(fork, paBlsG1Mul)
#of "blsg1multiexp" : data.doTest(fork, paBlsG1MultiExp)
#of "blsg2add" : data.doTest(fork, paBlsG2Add)
#of "blsg2mul" : data.doTest(fork, paBlsG2Mul)
#of "blsg2multiexp": data.doTest(fork, paBlsG2MultiExp)
#of "blspairing": data.doTest(fork, paBlsPairing)
#of "blsmapg1": data.doTest(fork, paBlsMapG1)
#of "blsmapg2": data.doTest(fork, paBlsMapG2)
#of "blsg1add" : data.doTest(vmState, fork, paBlsG1Add)
#of "blsg1mul" : data.doTest(vmState, fork, paBlsG1Mul)
#of "blsg1multiexp" : data.doTest(vmState, fork, paBlsG1MultiExp)
#of "blsg2add" : data.doTest(vmState, fork, paBlsG2Add)
#of "blsg2mul" : data.doTest(vmState, fork, paBlsG2Mul)
#of "blsg2multiexp": data.doTest(vmState, fork, paBlsG2MultiExp)
#of "blspairing": data.doTest(vmState, fork, paBlsPairing)
#of "blsmapg1": data.doTest(vmState, fork, paBlsMapG1)
#of "blsmapg2": data.doTest(vmState, fork, paBlsMapG2)
else:
echo "Unknown test vector '" & $label & "'"
testStatusIMPL = SKIPPED

View File

@ -11,7 +11,8 @@ import
json_rpc/[rpcserver, rpcclient], eth/common as eth_common,
eth/[rlp, keys, trie/db, p2p/private/p2p_types],
../nimbus/rpc/[common, p2p, hexstrings, rpc_types, rpc_utils],
../nimbus/[constants, vm_state, config, genesis, utils, transaction],
../nimbus/[constants, config, genesis, utils, transaction,
vm_state, vm_types],
../nimbus/db/[accounts_cache, db_chain],
../nimbus/p2p/[chain, executor, executor/executor_helpers],
../nimbus/sync/protocol_eth65,
@ -31,6 +32,11 @@ template sourceDir: string = currentSourcePath.rsplit(DirSep, 1)[0]
const sigPath = &"{sourceDir}{DirSep}rpcclient{DirSep}ethcallsigs.nim"
createRpcSigs(RpcSocketClient, sigPath)
const
zeroAddress = block:
var rc: EthAddress
rc
type
TestEnv = object
txHash: Hash256
@ -51,12 +57,16 @@ proc setupEnv(chainDB: BaseChainDB, signer, ks2: EthAddress, ctx: EthContext): T
PUSH1 "0x1C" # RETURN OFFSET at 28
RETURN
chainDB.initStateDB(parent.stateRoot)
chainDB.stateDB.setCode(ks2, code)
chainDB.stateDB.addBalance(signer, 9_000_000_000.u256)
var
vmState = newBaseVMState(chainDB.stateDB, BlockHeader(parentHash: parentHash), chainDB)
zeroAddress: EthAddress
let
vmHeader = BlockHeader(parentHash: parentHash)
vmState = BaseVMState.new(
parent = BlockHeader(stateRoot: parent.stateRoot),
header = vmHeader,
chainDB = chainDB,
pruneTrie = chainDB.pruneTrie)
vmState.stateDB.setCode(ks2, code)
vmState.stateDB.addBalance(signer, 9_000_000_000.u256)
let
unsignedTx1 = Transaction(
@ -85,7 +95,7 @@ proc setupEnv(chainDB: BaseChainDB, signer, ks2: EthAddress, ctx: EthContext): T
vmState.cumulativeGasUsed = 0
for txIndex, tx in txs:
let sender = tx.getSender()
discard vmState.processTransaction(tx, sender, vmState.blockHeader)
discard vmState.processTransaction(tx, sender, vmHeader)
vmState.receipts[txIndex] = makeReceipt(vmState, tx.txType)
let