nimbus-eth1/tools/evmstate/evmstate.nim

292 lines
7.8 KiB
Nim
Raw Normal View History

2022-12-10 12:53:24 +00:00
# Nimbus
# Copyright (c) 2022-2024 Status Research & Development GmbH
2022-12-10 12:53:24 +00:00
# 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.
2022-10-26 15:46:13 +00:00
import
std/[json, strutils, sets, tables, options, streams],
chronicles,
2022-12-02 04:39:12 +00:00
eth/keys,
2024-05-30 12:54:03 +00:00
stew/byteutils,
results,
2022-10-26 15:46:13 +00:00
stint,
2022-12-02 04:39:12 +00:00
eth/trie/[trie_defs],
../../nimbus/[evm/types, evm/state],
../../nimbus/db/ledger,
2022-10-26 15:46:13 +00:00
../../nimbus/transaction,
2022-12-02 04:39:12 +00:00
../../nimbus/core/executor,
../../nimbus/common/common,
../../nimbus/evm/tracer/json_tracer,
../../nimbus/core/eip4844,
../../nimbus/utils/state_dump,
../common/helpers as chp,
"."/[config, helpers],
../common/state_clearing
2022-10-26 15:46:13 +00:00
type
StateContext = object
name: string
parent: BlockHeader
2022-10-26 15:46:13 +00:00
header: BlockHeader
tx: Transaction
expectedHash: Hash256
expectedLogs: Hash256
2023-02-23 02:15:58 +00:00
forkStr: string
chainConfig: ChainConfig
2022-10-26 15:46:13 +00:00
index: int
tracerFlags: set[TracerFlags]
error: string
trustedSetupLoaded: bool
2022-10-26 15:46:13 +00:00
StateResult = object
name : string
pass : bool
root : Hash256
2022-10-26 15:46:13 +00:00
fork : string
error: string
state: StateDump
2022-12-15 03:46:28 +00:00
TestVMState = ref object of BaseVMState
2022-10-26 15:46:13 +00:00
proc extractNameAndFixture(ctx: var StateContext, n: JsonNode): JsonNode =
for label, child in n:
result = child
ctx.name = label
return
doAssert(false, "unreachable")
proc toBytes(x: string): seq[byte] =
result = newSeq[byte](x.len)
for i in 0..<x.len: result[i] = x[i].byte
method getAncestorHash(vmState: TestVMState; blockNumber: BlockNumber): Hash256 =
2022-12-15 03:46:28 +00:00
keccakHash(toBytes($blockNumber))
2022-10-26 15:46:13 +00:00
proc verifyResult(ctx: var StateContext, vmState: BaseVMState) =
ctx.error = ""
let obtainedHash = vmState.readOnlyStateDB.rootHash
if obtainedHash != ctx.expectedHash:
ctx.error = "post state root mismatch: got $1, want $2" %
[($obtainedHash).toLowerAscii, $ctx.expectedHash]
2022-10-26 15:46:13 +00:00
return
let logEntries = vmState.getAndClearLogEntries()
let actualLogsHash = rlpHash(logEntries)
if actualLogsHash != ctx.expectedLogs:
ctx.error = "post state log hash mismatch: got $1, want $2" %
[($actualLogsHash).toLowerAscii, $ctx.expectedLogs]
2022-10-26 15:46:13 +00:00
return
proc writeResultToStdout(stateRes: seq[StateResult]) =
var n = newJArray()
for res in stateRes:
let z = %{
"name" : %(res.name),
"pass" : %(res.pass),
"stateRoot" : %(res.root),
2022-10-26 15:46:13 +00:00
"fork" : %(res.fork),
"error": %(res.error)
}
if res.state.isNil.not:
z["state"] = %(res.state)
n.add(z)
stdout.write(n.pretty)
stdout.write("\n")
when false:
proc dumpAccounts(db: LedgerRef): Table[EthAddress, DumpAccount] =
for accAddr in db.addresses():
let acc = DumpAccount(
balance : db.getBalance(accAddr),
nonce : db.getNonce(accAddr),
root : db.getStorageRoot(accAddr),
codeHash: db.getCodeHash(accAddr),
code : db.getCode(accAddr),
key : keccakHash(accAddr)
)
for k, v in db.storage(accAddr):
acc.storage[k] = v
result[accAddr] = acc
proc dumpState(vmState: BaseVMState): StateDump =
StateDump(
root: vmState.readOnlyStateDB.rootHash,
accounts: dumpAccounts(vmState.stateDB)
)
proc writeRootHashToStderr(vmState: BaseVMState) =
let stateRoot = %{
"stateRoot": %(vmState.readOnlyStateDB.rootHash)
}
stderr.writeLine($stateRoot)
2022-10-26 15:46:13 +00:00
proc runExecution(ctx: var StateContext, conf: StateConf, pre: JsonNode): StateResult =
let
com = CommonRef.new(newCoreDbRef DefaultDbMemory, ctx.chainConfig)
stream = newFileStream(stderr)
tracer = if conf.jsonEnabled:
newJsonTracer(stream, ctx.tracerFlags, conf.pretty)
else:
JsonTracer(nil)
2022-10-26 15:46:13 +00:00
if com.isCancunOrLater(ctx.header.timestamp):
if not ctx.trustedSetupLoaded:
let res = loadKzgTrustedSetup()
if res.isErr:
echo "FATAL: ", res.error
quit(QuitFailure)
ctx.trustedSetupLoaded = true
2022-12-15 03:46:28 +00:00
let vmState = TestVMState()
vmState.init(
parent = ctx.parent,
header = ctx.header,
com = com,
tracer = tracer)
2022-10-26 15:46:13 +00:00
var gasUsed: GasInt
let sender = ctx.tx.getSender()
vmState.mutateStateDB:
setupStateDB(pre, db)
db.persist(clearEmptyAccount = false) # settle accounts storage
2022-10-26 15:46:13 +00:00
defer:
ctx.verifyResult(vmState)
result = StateResult(
name : ctx.name,
pass : ctx.error.len == 0,
root : vmState.readOnlyStateDB.rootHash,
2023-02-23 02:15:58 +00:00
fork : ctx.forkStr,
2022-10-26 15:46:13 +00:00
error: ctx.error
)
if conf.dumpEnabled:
result.state = dumpState(vmState.stateDB)
2022-10-26 15:46:13 +00:00
if conf.jsonEnabled:
writeRootHashToStderr(vmState)
2022-10-26 15:46:13 +00:00
try:
let rc = vmState.processTransaction(
ctx.tx, sender, ctx.header)
if rc.isOk:
gasUsed = rc.value
let miner = ctx.header.coinbase
coinbaseStateClearing(vmState, miner)
except CatchableError as ex:
echo "FATAL: ", ex.msg
quit(QuitFailure)
except AssertionDefect as ex:
echo "FATAL: ", ex.msg
quit(QuitFailure)
2022-10-26 15:46:13 +00:00
proc toTracerFlags(conf: StateConf): set[TracerFlags] =
2022-10-26 15:46:13 +00:00
result = {
TracerFlags.DisableStateDiff
2022-10-26 15:46:13 +00:00
}
if conf.disableMemory : result.incl TracerFlags.DisableMemory
if conf.disableStack : result.incl TracerFlags.DisableStack
2022-10-26 15:46:13 +00:00
if conf.disableReturnData: result.incl TracerFlags.DisableReturnData
if conf.disableStorage : result.incl TracerFlags.DisableStorage
template hasError(ctx: StateContext): bool =
ctx.error.len > 0
proc prepareAndRun(ctx: var StateContext, conf: StateConf): bool =
let
fixture = json.parseFile(conf.inputFile)
n = ctx.extractNameAndFixture(fixture)
txData = n["transaction"]
post = n["post"]
pre = n["pre"]
ctx.parent = parseParentHeader(n["env"])
2022-10-26 15:46:13 +00:00
ctx.header = parseHeader(n["env"])
if conf.debugEnabled or conf.jsonEnabled:
ctx.tracerFlags = toTracerFlags(conf)
var
stateRes = newSeqOfCap[StateResult](post.len)
index = 1
hasError = false
template prepareFork(forkName: string) =
try:
2023-02-23 02:15:58 +00:00
ctx.forkStr = forkName
ctx.chainConfig = getChainConfig(forkName)
except ValueError as ex:
debugEcho ex.msg
return false
2022-10-26 15:46:13 +00:00
ctx.index = index
inc index
template runSubTest(subTest: JsonNode) =
ctx.expectedHash = Hash256.fromJson(subTest["hash"])
ctx.expectedLogs = Hash256.fromJson(subTest["logs"])
ctx.tx = parseTx(txData, subTest["indexes"])
let res = ctx.runExecution(conf, pre)
stateRes.add res
hasError = hasError or ctx.hasError
if conf.fork.len > 0:
if not post.hasKey(conf.fork):
stdout.writeLine("selected fork not available: " & conf.fork)
return false
let forkData = post[conf.fork]
prepareFork(conf.fork)
if conf.index.isNone:
for subTest in forkData:
runSubTest(subTest)
else:
let index = conf.index.get()
if index > forkData.len or index < 0:
stdout.writeLine("selected index out of range(0-$1), requested $2" %
[$forkData.len, $index])
return false
let subTest = forkData[index]
runSubTest(subTest)
else:
for forkName, forkData in post:
prepareFork(forkName)
for subTest in forkData:
runSubTest(subTest)
writeResultToStdout(stateRes)
not hasError
when defined(chronicles_runtime_filtering):
type Lev = chronicles.LogLevel
proc toLogLevel(v: int): Lev =
case v
of 1: Lev.ERROR
of 2: Lev.WARN
of 3: Lev.INFO
of 4: Lev.DEBUG
of 5: Lev.TRACE
else: Lev.NONE
proc setVerbosity(v: int) =
let level = v.toLogLevel
setLogLevel(level)
2022-10-26 15:46:13 +00:00
proc main() =
let conf = StateConf.init()
when defined(chronicles_runtime_filtering):
setVerbosity(conf.verbosity)
2022-10-26 15:46:13 +00:00
var ctx: StateContext
if not ctx.prepareAndRun(conf):
quit(QuitFailure)
main()