Reduce EVM complexity by removing forkOverride (#2448)

* Reduce EVM complexity by removing forkOverride

* Fixes
This commit is contained in:
andri lim 2024-07-04 20:48:36 +07:00 committed by GitHub
parent 81e75622cf
commit f04f30c72b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 78 additions and 115 deletions

View File

@ -45,8 +45,9 @@ proc processBlock(
var dbTx = vmState.com.db.newTransaction()
defer: dbTx.dispose()
if vmState.com.daoForkSupport and
vmState.com.daoForkBlock.get == header.number:
let com = vmState.com
if com.daoForkSupport and
com.daoForkBlock.get == header.number:
vmState.mutateStateDB:
db.applyDAOHardFork()
@ -55,19 +56,19 @@ proc processBlock(
? processTransactions(vmState, header, blk.transactions)
if vmState.determineFork >= FkShanghai:
if com.isShanghaiOrLater(header.timestamp):
for withdrawal in blk.withdrawals.get:
vmState.stateDB.addBalance(withdrawal.address, withdrawal.weiAmount)
if header.ommersHash != EMPTY_UNCLE_HASH:
discard vmState.com.db.persistUncles(blk.uncles)
discard com.db.persistUncles(blk.uncles)
# EIP-3675: no reward for miner in POA/POS
if vmState.com.consensus == ConsensusType.POW:
if com.consensus == ConsensusType.POW:
vmState.calculateReward(header, blk.uncles)
vmState.mutateStateDB:
let clearEmptyAccount = vmState.determineFork >= FkSpurious
let clearEmptyAccount = com.isSpuriousOrLater(header.number)
db.persist(clearEmptyAccount)
dbTx.commit()

View File

@ -302,6 +302,9 @@ func toEVMFork*(com: CommonRef, forkDeterminer: ForkDeterminationInfo): EVMFork
func toEVMFork*(com: CommonRef): EVMFork =
ToEVMFork[com.currentFork]
func isSpuriousOrLater*(com: CommonRef, number: BlockNumber): bool =
com.toHardFork(number.forkDeterminationInfo) >= Spurious
func isLondonOrLater*(com: CommonRef, number: BlockNumber): bool =
# TODO: Fixme, use only London comparator
com.toHardFork(number.forkDeterminationInfo) >= London

View File

@ -57,7 +57,8 @@ proc procBlkPreamble(
template header(): BlockHeader =
blk.header
if vmState.com.daoForkSupport and vmState.com.daoForkBlock.get == header.number:
let com = vmState.com
if com.daoForkSupport and com.daoForkBlock.get == header.number:
vmState.mutateStateDB:
db.applyDAOHardFork()
@ -65,7 +66,7 @@ proc procBlkPreamble(
if blk.transactions.calcTxRoot != header.txRoot:
return err("Mismatched txRoot")
if vmState.determineFork >= FkCancun:
if com.isCancunOrLater(header.timestamp):
if header.parentBeaconBlockRoot.isNone:
return err("Post-Cancun block header must have parentBeaconBlockRoot")
@ -82,7 +83,7 @@ proc procBlkPreamble(
elif blk.transactions.len > 0:
return err("Transactions in block with empty txRoot")
if vmState.determineFork >= FkShanghai:
if com.isShanghaiOrLater(header.timestamp):
if header.withdrawalsRoot.isNone:
return err("Post-Shanghai block header must have withdrawalsRoot")
if blk.withdrawals.isNone:
@ -127,7 +128,7 @@ proc procBlkEpilogue(
# Clearing the account cache here helps manage its size when replaying
# large ranges of blocks, implicitly limiting its size using the gas limit
db.persist(
clearEmptyAccount = vmState.determineFork >= FkSpurious,
clearEmptyAccount = vmState.com.isSpuriousOrLater(header.number),
clearCache = true)
if not skipValidation:

View File

@ -70,12 +70,12 @@ proc processTransactionImpl(
tx: Transaction; ## Transaction to validate
sender: EthAddress; ## tx.getSender or tx.ecRecover
header: BlockHeader; ## Header for the block containing the current tx
fork: EVMFork;
): Result[GasInt, string] =
## Modelled after `https://eips.ethereum.org/EIPS/eip-1559#specification`_
## which provides a backward compatible framwork for EIP1559.
let
fork = vmState.fork
roDB = vmState.readOnlyStateDB
baseFee256 = header.eip1559BaseFee(fork)
baseFee = baseFee256.truncate(GasInt)
@ -115,7 +115,7 @@ proc processTransactionImpl(
vmState.captureTxStart(tx.gasLimit)
let
accTx = vmState.stateDB.beginSavepoint
gasBurned = tx.txCallEvm(sender, vmState, fork)
gasBurned = tx.txCallEvm(sender, vmState)
vmState.captureTxEnd(tx.gasLimit - gasBurned)
res = commitOrRollbackDependingOnGasUsed(vmState, accTx, header, tx, gasBurned, priorityFee)
@ -169,18 +169,8 @@ proc processTransaction*(
tx: Transaction; ## Transaction to validate
sender: EthAddress; ## tx.getSender or tx.ecRecover
header: BlockHeader; ## Header for the block containing the current tx
fork: EVMFork;
): Result[GasInt,string] =
vmState.processTransactionImpl(tx, sender, header, fork)
proc processTransaction*(
vmState: BaseVMState; ## Parent accounts environment for transaction
tx: Transaction; ## Transaction to validate
sender: EthAddress; ## tx.getSender or tx.ecRecover
header: BlockHeader;
): Result[GasInt,string] =
let fork = vmState.com.toEVMFork(header.forkDeterminationInfo)
vmState.processTransaction(tx, sender, header, fork)
vmState.processTransactionImpl(tx, sender, header)
# ------------------------------------------------------------------------------
# End

View File

@ -250,7 +250,7 @@ func excessBlobGas*(dh: TxChainRef): uint64 =
func nextFork*(dh: TxChainRef): EVMFork =
## Getter, fork of next block
dh.com.toEVMFork(dh.txEnv.vmState.forkDeterminationInfoForVMState)
dh.txEnv.vmState.fork
func gasUsed*(dh: TxChainRef): GasInt =
## Getter, accumulated gas burned for collected blocks

View File

@ -81,11 +81,10 @@ proc runTx(pst: TxPackerStateRef; item: TxItemRef): GasInt =
## Execute item transaction and update `vmState` book keeping. Returns the
## `gasUsed` after executing the transaction.
let
fork = pst.xp.chain.nextFork
baseFee = pst.xp.chain.baseFee
tx = item.tx.eip1559TxNormalization(baseFee.GasInt)
let gasUsed = tx.txCallEvm(item.sender, pst.xp.chain.vmState, fork)
let gasUsed = tx.txCallEvm(item.sender, pst.xp.chain.vmState)
pst.cleanState = false
doAssert 0 <= gasUsed
gasUsed

View File

@ -20,6 +20,12 @@ import
./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,
@ -37,6 +43,8 @@ proc init(
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 =
@ -272,15 +280,6 @@ proc `collectWitnessData=`*(vmState: BaseVMState, status: bool) =
if status: vmState.flags.incl CollectWitnessData
else: vmState.flags.excl CollectWitnessData
func forkDeterminationInfoForVMState*(vmState: BaseVMState): ForkDeterminationInfo =
# FIXME-Adam: Is this timestamp right? Note that up above in blockNumber we add 1;
# should timestamp be adding 12 or something?
# Also, can I get the TD? Do I need to?
forkDeterminationInfo(vmState.blockNumber, vmState.blockCtx.timestamp)
func determineFork*(vmState: BaseVMState): EVMFork =
vmState.com.toEVMFork(vmState.forkDeterminationInfoForVMState)
func tracingEnabled*(vmState: BaseVMState): bool =
vmState.tracer.isNil.not

View File

@ -9,10 +9,8 @@
# according to those terms.
import
eth/common/eth_types,
../constants,
../db/ledger,
../transaction,
./computation,
./interpreter_dispatch,
./interpreter/gas_costs,
@ -22,19 +20,6 @@ import
{.push raises: [].}
proc setupTxContext*(vmState: BaseVMState,
txCtx: sink TxContext,
forkOverride=Opt.none(EVMFork)) =
## this proc will be called each time a new transaction
## is going to be executed
vmState.txCtx = system.move(txCtx)
vmState.fork =
if forkOverride.isSome:
forkOverride.get
else:
vmState.determineFork
vmState.gasCosts = vmState.fork.forkToSchedule
# Using `proc` as `incNonce()` might be `proc` in logging mode
proc preExecComputation(c: Computation) =
if not c.msg.isCreate:

View File

@ -32,8 +32,7 @@ func destination*(args: TransactionArgs): EthAddress =
ethAddr args.to.get(ZeroAddr)
proc toCallParams*(vmState: BaseVMState, args: TransactionArgs,
globalGasCap: GasInt, baseFee: Opt[UInt256],
forkOverride = Opt.none(EVMFork)): EvmResult[CallParams] =
globalGasCap: GasInt, baseFee: Opt[UInt256]): EvmResult[CallParams] =
# Reject invalid combinations of pre- and post-1559 fee styles
if args.gasPrice.isSome and
@ -74,7 +73,6 @@ proc toCallParams*(vmState: BaseVMState, args: TransactionArgs,
ok(CallParams(
vmState: vmState,
forkOverride: forkOverride,
sender: args.sender,
to: args.destination,
isCreate: args.to.isNone,

View File

@ -31,7 +31,6 @@ type
# Standard call parameters.
CallParams* = object
vmState*: BaseVMState # Chain, database, state, block, fork.
forkOverride*: Opt[EVMFork] # Default fork is usually correct.
origin*: Opt[HostAddress] # Default origin is `sender`.
gasPrice*: GasInt # Gas price for this call.
gasLimit*: GasInt # Maximum gas available for this call.
@ -133,14 +132,11 @@ proc initialAccessListEIP2929(call: CallParams) =
proc setupHost(call: CallParams): TransactionHost =
let vmState = call.vmState
vmState.setupTxContext(
TxContext(
origin : call.origin.get(call.sender),
gasPrice : call.gasPrice,
versionedHashes: call.versionedHashes,
blobBaseFee : getBlobBaseFee(vmState.blockCtx.excessBlobGas),
),
forkOverride = call.forkOverride
vmState.txCtx = TxContext(
origin : call.origin.get(call.sender),
gasPrice : call.gasPrice,
versionedHashes: call.versionedHashes,
blobBaseFee : getBlobBaseFee(vmState.blockCtx.excessBlobGas),
)
var intrinsicGas: GasInt = 0

View File

@ -63,7 +63,7 @@ proc rpcEstimateGas*(args: TransactionArgs,
baseFeePerGas: Opt.none UInt256, ## ???
)
let vmState = ? BaseVMState.new(topHeader, com)
let fork = vmState.determineFork
let fork = vmState.fork
let txGas = gasFees[fork][GasTransaction] # txGas always 21000, use constants?
var params = ? toCallParams(vmState, args, gasCap, header.baseFeePerGas)
@ -147,12 +147,11 @@ proc rpcEstimateGas*(args: TransactionArgs,
ok(hi)
proc callParamsForTx(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: EVMFork): CallParams =
proc callParamsForTx(tx: Transaction, sender: EthAddress, vmState: BaseVMState): CallParams =
# Is there a nice idiom for this kind of thing? Should I
# just be writing this as a bunch of assignment statements?
result = CallParams(
vmState: vmState,
forkOverride: Opt.some(fork),
gasPrice: tx.gasPrice,
gasLimit: tx.gasLimit,
sender: sender,
@ -167,10 +166,9 @@ proc callParamsForTx(tx: Transaction, sender: EthAddress, vmState: BaseVMState,
if tx.txType >= TxEip4844:
result.versionedHashes = tx.versionedHashes
proc callParamsForTest(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: EVMFork): CallParams =
proc callParamsForTest(tx: Transaction, sender: EthAddress, vmState: BaseVMState): CallParams =
result = CallParams(
vmState: vmState,
forkOverride: Opt.some(fork),
gasPrice: tx.gasPrice,
gasLimit: tx.gasLimit,
sender: sender,
@ -190,16 +188,14 @@ proc callParamsForTest(tx: Transaction, sender: EthAddress, vmState: BaseVMState
proc txCallEvm*(tx: Transaction,
sender: EthAddress,
vmState: BaseVMState,
fork: EVMFork): GasInt =
vmState: BaseVMState): GasInt =
let
call = callParamsForTx(tx, sender, vmState, fork)
call = callParamsForTx(tx, sender, vmState)
res = runComputation(call)
res.gasUsed
proc testCallEvm*(tx: Transaction,
sender: EthAddress,
vmState: BaseVMState,
fork: EVMFork): CallResult =
let call = callParamsForTest(tx, sender, vmState, fork)
vmState: BaseVMState): CallResult =
let call = callParamsForTest(tx, sender, vmState)
runComputation(call)

View File

@ -395,12 +395,11 @@ proc createSignedTx(payload: Blob, chainId: ChainId): Transaction =
proc runVM*(vmState: BaseVMState, boa: Assembler): bool =
let
com = vmState.com
fork = com.toEVMFork()
vmState.mutateStateDB:
db.setCode(codeAddress, boa.code)
db.setBalance(codeAddress, 1_000_000.u256)
let tx = createSignedTx(boa.data, com.chainId)
let asmResult = testCallEvm(tx, tx.getSender, vmState, fork)
let asmResult = testCallEvm(tx, tx.getSender, vmState)
verifyAsmResult(vmState, boa, asmResult)
macro assembler*(list: untyped): untyped =

View File

@ -19,7 +19,6 @@ import
../nimbus/evm/memory,
../nimbus/evm/code_stream,
../nimbus/evm/internals,
../nimbus/evm/types,
../nimbus/constants,
../nimbus/core/pow/header,
../nimbus/db/ledger,
@ -377,7 +376,7 @@ proc runTestOverflow() =
let privateKey = PrivateKey.fromHex("0000000000000000000000000000000000000000000000000000001000000000")[]
let tx = signTransaction(unsignedTx, privateKey, ChainId(1), false)
let res = testCallEvm(tx, tx.getSender, s, FkHomestead)
let res = testCallEvm(tx, tx.getSender, s)
when defined(evmc_enabled):
check res.error == "EVMC_FAILURE"

View File

@ -101,7 +101,6 @@ proc testFixtureIndexes(ctx: var TestCtx, testStatusIMPL: var TestStatus) =
var gasUsed: GasInt
let sender = ctx.tx.getSender()
let fork = com.toEVMFork(ctx.header.forkDeterminationInfo)
vmState.mutateStateDB:
setupStateDB(ctx.pre, db)
@ -112,12 +111,12 @@ proc testFixtureIndexes(ctx: var TestCtx, testStatusIMPL: var TestStatus) =
db.persist()
let rc = vmState.processTransaction(
ctx.tx, sender, ctx.header, fork)
ctx.tx, sender, ctx.header)
if rc.isOk:
gasUsed = rc.value
let miner = ctx.header.coinbase
coinbaseStateClearing(vmState, miner, fork)
coinbaseStateClearing(vmState, miner)
block post:
let obtainedHash = vmState.readOnlyStateDB.rootHash

View File

@ -10,6 +10,7 @@ import
unittest2, stew/byteutils,
eth/[keys, trie],
../nimbus/common/common,
../tools/common/helpers as chp,
../nimbus/[evm/computation,
evm/state,
evm/types,
@ -23,7 +24,7 @@ import
proc initAddress(i: byte): EthAddress = result[19] = i
template doTest(fixture: JsonNode; vmState: BaseVMState; fork: EVMFork, address: PrecompileAddresses): untyped =
template doTest(fixture: JsonNode; vmState: BaseVMState; address: PrecompileAddresses): untyped =
for test in fixture:
let
expectedErr = test.hasKey("ExpectedError")
@ -44,7 +45,7 @@ template doTest(fixture: JsonNode; vmState: BaseVMState; fork: EVMFork, address:
payload: if dataStr.len > 0: dataStr.hexToSeqByte else: @[]
)
let tx = signTransaction(unsignedTx, privateKey, ChainId(1), false)
let fixtureResult = testCallEvm(tx, tx.getSender, vmState, fork)
let fixtureResult = testCallEvm(tx, tx.getSender, vmState)
if expectedErr:
check fixtureResult.isError
@ -59,20 +60,16 @@ template doTest(fixture: JsonNode; vmState: BaseVMState; fork: EVMFork, address:
debugEcho "GAS: ", fixtureResult.gasUsed, " ", gasExpected.get
check fixtureResult.gasUsed == gasExpected.get
proc parseFork(x: string): EVMFork =
let x = x.toLowerAscii
for name, fork in nameToFork:
if name.toLowerAscii == x:
return fork
doAssert(false, "unsupported fork name " & x)
proc parseFork(x: string): string =
result = x.capitalizeAscii
proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) =
let
label = fixtures["func"].getStr
fork = parseFork(fixtures["fork"].getStr)
conf = getChainConfig(parseFork(fixtures["fork"].getStr))
data = fixtures["data"]
privateKey = PrivateKey.fromHex("7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d")[]
com = CommonRef.new(newCoreDbRef DefaultDbMemory, config = ChainConfig())
com = CommonRef.new(newCoreDbRef DefaultDbMemory, config = conf)
vmState = BaseVMState.new(
BlockHeader(number: 1'u64, stateRoot: emptyRlpHash),
BlockHeader(),
@ -80,25 +77,25 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) =
)
case toLowerAscii(label)
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)
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 "ecrecover": data.doTest(vmState, paEcRecover)
of "sha256" : data.doTest(vmState, paSha256)
of "ripemd" : data.doTest(vmState, paRipeMd160)
of "identity" : data.doTest(vmState, paIdentity)
of "modexp" : data.doTest(vmState, paModExp)
of "bn256add" : data.doTest(vmState, paEcAdd)
of "bn256mul" : data.doTest(vmState, paEcMul)
of "ecpairing": data.doTest(vmState, paPairing)
of "blake2f" : data.doTest(vmState, paBlake2bf)
of "blsg1add" : data.doTest(vmState, paBlsG1Add)
of "blsg1mul" : data.doTest(vmState, paBlsG1Mul)
of "blsg1multiexp" : data.doTest(vmState, paBlsG1MultiExp)
of "blsg2add" : data.doTest(vmState, paBlsG2Add)
of "blsg2mul" : data.doTest(vmState, paBlsG2Mul)
# EIP 2537: disabled due to gas price changes/discprepancies
#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)
#of "blsg2multiexp": data.doTest(vmState, paBlsG2MultiExp)
#of "blspairing": data.doTest(vmState, paBlsPairing)
#of "blsmapg1": data.doTest(vmState, paBlsMapG1)
#of "blsmapg2": data.doTest(vmState, paBlsMapG2)
else:
echo "Unknown test vector '" & $label & "'"
testStatusIMPL = SKIPPED

View File

@ -121,6 +121,8 @@ func getChainConfig*(network: string, c: ChainConfig) =
c.assignTime(HardFork.Cancun, TimeZero)
of $TestFork.ShanghaiToCancunAtTime15k:
c.assignTime(HardFork.Cancun, EthTime(15000))
of $TestFork.Prague:
c.assignTime(HardFork.Prague, TimeZero)
else:
raise newException(ValueError, "unsupported network " & network)

View File

@ -15,7 +15,6 @@ import
proc coinbaseStateClearing*(vmState: BaseVMState,
miner: EthAddress,
fork: EVMFork,
touched = true) =
# This is necessary due to the manner in which the state tests are
# generated. State tests are generated from the BlockChainTest tests
@ -38,4 +37,4 @@ proc coinbaseStateClearing*(vmState: BaseVMState,
# do not clear cache, we need the cache when constructing
# post state
db.persist(clearEmptyAccount = fork >= FkSpurious)
db.persist(clearEmptyAccount = vmState.fork >= FkSpurious)

View File

@ -37,6 +37,7 @@ type
ParisToShanghaiAtTime15k
Cancun
ShanghaiToCancunAtTime15k
Prague
LogLevel* = enum
Silent

View File

@ -130,7 +130,6 @@ proc writeRootHashToStderr(vmState: BaseVMState) =
proc runExecution(ctx: var StateContext, conf: StateConf, pre: JsonNode): StateResult =
let
com = CommonRef.new(newCoreDbRef DefaultDbMemory, ctx.chainConfig)
fork = com.toEVMFork(ctx.header.forkDeterminationInfo)
stream = newFileStream(stderr)
tracer = if conf.jsonEnabled:
newJsonTracer(stream, ctx.tracerFlags, conf.pretty)
@ -175,12 +174,12 @@ proc runExecution(ctx: var StateContext, conf: StateConf, pre: JsonNode): StateR
try:
let rc = vmState.processTransaction(
ctx.tx, sender, ctx.header, fork)
ctx.tx, sender, ctx.header)
if rc.isOk:
gasUsed = rc.value
let miner = ctx.header.coinbase
coinbaseStateClearing(vmState, miner, fork)
coinbaseStateClearing(vmState, miner)
except CatchableError as ex:
echo "FATAL: ", ex.msg
quit(QuitFailure)

View File

@ -300,7 +300,7 @@ proc exec(ctx: var TransContext,
let miner = ctx.env.currentCoinbase
let fork = vmState.com.toEVMFork
coinbaseStateClearing(vmState, miner, fork, stateReward.isSome())
coinbaseStateClearing(vmState, miner, stateReward.isSome())
let stateDB = vmState.stateDB
stateDB.postState(result.alloc)