diff --git a/nimbus/evm/tracer/json_tracer.nim b/nimbus/evm/tracer/json_tracer.nim index e7fd9bd5e..be860a870 100644 --- a/nimbus/evm/tracer/json_tracer.nim +++ b/nimbus/evm/tracer/json_tracer.nim @@ -88,7 +88,10 @@ proc captureOpImpl(ctx: JsonTracer, pc: int, res["memory"] = mem if TracerFlags.DisableStack notin ctx.flags: - res["stack"] = ctx.stack + if ctx.stack.isNil: + res["stack"] = newJArray() + else: + res["stack"] = ctx.stack if TracerFlags.DisableReturnData notin ctx.flags: res["returnData"] = %(rData) diff --git a/tools/t8n/config.nim b/tools/t8n/config.nim index a96599546..a505b0493 100644 --- a/tools/t8n/config.nim +++ b/tools/t8n/config.nim @@ -28,9 +28,15 @@ type T8NConf* = object of RootObj traceEnabled* {. - desc: "Output full trace logs to files trace--.jsonl" - defaultValue: false - name: "trace" }: bool + desc: "Enable and set where to put full EVM trace logs" + longDesc: + "`stdout` - into the stdout output\n" & + "`stderr` - into the stderr output\n" & + " - into the file -.jsonl\n" & + "none - output.basedir/trace--.jsonl\n" + defaultValue: none(string) + defaultValueDesc: "disabled" + name: "trace" }: Option[string] traceMemory* {. desc: "Enable full memory dump in traces" @@ -166,7 +172,7 @@ proc convertToNimStyle(cmds: openArray[string]): seq[string] = const Copyright = "Copyright (c) 2022 Status Research & Development GmbH" - Version = "Nimbus-t8n 0.1.2" + Version = "Nimbus-t8n 0.2.2" # force the compiler to instantiate T8NConf.load # rather than have to export parseCmdArg diff --git a/tools/t8n/readme.md b/tools/t8n/readme.md index 10b947ede..75474d164 100644 --- a/tools/t8n/readme.md +++ b/tools/t8n/readme.md @@ -36,7 +36,11 @@ t8n [OPTIONS]... The following options are available: - --trace Output full trace logs to files trace--.jsonl [=false]. + --trace Enable and set where to put full EVM trace logs [=disabled]. + `stdout` - into the stdout output. + `stderr` - into the stderr output. + - into the file -.jsonl. + none - output.basedir/trace--.jsonl. --trace.memory Enable full memory dump in traces [=false]. --trace.nostack Disable stack output in traces [=false]. --trace.returndata Enable return data output in traces [=false]. diff --git a/tools/t8n/t8n_test.nim b/tools/t8n/t8n_test.nim index 8d7823847..6f958fbd6 100644 --- a/tools/t8n/t8n_test.nim +++ b/tools/t8n/t8n_test.nim @@ -25,6 +25,7 @@ type alloc : bool result: bool body : bool + trace : bool TestSpec = object name : string @@ -71,6 +72,9 @@ proc get(opt: T8nOutput): string = else: result.add(" --output.body") + if opt.trace: + result.add(" --trace stdout") + template exit(jsc: var JsonComparator, msg: string) = jsc.path = path jsc.error = msg @@ -137,16 +141,25 @@ proc runTest(appDir: string, spec: TestSpec): bool = return false if spec.expOut.len > 0: - let path = base / spec.expOut - let want = json.parseFile(path) - let have = json.parseJson(res) - var jsc = JsonComparator() - if not jsc.cmp(want, have, "root") and notRejectedError(jsc.path): - echo "test $1: output wrong, have \n$2\nwant\n$3\n" % - [spec.name, have.pretty, want.pretty] - echo "path: $1, error: $2" % - [jsc.path, jsc.error] - return false + if spec.expOut.endsWith(".json"): + let path = base / spec.expOut + let want = json.parseFile(path) + let have = json.parseJson(res) + var jsc = JsonComparator() + if not jsc.cmp(want, have, "root") and notRejectedError(jsc.path): + echo "test $1: output wrong, have \n$2\nwant\n$3\n" % + [spec.name, have.pretty, want.pretty] + echo "path: $1, error: $2" % + [jsc.path, jsc.error] + return false + else: + # compare as regular text + let path = base / spec.expOut + let want = readFile(path) + if want.replace("\x0D\x0A", "\n") != res: + echo "test $1: output wrong, have \n$2\nwant\n$3\n" % + [spec.name, res, want] + return false return true const @@ -484,6 +497,15 @@ const output: T8nOutput(alloc: true, result: true), expOut: "exp.json", ), + TestSpec( + name : "EVM tracer crash bug", + base : "testdata/00-519", + input : t8nInput( + "alloc.json", "txs.json", "env.json", "Shanghai", "0", + ), + output: T8nOutput(trace: true), + expOut: "exp.txt", + ), ] proc main() = diff --git a/tools/t8n/testdata/00-519/alloc.json b/tools/t8n/testdata/00-519/alloc.json new file mode 100644 index 000000000..8eefdf41d --- /dev/null +++ b/tools/t8n/testdata/00-519/alloc.json @@ -0,0 +1,5 @@ +{ + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x3635c9adc5dea00000" + } +} diff --git a/tools/t8n/testdata/00-519/env.json b/tools/t8n/testdata/00-519/env.json new file mode 100644 index 000000000..1dac71515 --- /dev/null +++ b/tools/t8n/testdata/00-519/env.json @@ -0,0 +1,20 @@ +{ + "currentCoinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentGasLimit": "100000000000000000", + "currentNumber": "2", + "currentTimestamp": "24", + "currentRandom": "0", + "currentDifficulty": "0", + "blockHashes": { + "0": "0xea2d7e0192d890c222f0302d972a02db0bf0c6d08257d73aa1210d08d24f30c3", + "1": "0x068f4313da4cb34b1b6b18ff37eb6ca9f7ad9d294357db81c75cb7790d25dd67" + }, + "ommers": [], + "withdrawals": [], + "parentDifficulty": "0", + "parentTimestamp": "12", + "parentBaseFee": "7", + "parentGasUsed": "0", + "parentGasLimit": "100000000000000000", + "parentUncleHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" +} diff --git a/tools/t8n/testdata/00-519/exp.txt b/tools/t8n/testdata/00-519/exp.txt new file mode 100644 index 000000000..d19b3514b --- /dev/null +++ b/tools/t8n/testdata/00-519/exp.txt @@ -0,0 +1,2 @@ +{"pc":0,"op":0,"gas":"0x0","gasCost":"0xfffffffffffecb68","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"STOP","error":"Blake2b F function invalid input"} +{"output":"0x","gasUsed":"0x13498","error":"Blake2b F function invalid input"} diff --git a/tools/t8n/testdata/00-519/txs.json b/tools/t8n/testdata/00-519/txs.json new file mode 100644 index 000000000..649472a4a --- /dev/null +++ b/tools/t8n/testdata/00-519/txs.json @@ -0,0 +1,16 @@ +[ + { + "type": "0x0", + "chainId": "0x1", + "nonce": "0x0", + "gasPrice": "0xa", + "gas": "0x186a0", + "to": "0x0000000000000000000000000000000000000009", + "value": "0x0", + "input": "0x", + "v": "0x26", + "r": "0x803b06b78b7bd29d0faf9401f2df5d71e8a445ad1ac0a45d2e5256ba23c43ed1", + "s": "0x6634f86b79da86a904b1900a52e470847ffe730ef4ec32a3b0f7eece7bfaae96", + "sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" + } +] \ No newline at end of file diff --git a/tools/t8n/transition.nim b/tools/t8n/transition.nim index 77a124e49..430d1e91b 100644 --- a/tools/t8n/transition.nim +++ b/tools/t8n/transition.nim @@ -147,18 +147,22 @@ proc calcLogsHash(receipts: openArray[Receipt]): Hash256 = logs.add rec.logs rlpHash(logs) -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 defaultTraceStream(conf: T8NConf, txIndex: int, txHash: Hash256): Stream = + let + txHash = "0x" & toLowerAscii($txHash) + baseDir = if conf.outputBaseDir.len > 0: + conf.outputBaseDir + else: + "." + fName = "$1/trace-$2-$3.jsonl" % [baseDir, $txIndex, txHash] + newFileStream(fName, fmWrite) -proc encodeHexInt(x: SomeInteger): JsonNode = - %("0x" & x.toHex.stripLeadingZeros.toLowerAscii) - -proc toHex(x: Hash256): string = - "0x" & x.data.toHex +proc traceToFileStream(path: string, txIndex: int): Stream = + # replace whatever `.ext` to `-${txIndex}.jsonl` + let + file = path.splitFile + fName = "$1/$2-$3.jsonl" % [file.dir, file.name, $txIndex] + newFileStream(fName, fmWrite) proc setupTrace(conf: T8NConf, txIndex: int, txHash: Hash256, vmState: BaseVMstate) = var tracerFlags = { @@ -169,22 +173,20 @@ proc setupTrace(conf: T8NConf, txIndex: int, txHash: Hash256, vmState: BaseVMsta TracerFlags.DisableReturnData } - if conf.traceEnabled: - if conf.traceMemory: tracerFlags.excl TracerFlags.DisableMemory - if conf.traceNostack: tracerFlags.incl TracerFlags.DisableStack - if conf.traceReturnData: tracerFlags.excl TracerFlags.DisableReturnData + if conf.traceMemory: tracerFlags.excl TracerFlags.DisableMemory + if conf.traceNostack: tracerFlags.incl TracerFlags.DisableStack + if conf.traceReturnData: tracerFlags.excl TracerFlags.DisableReturnData - let - txHash = "0x" & toLowerAscii($txHash) - baseDir = if conf.outputBaseDir.len > 0: - conf.outputBaseDir - else: - "." - fName = "$1/trace-$2-$3.jsonl" % [baseDir, $txIndex, txHash] - stream = newFileStream(fName, fmWrite) - tracerInst = newJsonTracer(stream, tracerFlags, false) - - vmState.tracer = tracerInst + let traceMode = conf.traceEnabled.get + let stream = if traceMode == "stdout": + newFileStream(stdout) + elif traceMode == "stderr": + newFileStream(stderr) + elif traceMode.len > 0: + traceToFileStream(traceMode, txIndex) + else: + defaultTraceStream(conf, txIndex, txHash) + vmState.tracer = newJsonTracer(stream, tracerFlags, false) proc closeTrace(vmState: BaseVMstate) = let tracer = JsonTracer(vmState.tracer) @@ -233,12 +235,12 @@ proc exec(ctx: var TransContext, ) continue - if conf.traceEnabled: + if conf.traceEnabled.isSome: setupTrace(conf, txIndex, rlpHash(tx), vmState) let rc = vmState.processTransaction(tx, sender, header) - if conf.traceEnabled: + if conf.traceEnabled.isSome: closeTrace(vmState) if rc.isErr: