diff --git a/nimbus/vm/interpreter_dispatch.nim b/nimbus/vm/interpreter_dispatch.nim index a64b9bd62..a1d399f55 100644 --- a/nimbus/vm/interpreter_dispatch.nim +++ b/nimbus/vm/interpreter_dispatch.nim @@ -11,7 +11,7 @@ import ./interpreter/[opcode_values, opcodes_impl, vm_forks, gas_costs, gas_meter, utils/macros_gen_opcodes], ./code_stream, ../vm_types, ../errors, - ./stack, ./computation, terminal # Those are only needed for logging + ./stack, ./computation, ./transaction_tracer, terminal # Those are only needed for logging func invalidInstruction*(computation: var BaseComputation) {.inline.} = raise newException(ValueError, "Invalid instruction, received an opcode not implemented in the current fork.") @@ -191,12 +191,20 @@ proc opTableToCaseStmt(opTable: array[Op, NimNode], computation: NimNode): NimNo let asOp = quote do: Op(`op`) # TODO: unfortunately when passing to runtime, ops are transformed into int if BaseGasCosts[op].kind == GckFixed: quote do: + if `computation`.tracingEnabled: + `computation`.tracer.traceOpCodeStarted(`computation`, $`asOp`) `computation`.gasMeter.consumeGas(`computation`.gasCosts[`asOp`].cost, reason = $`asOp`) `opImpl`(`computation`) + if `computation`.tracingEnabled: + `computation`.tracer.traceOpCodeEnded(`computation`) `instr` = `computation`.code.next() else: quote do: + if `computation`.tracingEnabled: + `computation`.tracer.traceOpCodeStarted(`computation`, $`asOp`) `opImpl`(`computation`) + if `computation`.tracingEnabled: + `computation`.tracer.traceOpCodeEnded(`computation`) when `asOp` in {Return, Revert, SelfDestruct}: break else: diff --git a/nimbus/vm/transaction_tracer.nim b/nimbus/vm/transaction_tracer.nim new file mode 100644 index 000000000..05e1ad35a --- /dev/null +++ b/nimbus/vm/transaction_tracer.nim @@ -0,0 +1,40 @@ +import + json, strutils, + eth_common, stint, byteutils, + ../vm_types, memory, stack + +proc initTrace(t: var TransactionTracer) = + t.trace = newJObject() + t.trace["structLogs"] = newJArray() + +proc traceOpCodeStarted*(t: var TransactionTracer, c: BaseComputation, op: string) = + if unlikely t.trace.isNil: + t.initTrace() + + let j = newJObject() + t.trace["structLogs"].add(j) + + j["op"] = %op.toUpperAscii + j["pc"] = %(c.code.pc - 1) + j["depth"] = %1 # stub + j["gas"] = %c.gasMeter.gasRemaining + t.gasRemaining = c.gasMeter.gasRemaining + + # log stack + let st = newJArray() + for v in c.stack.values: + st.add(%v.dumpHex()) + j["stack"] = st + # log memory + let mem = newJArray() + const chunkLen = 32 + let numChunks = c.memory.len div chunkLen + for i in 0 ..< numChunks: + mem.add(%c.memory.bytes.toOpenArray(i * chunkLen, (i + 1) * chunkLen - 1).toHex()) + j["memory"] = mem + # TODO: log storage + +proc traceOpCodeEnded*(t: var TransactionTracer, c: BaseComputation) = + let j = t.trace["structLogs"].elems[^1] + j["gasCost"] = %(t.gasRemaining - c.gasMeter.gasRemaining) + diff --git a/nimbus/vm_types.nim b/nimbus/vm_types.nim index 1cdafb0b4..6597e2904 100644 --- a/nimbus/vm_types.nim +++ b/nimbus/vm_types.nim @@ -6,7 +6,7 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - tables, + tables, json, eth_common, ./constants, ./vm_state, ./vm/[memory, stack, code_stream], @@ -35,6 +35,8 @@ type precompiles*: Table[string, Opcode] gasCosts*: GasCosts # TODO - will be hidden at a lower layer opCodeExec*: OpcodeExecutor + tracingEnabled*: bool + tracer*: TransactionTracer Error* = ref object info*: string @@ -105,3 +107,8 @@ type createAddress*: EthAddress codeAddress*: EthAddress flags*: MsgFlags + + TransactionTracer* = object + trace*: JsonNode + gasRemaining*: GasInt +