nimbus-eth1/tools/evmstate/evmstate.nim
jangko a31db89e0e
fix t8n tool bug and accounts cache bug
t8n: a silly bug contract address generator, should use original
tx nonce instead of read the nonce from sender address in state db.
Although in EVM contract address generated by reading nonce from state db
is correct, outside EVM that nonce value might have been modified,
thus generating incorrect contract address.

accounts cache: when clearing account storage, the originalValue
cache is not cleared, only the storageRoot set to empty storage root,
this will cause getStorage and getCommitedStorage return wrong value
if the originalValue cache contains old value.
2023-03-23 11:07:30 +07:00

345 lines
9.1 KiB
Nim

# Nimbus
# Copyright (c) 2022 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, strutils, sets, tables, options],
chronicles,
eth/keys,
stew/[results, byteutils],
stint,
eth/trie/[trie_defs],
../../nimbus/[vm_types, vm_state],
../../nimbus/db/accounts_cache,
../../nimbus/transaction,
../../nimbus/core/executor,
../../nimbus/common/common,
../common/helpers as chp,
"."/[config, helpers],
../common/state_clearing
type
StateContext = object
name: string
header: BlockHeader
tx: Transaction
expectedHash: Hash256
expectedLogs: Hash256
forkStr: string
chainConfig: ChainConfig
index: int
tracerFlags: set[TracerFlags]
error: string
DumpAccount = ref object
balance : UInt256
nonce : AccountNonce
root : Hash256
codeHash: Hash256
code : Blob
key : Hash256
storage : Table[UInt256, UInt256]
StateDump = ref object
root: Hash256
accounts: Table[EthAddress, DumpAccount]
StateResult = object
name : string
pass : bool
root : Hash256
fork : string
error: string
state: StateDump
TestVMState = ref object of BaseVMState
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 {.gcsafe.} =
keccakHash(toBytes($blockNumber))
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]
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]
return
proc `%`(x: UInt256): JsonNode =
%("0x" & x.toHex)
proc `%`(x: Blob): JsonNode =
%("0x" & x.toHex)
proc `%`(x: Hash256): JsonNode =
%("0x" & x.data.toHex)
proc `%`(x: AccountNonce): JsonNode =
%("0x" & x.toHex)
proc `%`(x: Table[UInt256, UInt256]): JsonNode =
result = newJObject()
for k, v in x:
result["0x" & k.toHex] = %(v)
proc `%`(x: DumpAccount): JsonNode =
result = %{
"balance" : %(x.balance),
"nonce" : %(x.nonce),
"root" : %(x.root),
"codeHash": %(x.codeHash),
"code" : %(x.code),
"key" : %(x.key)
}
if x.storage.len > 0:
result["storage"] = %(x.storage)
proc `%`(x: Table[EthAddress, DumpAccount]): JsonNode =
result = newJObject()
for k, v in x:
result["0x" & k.toHex] = %(v)
proc `%`(x: StateDump): JsonNode =
result = %{
"root": %(x.root),
"accounts": %(x.accounts)
}
proc writeResultToStdout(stateRes: seq[StateResult]) =
var n = newJArray()
for res in stateRes:
let z = %{
"name" : %(res.name),
"pass" : %(res.pass),
"stateRoot" : %(res.root),
"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")
proc dumpAccounts(db: AccountsCache): 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)
)
template stripLeadingZeros(value: string): string =
var cidx = 0
# ignore the last character so we retain '0' on zero value
while cidx < value.len - 1 and value[cidx] == '0':
cidx.inc
value[cidx .. ^1]
proc encodeHexInt(x: SomeInteger): JsonNode =
%("0x" & x.toHex.stripLeadingZeros.toLowerAscii)
proc writeTraceToStderr(vmState: BaseVMState, pretty: bool) =
let trace = vmState.getTracingResult()
trace["gasUsed"] = encodeHexInt(vmState.tracerGasUsed)
trace.delete("gas")
let stateRoot = %{
"stateRoot": %(vmState.readOnlyStateDB.rootHash)
}
if pretty:
stderr.writeLine(trace.pretty)
else:
let logs = trace["structLogs"]
trace.delete("structLogs")
for x in logs:
if "error" in x:
trace["error"] = x["error"]
x.delete("error")
stderr.writeLine($x)
stderr.writeLine($trace)
stderr.writeLine($stateRoot)
proc runExecution(ctx: var StateContext, conf: StateConf, pre: JsonNode): StateResult =
let
com = CommonRef.new(newMemoryDB(), ctx.chainConfig, pruneTrie = false)
parent = BlockHeader(stateRoot: emptyRlpHash)
fork = com.toEVMFork(ctx.header.forkDeterminationInfoForHeader)
let vmState = TestVMState()
vmState.init(
parent = parent,
header = ctx.header,
com = com,
tracerFlags = ctx.tracerFlags)
var gasUsed: GasInt
let sender = ctx.tx.getSender()
vmState.mutateStateDB:
setupStateDB(pre, db)
db.persist(clearEmptyAccount = false, clearCache = false) # settle accounts storage
defer:
ctx.verifyResult(vmState)
result = StateResult(
name : ctx.name,
pass : ctx.error.len == 0,
root : vmState.stateDB.rootHash,
fork : ctx.forkStr,
error: ctx.error
)
if conf.dumpEnabled:
result.state = dumpState(vmState)
if conf.jsonEnabled:
writeTraceToStderr(vmState, conf.pretty)
let rc = vmState.processTransaction(
ctx.tx, sender, ctx.header, fork)
if rc.isOk:
gasUsed = rc.value
let miner = ctx.header.coinbase
coinbaseStateClearing(vmState, miner, fork)
proc toTracerFlags(conf: Stateconf): set[TracerFlags] =
result = {
TracerFlags.DisableStateDiff,
TracerFlags.EnableTracing,
TracerFlags.GethCompatibility
}
if conf.disableMemory : result.incl TracerFlags.DisableMemory
if conf.disablestack : result.incl TracerFlags.DisableStack
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.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:
ctx.forkStr = forkName
ctx.chainConfig = getChainConfig(forkName)
except ValueError as ex:
debugEcho ex.msg
return false
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)
proc main() =
let conf = StateConf.init()
when defined(chronicles_runtime_filtering):
setVerbosity(conf.verbosity)
var ctx: StateContext
if not ctx.prepareAndRun(conf):
quit(QuitFailure)
main()