add geth compatibility flag to emvstate tool and transaction tracer

This commit is contained in:
jangko 2022-12-14 16:42:55 +07:00
parent abafc3e91f
commit 2b5195c526
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
6 changed files with 95 additions and 21 deletions

View File

@ -365,6 +365,12 @@ proc removeTracedAccounts*(vmState: BaseVMState, accounts: varargs[EthAddress])
for acc in accounts: for acc in accounts:
vmState.tracer.accounts.excl acc vmState.tracer.accounts.excl acc
proc tracerGasUsed*(vmState: BaseVMState, gasUsed: GasInt) =
vmState.tracer.gasUsed = gasUsed
proc tracerGasUsed*(vmState: BaseVMState): GasInt =
vmState.tracer.gasUsed
proc status*(vmState: BaseVMState): bool = proc status*(vmState: BaseVMState): bool =
ExecutionOK in vmState.flags ExecutionOK in vmState.flags

View File

@ -18,7 +18,6 @@ proc initTracer*(tracer: var TransactionTracer, flags: set[TracerFlags] = {}) =
tracer.trace["gas"] = %0 tracer.trace["gas"] = %0
tracer.trace["failed"] = %false tracer.trace["failed"] = %false
tracer.trace["returnValue"] = %"" tracer.trace["returnValue"] = %""
tracer.trace["structLogs"] = newJArray() tracer.trace["structLogs"] = newJArray()
tracer.flags = flags tracer.flags = flags
tracer.accounts = initHashSet[EthAddress]() tracer.accounts = initHashSet[EthAddress]()
@ -44,6 +43,16 @@ iterator storage(tracer: TransactionTracer, compDepth: int): UInt256 =
for key in tracer.storageKeys[compDepth]: for key in tracer.storageKeys[compDepth]:
yield key yield key
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 traceOpCodeStarted*(tracer: var TransactionTracer, c: Computation, op: Op): int = proc traceOpCodeStarted*(tracer: var TransactionTracer, c: Computation, op: Op): int =
if unlikely tracer.trace.isNil: if unlikely tracer.trace.isNil:
tracer.initTracer() tracer.initTracer()
@ -51,17 +60,34 @@ proc traceOpCodeStarted*(tracer: var TransactionTracer, c: Computation, op: Op):
let j = newJObject() let j = newJObject()
tracer.trace["structLogs"].add(j) tracer.trace["structLogs"].add(j)
j["op"] = %(($op).toUpperAscii) if TracerFlags.GethCompatibility in tracer.flags:
j["pc"] = %(c.code.pc - 1) j["pc"] = %(c.code.pc - 1)
j["depth"] = %(c.msg.depth + 1) j["op"] = %(op.int)
j["gas"] = %c.gasMeter.gasRemaining j["gas"] = encodeHexInt(c.gasMeter.gasRemaining)
j["gasCost"] = %("")
j["memSize"] = %c.memory.len
j["opName"] = %(($op).toUpperAscii)
j["depth"] = %(c.msg.depth + 1)
# log stack # log stack
if TracerFlags.DisableStack notin tracer.flags: if TracerFlags.DisableStack notin tracer.flags:
let st = newJArray() let st = newJArray()
for v in c.stack.values: for v in c.stack.values:
st.add(%v.dumpHex()) st.add(%("0x" & v.dumpHex.stripLeadingZeros))
j["stack"] = st j["stack"] = st
else:
j["op"] = %(($op).toUpperAscii)
j["pc"] = %(c.code.pc - 1)
j["depth"] = %(c.msg.depth + 1)
j["gas"] = %c.gasMeter.gasRemaining
# log stack
if TracerFlags.DisableStack notin tracer.flags:
let st = newJArray()
for v in c.stack.values:
st.add(%v.dumpHex())
j["stack"] = st
# log memory # log memory
if TracerFlags.DisableMemory notin tracer.flags: if TracerFlags.DisableMemory notin tracer.flags:
@ -101,11 +127,19 @@ proc traceOpCodeEnded*(tracer: var TransactionTracer, c: Computation, op: Op, la
var stateDB = c.vmState.stateDB var stateDB = c.vmState.stateDB
for key in tracer.storage(c.msg.depth): for key in tracer.storage(c.msg.depth):
let value = stateDB.getStorage(c.msg.contractAddress, key) let value = stateDB.getStorage(c.msg.contractAddress, key)
storage[key.dumpHex] = %(value.dumpHex) if TracerFlags.GethCompatibility in tracer.flags:
storage["0x" & key.dumpHex.stripLeadingZeros] =
%("0x" & value.dumpHex.stripLeadingZeros)
else:
storage[key.dumpHex] = %(value.dumpHex)
j["storage"] = storage j["storage"] = storage
let gasRemaining = j["gas"].getBiggestInt() if TracerFlags.GethCompatibility in tracer.flags:
j["gasCost"] = %(gasRemaining - c.gasMeter.gasRemaining) let gas = fromHex[GasInt](j["gas"].getStr)
j["gasCost"] = encodeHexInt(gas - c.gasMeter.gasRemaining)
else:
let gas = j["gas"].getBiggestInt()
j["gasCost"] = %(gas - c.gasMeter.gasRemaining)
if op in {Return, Revert} and TracerFlags.DisableReturnData notin tracer.flags: if op in {Return, Revert} and TracerFlags.DisableReturnData notin tracer.flags:
let returnValue = %("0x" & toHex(c.output, true)) let returnValue = %("0x" & toHex(c.output, true))
@ -123,7 +157,11 @@ proc traceError*(tracer: var TransactionTracer, c: Computation) =
# even though the gasCost is incorrect, # even though the gasCost is incorrect,
# we have something to display, # we have something to display,
# it is an error anyway # it is an error anyway
let gasRemaining = j["gas"].getBiggestInt() if TracerFlags.GethCompatibility in tracer.flags:
j["gasCost"] = %(gasRemaining - c.gasMeter.gasRemaining) let gas = fromHex[GasInt](j["gas"].getStr)
j["gasCost"] = encodeHexInt(gas - c.gasMeter.gasRemaining)
else:
let gas = j["gas"].getBiggestInt()
j["gasCost"] = %(gas - c.gasMeter.gasRemaining)
tracer.trace["failed"] = %true tracer.trace["failed"] = %true

View File

@ -65,12 +65,14 @@ type
DisableStateDiff DisableStateDiff
EnableAccount EnableAccount
DisableReturnData DisableReturnData
GethCompatibility
TransactionTracer* = object TransactionTracer* = object
trace*: JsonNode trace*: JsonNode
flags*: set[TracerFlags] flags*: set[TracerFlags]
accounts*: HashSet[EthAddress] accounts*: HashSet[EthAddress]
storageKeys*: seq[HashSet[UInt256]] storageKeys*: seq[HashSet[UInt256]]
gasUsed*: GasInt
Computation* = ref object Computation* = ref object
# The execution computation # The execution computation

View File

@ -242,6 +242,8 @@ proc finishRunningComputation(host: TransactionHost, call: CallParams): CallResu
let c = host.computation let c = host.computation
let gasRemaining = calculateAndPossiblyRefundGas(host, call) let gasRemaining = calculateAndPossiblyRefundGas(host, call)
# evm gas used without intrinsic gas
host.vmState.tracerGasUsed(host.msg.gas - gasRemaining)
result.isError = c.isError result.isError = c.isError
result.gasUsed = call.gasLimit - gasRemaining result.gasUsed = call.gasLimit - gasRemaining

View File

@ -38,6 +38,7 @@ export
vms.status, vms.status,
vms.`status=`, vms.`status=`,
vms.tracedAccounts, vms.tracedAccounts,
vms.tracedAccountsPairs vms.tracedAccountsPairs,
vms.tracerGasUsed
# End # End

View File

@ -50,6 +50,7 @@ type
StateResult = object StateResult = object
name : string name : string
pass : bool pass : bool
root : Hash256
fork : string fork : string
error: string error: string
state: StateDump state: StateDump
@ -87,14 +88,14 @@ proc verifyResult(ctx: var StateContext, vmState: BaseVMState) =
let obtainedHash = vmState.readOnlyStateDB.rootHash let obtainedHash = vmState.readOnlyStateDB.rootHash
if obtainedHash != ctx.expectedHash: if obtainedHash != ctx.expectedHash:
ctx.error = "post state root mismatch: got $1, want $2" % ctx.error = "post state root mismatch: got $1, want $2" %
[$obtainedHash, $ctx.expectedHash] [($obtainedHash).toLowerAscii, $ctx.expectedHash]
return return
let logEntries = vmState.getAndClearLogEntries() let logEntries = vmState.getAndClearLogEntries()
let actualLogsHash = rlpHash(logEntries) let actualLogsHash = rlpHash(logEntries)
if actualLogsHash != ctx.expectedLogs: if actualLogsHash != ctx.expectedLogs:
ctx.error = "post state log hash mismatch: got $1, want $2" % ctx.error = "post state log hash mismatch: got $1, want $2" %
[$actualLogsHash, $ctx.expectedLogs] [($actualLogsHash).toLowerAscii, $ctx.expectedLogs]
return return
proc `%`(x: UInt256): JsonNode = proc `%`(x: UInt256): JsonNode =
@ -143,6 +144,7 @@ proc writeResultToStdout(stateRes: seq[StateResult]) =
let z = %{ let z = %{
"name" : %(res.name), "name" : %(res.name),
"pass" : %(res.pass), "pass" : %(res.pass),
"stateRoot" : %(res.root),
"fork" : %(res.fork), "fork" : %(res.fork),
"error": %(res.error) "error": %(res.error)
} }
@ -173,16 +175,37 @@ proc dumpState(vmState: BaseVMState): StateDump =
accounts: dumpAccounts(vmState.stateDB) 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) = proc writeTraceToStderr(vmState: BaseVMState, pretty: bool) =
let trace = vmState.getTracingResult() let trace = vmState.getTracingResult()
trace["gasUsed"] = encodeHexInt(vmState.tracerGasUsed)
trace.delete("gas")
let stateRoot = %{
"stateRoot": %(vmState.readOnlyStateDB.rootHash)
}
if pretty: if pretty:
stderr.writeLine(trace.pretty) stderr.writeLine(trace.pretty)
else: else:
var gasUsed = 0
let logs = trace["structLogs"] let logs = trace["structLogs"]
trace.delete("structLogs") trace.delete("structLogs")
for x in logs: for x in logs:
if "error" in x:
trace["error"] = x["error"]
x.delete("error")
stderr.writeLine($x) stderr.writeLine($x)
stderr.writeLine($trace) stderr.writeLine($trace)
stderr.writeLine($stateRoot)
proc runExecution(ctx: var StateContext, conf: StateConf, pre: JsonNode): StateResult = proc runExecution(ctx: var StateContext, conf: StateConf, pre: JsonNode): StateResult =
let let
@ -213,6 +236,7 @@ proc runExecution(ctx: var StateContext, conf: StateConf, pre: JsonNode): StateR
result = StateResult( result = StateResult(
name : ctx.name, name : ctx.name,
pass : ctx.error.len == 0, pass : ctx.error.len == 0,
root : vmState.stateDB.rootHash,
fork : toString(ctx.fork), fork : toString(ctx.fork),
error: ctx.error error: ctx.error
) )
@ -233,12 +257,13 @@ proc runExecution(ctx: var StateContext, conf: StateConf, pre: JsonNode): StateR
if ctx.fork >= FkSpurious: if ctx.fork >= FkSpurious:
if db.isEmptyAccount(miner): if db.isEmptyAccount(miner):
db.deleteAccount(miner) db.deleteAccount(miner)
db.persist() db.persist(clearCache = false)
proc toTracerFlags(conf: Stateconf): set[TracerFlags] = proc toTracerFlags(conf: Stateconf): set[TracerFlags] =
result = { result = {
TracerFlags.DisableStateDiff, TracerFlags.DisableStateDiff,
TracerFlags.EnableTracing TracerFlags.EnableTracing,
TracerFlags.GethCompatibility
} }
if conf.disableMemory : result.incl TracerFlags.DisableMemory if conf.disableMemory : result.incl TracerFlags.DisableMemory