nimbus-eth1/nimbus/evm/tracer/legacy_tracer.nim
Jacek Sieka f6be4bd0ec
avoid initTable (#2328)
`initTable` is obsolete since nim 0.19 and can introduce significant
memory overhead while providing no benefit (since the table will be
grown to the default initial size on first use anyway).

In particular, aristo layers will not necessarily use all tables they
initialize, for exampe when many empty accounts are being created.
2024-06-10 11:05:30 +02:00

185 lines
5.8 KiB
Nim

# Nimbus
# Copyright (c) 2023-2024 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.
## Populates the tracer API methods
## ================================
##
## The module name `legacy_tracer` is probably a misonmer as it also works
## with the new APIs for `CoreDb` and `Ledger`.
##
import
std/[json, sets, strutils, hashes],
eth/common/eth_types,
eth/rlp,
stew/byteutils,
chronicles,
".."/[types, memory, stack],
../interpreter/op_codes,
../../db/ledger,
../evm_errors
type
LegacyTracer* = ref object of TracerRef
trace: JsonNode
accounts: HashSet[EthAddress]
storageKeys: seq[HashSet[UInt256]]
gas: GasInt
proc hash*(x: UInt256): Hash =
result = hash(x.toByteArrayBE)
proc rememberStorageKey(ctx: LegacyTracer, compDepth: int, key: UInt256) =
ctx.storageKeys[compDepth].incl key
iterator storage(ctx: LegacyTracer, compDepth: int): UInt256 =
doAssert compDepth >= 0 and compDepth < ctx.storageKeys.len
for key in ctx.storageKeys[compDepth]:
yield key
proc newLegacyTracer*(flags: set[TracerFlags]): LegacyTracer =
let trace = newJObject()
# make appear at the top of json object
trace["gas"] = %0
trace["failed"] = %false
trace["returnValue"] = %""
trace["structLogs"] = newJArray()
LegacyTracer(
flags: flags,
trace: trace
)
method capturePrepare*(ctx: LegacyTracer, comp: Computation, depth: int) {.gcsafe.} =
if depth >= ctx.storageKeys.len:
let prevLen = ctx.storageKeys.len
ctx.storageKeys.setLen(depth + 1)
for i in prevLen ..< ctx.storageKeys.len - 1:
ctx.storageKeys[i] = HashSet[UInt256]()
ctx.storageKeys[depth] = HashSet[UInt256]()
# Opcode level
method captureOpStart*(ctx: LegacyTracer, c: Computation,
fixed: bool, pc: int, op: Op, gas: GasInt,
depth: int): int {.gcsafe.} =
try:
let
j = newJObject()
ctx.trace["structLogs"].add(j)
j["op"] = %(($op).toUpperAscii)
j["pc"] = %(c.code.pc - 1)
j["depth"] = %(c.msg.depth + 1)
j["gas"] = %(gas)
ctx.gas = gas
# log stack
if TracerFlags.DisableStack notin ctx.flags:
let stack = newJArray()
for v in c.stack:
stack.add(%v.dumpHex())
j["stack"] = stack
# log memory
if TracerFlags.DisableMemory notin ctx.flags:
let mem = newJArray()
const chunkLen = 32
let numChunks = c.memory.len div chunkLen
for i in 0 ..< numChunks:
let memHex = c.memory.bytes.toOpenArray(i * chunkLen, (i + 1) * chunkLen - 1).toHex()
mem.add(%(memHex.toUpperAscii))
j["memory"] = mem
if TracerFlags.EnableAccount in ctx.flags:
case op
of Call, CallCode, DelegateCall, StaticCall:
if c.stack.len > 2:
ctx.accounts.incl c.stack[^2, EthAddress].expect("stack constains more than 2 elements")
of ExtCodeCopy, ExtCodeSize, Balance, SelfDestruct:
if c.stack.len > 1:
ctx.accounts.incl c.stack[^1, EthAddress].expect("stack is not empty")
else:
discard
if TracerFlags.DisableStorage notin ctx.flags:
if op == Sstore:
if c.stack.len > 1:
ctx.rememberStorageKey(c.msg.depth,
c.stack[^1, UInt256].expect("stack is not empty"))
result = ctx.trace["structLogs"].len - 1
except KeyError as ex:
error "LegacyTracer captureOpStart", msg=ex.msg
except ValueError as ex:
error "LegacyTracer captureOpStart", msg=ex.msg
method captureOpEnd*(ctx: LegacyTracer, c: Computation,
fixed: bool, pc: int, op: Op, gas: GasInt, refund: GasInt,
rData: openArray[byte],
depth: int, opIndex: int) {.gcsafe.} =
try:
let
j = ctx.trace["structLogs"].elems[opIndex]
# TODO: figure out how to get storage
# when contract execution interrupted by exception
if TracerFlags.DisableStorage notin ctx.flags:
var storage = newJObject()
if c.msg.depth < ctx.storageKeys.len:
var stateDB = c.vmState.stateDB
for key in ctx.storage(c.msg.depth):
let value = stateDB.getStorage(c.msg.contractAddress, key)
storage[key.dumpHex] = %(value.dumpHex)
j["storage"] = storage
j["gasCost"] = %(ctx.gas - gas)
if op in {Return, Revert} and TracerFlags.DisableReturnData notin ctx.flags:
let returnValue = %("0x" & toHex(c.output))
j["returnValue"] = returnValue
ctx.trace["returnValue"] = returnValue
except KeyError as ex:
error "LegacyTracer captureOpEnd", msg=ex.msg
except RlpError as ex:
error "LegacyTracer captureOpEnd", msg=ex.msg
method captureFault*(ctx: LegacyTracer, comp: Computation,
fixed: bool, pc: int, op: Op, gas: GasInt, refund: GasInt,
rData: openArray[byte],
depth: int, error: Opt[string]) {.gcsafe.} =
try:
if ctx.trace["structLogs"].elems.len > 0:
let j = ctx.trace["structLogs"].elems[^1]
j["error"] = %(comp.error.info)
j["gasCost"] = %(ctx.gas - gas)
ctx.trace["failed"] = %true
except KeyError as ex:
error "LegacyTracer captureOpEnd", msg=ex.msg
proc getTracingResult*(ctx: LegacyTracer): JsonNode =
ctx.trace
iterator tracedAccounts*(ctx: LegacyTracer): EthAddress =
for acc in ctx.accounts:
yield acc
iterator tracedAccountsPairs*(ctx: LegacyTracer): (int, EthAddress) =
var idx = 0
for acc in ctx.accounts:
yield (idx, acc)
inc idx
proc removeTracedAccounts*(ctx: LegacyTracer, accounts: varargs[EthAddress]) =
for acc in accounts:
ctx.accounts.excl acc