From 69424d6bcdfe06e8089b76e142be09256745e8e0 Mon Sep 17 00:00:00 2001 From: Joseph Cook <33655003+jmcook1186@users.noreply.github.com> Date: Thu, 7 Jul 2022 12:46:38 +0100 Subject: [PATCH] docs: update EVM tracing docs (#25242) Improved tracing docs. Added section about native tracing. Co-authored-by: Sina Mahmoodi --- docs/_dapp/custom-tracer.md | 458 +++++++++++++++++++++++++++++++++ docs/_dapp/tracing-filtered.md | 343 ------------------------ docs/_dapp/tracing.md | 247 ++++++++++-------- 3 files changed, 598 insertions(+), 450 deletions(-) create mode 100644 docs/_dapp/custom-tracer.md delete mode 100644 docs/_dapp/tracing-filtered.md diff --git a/docs/_dapp/custom-tracer.md b/docs/_dapp/custom-tracer.md new file mode 100644 index 0000000000..4817ed4053 --- /dev/null +++ b/docs/_dapp/custom-tracer.md @@ -0,0 +1,458 @@ +--- +title: Custom EVM tracer +sort_key: B +--- + +In addition to the default opcode tracer and the built-in tracers, Geth offers the possibility to write custom code +that hook to events in the EVM to process and return the data in a consumable format. Custom tracers can be +written either in Javascript or Go. JS tracers are good for quick prototyping and experimentation as well as for +less intensive applications. Go tracers are performant but require the tracer to be compiled together with the Geth source code. + +* TOC +{:toc} + +## Custom Javascript tracing + +Transaction traces include the complete status of the EVM at every point during the transaction execution, which +can be a very large amount of data. Often, users are only interested in a small subset of that data. Javascript trace +filters are available to isolate the useful information. Detailed information about `debug_traceTransaction` and its +component parts is available in the [reference documentation](/docs/rpc/ns-debug#debug_tracetransaction). + +### A simple filter + +Filters are Javascript functions that select information from the trace to persist and discard based on some +conditions. The following Javascript function returns only the sequence of opcodes executed by the transaction as a +comma-separated list. The function could be written directly in the Javascript console, but it is cleaner to +write it in a separate re-usable file and load it into the console. + +1. Create a file, `filterTrace_1.js`, with this content: + + ```javascript + + tracer = function(tx) { + return debug.traceTransaction(tx, {tracer: + '{' + + 'retVal: [],' + + 'step: function(log,db) {this.retVal.push(log.getPC() + ":" + log.op.toString())},' + + 'fault: function(log,db) {this.retVal.push("FAULT: " + JSON.stringify(log))},' + + 'result: function(ctx,db) {return this.retVal}' + + '}' + }) // return debug.traceTransaction ... + } // tracer = function ... + + ``` + +2. Run the [JavaScript console](https://geth.ethereum.org/docs/interface/javascript-console). + +3. Get the hash of a recent transaction from a node or block explorer. + +4. Run this command to run the script: + + ```javascript + loadScript("filterTrace_1.js") + ``` + +5. Run the tracer from the script. Be patient, it could take a long time. + + ```javascript + tracer("") + ``` + + The bottom of the output looks similar to: + ```sh + "3366:POP", "3367:JUMP", "1355:JUMPDEST", "1356:PUSH1", "1358:MLOAD", "1359:DUP1", "1360:DUP3", "1361:ISZERO", "1362:ISZERO", + "1363:ISZERO", "1364:ISZERO", "1365:DUP2", "1366:MSTORE", "1367:PUSH1", "1369:ADD", "1370:SWAP2", "1371:POP", "1372:POP", "1373:PUSH1", + "1375:MLOAD", "1376:DUP1", "1377:SWAP2", "1378:SUB", "1379:SWAP1", "1380:RETURN" + ``` + +6. Run this line to get a more readable output with each string in its own line. + + ```javascript + console.log(JSON.stringify(tracer(""), null, 2)) + ``` + +More information about the `JSON.stringify` function is available +[here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify). + +The commands above worked by calling the same `debug.traceTransaction` function that was previously +explained in [basic traces](https://geth.ethereum.org/docs/dapp/tracing), but with a new parameter, `tracer`. +This parameter takes the JavaScript object formated as a string. In the case of the trace above, it is: + +```javascript +{ + retVal: [], + step: function(log,db) {this.retVal.push(log.getPC() + ":" + log.op.toString())}, + fault: function(log,db) {this.retVal.push("FAULT: " + JSON.stringify(log))}, + result: function(ctx,db) {return this.retVal} +} +``` +This object has three member functions: + +- `step`, called for each opcode. +- `fault`, called if there is a problem in the execution. +- `result`, called to produce the results that are returned by `debug.traceTransaction` after the execution is done. + +In this case, `retVal` is used to store the list of strings to return in `result`. + +The `step` function adds to `retVal` the program counter and the name of the opcode there. Then, in `result`, this +list is returned to be sent to the caller. + + +### Filtering with conditions + +For actual filtered tracing we need an `if` statement to only log relevant information. For example, to isolate +the transaction's interaction with storage, the following tracer could be used: + +```javascript +tracer = function(tx) { + return debug.traceTransaction(tx, {tracer: + '{' + + 'retVal: [],' + + 'step: function(log,db) {' + + ' if(log.op.toNumber() == 0x54) ' + + ' this.retVal.push(log.getPC() + ": SLOAD");' + + ' if(log.op.toNumber() == 0x55) ' + + ' this.retVal.push(log.getPC() + ": SSTORE");' + + '},' + + 'fault: function(log,db) {this.retVal.push("FAULT: " + JSON.stringify(log))},' + + 'result: function(ctx,db) {return this.retVal}' + + '}' + }) // return debug.traceTransaction ... +} // tracer = function ... +``` + +The `step` function here looks at the opcode number of the op, and only pushes an entry if the opcode is +`SLOAD` or `SSTORE` ([here is a list of EVM opcodes and their numbers](https://github.com/wolflo/evm-opcodes)). +We could have used `log.op.toString()` instead, but it is faster to compare numbers rather than strings. + +The output looks similar to this: + +```javascript +[ + "5921: SLOAD", + . + . + . + "2413: SSTORE", + "2420: SLOAD", + "2475: SSTORE", + "6094: SSTORE" +] +``` + + +### Stack Information + +The trace above reports the program counter (PC) and whether the program read from storage or wrote to it. +That alone isn't particularly useful. To know more, the `log.stack.peek` function can be used to peek +into the stack. `log.stack.peek(0)` is the stack top, `log.stack.peek(1)` the entry below it, etc. + +The values returned by `log.stack.peek` are Go `big.Int` objects. By default they are converted to JavaScript +floating point numbers, so you need `toString(16)` to get them as hexadecimals, which is how 256-bit values such as +storage cells and their content are normally represented. + +#### Storage Information + +The function below provides a trace of all the storage operations and their parameters. This gives +a more complete picture of the program's interaction with storage. + +```javascript +tracer = function(tx) { + return debug.traceTransaction(tx, {tracer: + '{' + + 'retVal: [],' + + 'step: function(log,db) {' + + ' if(log.op.toNumber() == 0x54) ' + + ' this.retVal.push(log.getPC() + ": SLOAD " + ' + + ' log.stack.peek(0).toString(16));' + + ' if(log.op.toNumber() == 0x55) ' + + ' this.retVal.push(log.getPC() + ": SSTORE " +' + + ' log.stack.peek(0).toString(16) + " <- " +' + + ' log.stack.peek(1).toString(16));' + + '},' + + 'fault: function(log,db) {this.retVal.push("FAULT: " + JSON.stringify(log))},' + + 'result: function(ctx,db) {return this.retVal}' + + '}' + }) // return debug.traceTransaction ... +} // tracer = function ... + +``` + +The output is similar to: + +```javascript +[ + "5921: SLOAD 0", + . + . + . + "2413: SSTORE 3f0af0a7a3ed17f5ba6a93e0a2a05e766ed67bf82195d2dd15feead3749a575d <- fb8629ad13d9a12456", + "2420: SLOAD cc39b177dd3a7f50d4c09527584048378a692aed24d31d2eabeddb7f3c041870", + "2475: SSTORE cc39b177dd3a7f50d4c09527584048378a692aed24d31d2eabeddb7f3c041870 <- 358c3de691bd19", + "6094: SSTORE 0 <- 1" +] +``` + +#### Operation Results + +One piece of information missing from the function above is the result on an `SLOAD` operation. The +state we get inside `log` is the state prior to the execution of the opcode, so that value is not +known yet. For more operations we can figure it out for ourselves, but we don't have access to the +storage, so here we can't. + +The solution is to have a flag, `afterSload`, which is only true in the opcode right after an +`SLOAD`, when we can see the result at the top of the stack. + +```javascript +tracer = function(tx) { + return debug.traceTransaction(tx, {tracer: + '{' + + 'retVal: [],' + + 'afterSload: false,' + + 'step: function(log,db) {' + + ' if(this.afterSload) {' + + ' this.retVal.push(" Result: " + ' + + ' log.stack.peek(0).toString(16)); ' + + ' this.afterSload = false; ' + + ' } ' + + ' if(log.op.toNumber() == 0x54) {' + + ' this.retVal.push(log.getPC() + ": SLOAD " + ' + + ' log.stack.peek(0).toString(16));' + + ' this.afterSload = true; ' + + ' } ' + + ' if(log.op.toNumber() == 0x55) ' + + ' this.retVal.push(log.getPC() + ": SSTORE " +' + + ' log.stack.peek(0).toString(16) + " <- " +' + + ' log.stack.peek(1).toString(16));' + + '},' + + 'fault: function(log,db) {this.retVal.push("FAULT: " + JSON.stringify(log))},' + + 'result: function(ctx,db) {return this.retVal}' + + '}' + }) // return debug.traceTransaction ... +} // tracer = function ... +``` + +The output now contains the result in the line that follows the `SLOAD`. + +```javascript +[ + "5921: SLOAD 0", + " Result: 1", + . + . + . + "2413: SSTORE 3f0af0a7a3ed17f5ba6a93e0a2a05e766ed67bf82195d2dd15feead3749a575d <- fb8629ad13d9a12456", + "2420: SLOAD cc39b177dd3a7f50d4c09527584048378a692aed24d31d2eabeddb7f3c041870", + " Result: 0", + "2475: SSTORE cc39b177dd3a7f50d4c09527584048378a692aed24d31d2eabeddb7f3c041870 <- 358c3de691bd19", + "6094: SSTORE 0 <- 1" +] +``` + +### Dealing With Calls Between Contracts + +So the storage has been treated as if there are only 2256 cells. However, that is not true. +Contracts can call other contracts, and then the storage involved is the storage of the other contract. +We can see the address of the current contract in `log.contract.getAddress()`. This value is the execution +context - the contract whose storage we are using - even when code from another contract is executed (by using +[`CALLCODE` or `DELEGATECALL`][solidity-delcall]). + +However, `log.contract.getAddress()` returns an array of bytes. To convert this to the familiar hexadecimal +representation of Ethereum addresses, `this.byteHex()` and `array2Hex()` can be used. + +```javascript +tracer = function(tx) { + return debug.traceTransaction(tx, {tracer: + '{' + + 'retVal: [],' + + 'afterSload: false,' + + 'callStack: [],' + + + 'byte2Hex: function(byte) {' + + ' if (byte < 0x10) ' + + ' return "0" + byte.toString(16); ' + + ' return byte.toString(16); ' + + '},' + + + 'array2Hex: function(arr) {' + + ' var retVal = ""; ' + + ' for (var i=0; i 0 { + t.env.Cancel() + return + } + + name := op.String() + if _, ok := t.counts[name]; !ok { + t.counts[name] = 0 + } + t.counts[name]++ +} + +// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). +func (t *opcounter) CaptureEnter(op vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {} + +// CaptureExit is called when EVM exits a scope, even if the scope didn't +// execute any code. +func (t *opcounter) CaptureExit(output []byte, gasUsed uint64, err error) {} + +// CaptureFault implements the EVMLogger interface to trace an execution fault. +func (t *opcounter) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {} + +// CaptureEnd is called after the call finishes to finalize the tracing. +func (t *opcounter) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {} + +func (*opcounter) CaptureTxStart(gasLimit uint64) {} + +func (*opcounter) CaptureTxEnd(restGas uint64) {} + +// GetResult returns the json-encoded nested list of call traces, and any +// error arising from the encoding or forceful termination (via `Stop`). +func (t *opcounter) GetResult() (json.RawMessage, error) { + res, err := json.Marshal(t.counts) + if err != nil { + return nil, err + } + return res, t.reason +} + +// Stop terminates execution of the tracer at the first opportune moment. +func (t *opcounter) Stop(err error) { + t.reason = err + atomic.StoreUint32(&t.interrupt, 1) +} +``` + +As can be seen every method of the [EVMLogger interface](https://pkg.go.dev/github.com/ethereum/go-ethereum/core/vm#EVMLogger) needs to be implemented (even if empty). Key parts to notice are the `init()` function which registers the tracer in Geth, the `CaptureState` hook where the opcode counts are incremented and `GetResult` where the result is serialized and delivered. To test this out the source is first compiled with `make geth`. Then in the console it can be invoked through the usual API methods by passing in the name it was registered under: + +```console +> debug.traceTransaction('0x7ae446a7897c056023a8104d254237a8d97783a92900a7b0f7db668a9432f384', { tracer: 'opcounter' }) +{ + ADD: 4, + AND: 3, + CALLDATALOAD: 2, + ... +} +``` + +[solidity-delcall]:https://docs.soliditylang.org/en/v0.8.14/introduction-to-smart-contracts.html#delegatecall-callcode-and-libraries +[debug-docs]: /docs/rpc/ns-debug diff --git a/docs/_dapp/tracing-filtered.md b/docs/_dapp/tracing-filtered.md deleted file mode 100644 index 674377aa8d..0000000000 --- a/docs/_dapp/tracing-filtered.md +++ /dev/null @@ -1,343 +0,0 @@ ---- -title: Filtered Tracing -sort_key: B ---- - -In the previous section you learned how to create a complete trace. However, those traces can include the complete status of the EVM at every point -in the execution, which is huge. Usually you are only interested in a small subset of this information. To get it, you can specify a JavaScript filter. - -**Note:** The JavaScript interpreter used by Geth is [duktape](https://duktape.org), which is only up to the -[ECMAScript 5.1 standard](https://262.ecma-international.org/5.1/). This means we cannot use [arrow functions](https://www.w3schools.com/js/js_arrow_function.asp) -and [template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals). - - -## Running a Simple Trace - -1. Create a file, `filterTrace_1.js`, with this content: - - ```javascript - - tracer = function(tx) { - return debug.traceTransaction(tx, {tracer: - '{' + - 'retVal: [],' + - 'step: function(log,db) {this.retVal.push(log.getPC() + ":" + log.op.toString())},' + - 'fault: function(log,db) {this.retVal.push("FAULT: " + JSON.stringify(log))},' + - 'result: function(ctx,db) {return this.retVal}' + - '}' - }) // return debug.traceTransaction ... - } // tracer = function ... - - ``` - - We could specify this function directly in the JavaScript console, but it would be unwieldy and difficult - to edit. - -2. Run the [JavaScript console](https://geth.ethereum.org/docs/interface/javascript-console). -3. Get the hash of a recent transaction. For example, if you use the Goerli network, you can get such a value - [here](https://goerli.etherscan.io/). -4. Run this command to run the script: - - ```javascript - loadScript("filterTrace_1.js") - ``` - -5. Run the tracer from the script. Be patient, it could take a long time. - - ```javascript - tracer("") - ``` - - The bottom of the output looks similar to: - ```json - "3366:POP", "3367:JUMP", "1355:JUMPDEST", "1356:PUSH1", "1358:MLOAD", "1359:DUP1", "1360:DUP3", "1361:ISZERO", "1362:ISZERO", - "1363:ISZERO", "1364:ISZERO", "1365:DUP2", "1366:MSTORE", "1367:PUSH1", "1369:ADD", "1370:SWAP2", "1371:POP", "1372:POP", "1373:PUSH1", - "1375:MLOAD", "1376:DUP1", "1377:SWAP2", "1378:SUB", "1379:SWAP1", "1380:RETURN"] - ``` - -6. This output isn't very readable. Run this line to get a more readable output with each string in its own line. - - ```javascript - console.log(JSON.stringify(tracer(""), null, 2)) - ``` - - You can read about the `JSON.stringify` function - [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify). If we just - return the output we get `\n` for newlines, which is why we need to use `console.log`. - -### How Does It Work? - -We call the same `debug.traceTransaction` function we use for [basic traces](https://geth.ethereum.org/docs/dapp/tracing), but -with a new parameter, `tracer`. This parameter is a string that is the JavaScript object we use. In the case of the trace -above, it is: - -```javascript -{ - retVal: [], - step: function(log,db) {this.retVal.push(log.getPC() + ":" + log.op.toString())}, - fault: function(log,db) {this.retVal.push("FAULT: " + JSON.stringify(log))}, - result: function(ctx,db) {return this.retVal} -} -``` - -This object has to have three member functions: - -- `step`, called for each opcode -- `fault`, called if there is a problem in the execution -- `result`, called to produce the results that are returned by `debug.traceTransaction` after the execution is done - -It can have additional members. In this case, we use `retVal` to store the list of strings that we'll return in `result`. - -The `step` function here adds to `retVal` the program counter and the name of the opcode there. Then, in `result`, we return this -list to be sent to the caller. - - -## Actual Filtering - -For actual filtered tracing we need an `if` statement to only log relevant information. For example, if we are interested in -the transaction's interaction with storage, we might use: - -```javascript -tracer = function(tx) { - return debug.traceTransaction(tx, {tracer: - '{' + - 'retVal: [],' + - 'step: function(log,db) {' + - ' if(log.op.toNumber() == 0x54) ' + - ' this.retVal.push(log.getPC() + ": SLOAD");' + - ' if(log.op.toNumber() == 0x55) ' + - ' this.retVal.push(log.getPC() + ": SSTORE");' + - '},' + - 'fault: function(log,db) {this.retVal.push("FAULT: " + JSON.stringify(log))},' + - 'result: function(ctx,db) {return this.retVal}' + - '}' - }) // return debug.traceTransaction ... -} // tracer = function ... -``` - -The `step` function here looks at the opcode number of the op, and only pushes an entry if the opcode is -`SLOAD` or `SSTORE` ([here is a list of EVM opcodes and their numbers](https://github.com/wolflo/evm-opcodes)). -We could have used `log.op.toString()` instead, but it is faster to compare numbers rather than strings. - -The output looks similar to this: - -```javascript -[ - "5921: SLOAD", - . - . - . - "2413: SSTORE", - "2420: SLOAD", - "2475: SSTORE", - "6094: SSTORE" -] -``` - - -## Stack Information - -The trace above tells us the program counter (PC) and whether the program read from storage or wrote to it. That -isn't very useful. To know more, you can use the `log.stack.peek` function to peek into the stack. `log.stack.peek(0)` -is the stack top, `log.stack.peek(1)` the entry below it, etc. The values returned by `log.stack.peek` are -Go `big.Int` objects. By default they are converted to JavaScript floating point numbers, so you need -`toString(16)` to get them as hexadecimals, which is how we normally represent 256-bit values such as -storage cells and their content. - -```javascript -tracer = function(tx) { - return debug.traceTransaction(tx, {tracer: - '{' + - 'retVal: [],' + - 'step: function(log,db) {' + - ' if(log.op.toNumber() == 0x54) ' + - ' this.retVal.push(log.getPC() + ": SLOAD " + ' + - ' log.stack.peek(0).toString(16));' + - ' if(log.op.toNumber() == 0x55) ' + - ' this.retVal.push(log.getPC() + ": SSTORE " +' + - ' log.stack.peek(0).toString(16) + " <- " +' + - ' log.stack.peek(1).toString(16));' + - '},' + - 'fault: function(log,db) {this.retVal.push("FAULT: " + JSON.stringify(log))},' + - 'result: function(ctx,db) {return this.retVal}' + - '}' - }) // return debug.traceTransaction ... -} // tracer = function ... - -``` - -This function gives you a trace of all the storage operations, and show you their parameters. This gives -you a more complete picture of the program's interaction with storage. The output is similar to: - -```javascript -[ - "5921: SLOAD 0", - . - . - . - "2413: SSTORE 3f0af0a7a3ed17f5ba6a93e0a2a05e766ed67bf82195d2dd15feead3749a575d <- fb8629ad13d9a12456", - "2420: SLOAD cc39b177dd3a7f50d4c09527584048378a692aed24d31d2eabeddb7f3c041870", - "2475: SSTORE cc39b177dd3a7f50d4c09527584048378a692aed24d31d2eabeddb7f3c041870 <- 358c3de691bd19", - "6094: SSTORE 0 <- 1" -] -``` - -## Operation Results - -One piece of information missing from the function above is the result on an `SLOAD` operation. The -state we get inside `log` is the state prior to the execution of the opcode, so that value is not -known yet. For more operations we can figure it out for ourselves, but we don't have access to the -storage, so here we can't. - -The solution is to have a flag, `afterSload`, which is only true in the opcode right after an -`SLOAD`, when we can see the result at the top of the stack. - -```javascript -tracer = function(tx) { - return debug.traceTransaction(tx, {tracer: - '{' + - 'retVal: [],' + - 'afterSload: false,' + - 'step: function(log,db) {' + - ' if(this.afterSload) {' + - ' this.retVal.push(" Result: " + ' + - ' log.stack.peek(0).toString(16)); ' + - ' this.afterSload = false; ' + - ' } ' + - ' if(log.op.toNumber() == 0x54) {' + - ' this.retVal.push(log.getPC() + ": SLOAD " + ' + - ' log.stack.peek(0).toString(16));' + - ' this.afterSload = true; ' + - ' } ' + - ' if(log.op.toNumber() == 0x55) ' + - ' this.retVal.push(log.getPC() + ": SSTORE " +' + - ' log.stack.peek(0).toString(16) + " <- " +' + - ' log.stack.peek(1).toString(16));' + - '},' + - 'fault: function(log,db) {this.retVal.push("FAULT: " + JSON.stringify(log))},' + - 'result: function(ctx,db) {return this.retVal}' + - '}' - }) // return debug.traceTransaction ... -} // tracer = function ... -``` - -The output now contains the result in the line that follows the `SLOAD`. We could have also modified the `SLOAD` -line itself, but that would have been a bit more work. - - -```javascript -[ - "5921: SLOAD 0", - " Result: 1", - . - . - . - "2413: SSTORE 3f0af0a7a3ed17f5ba6a93e0a2a05e766ed67bf82195d2dd15feead3749a575d <- fb8629ad13d9a12456", - "2420: SLOAD cc39b177dd3a7f50d4c09527584048378a692aed24d31d2eabeddb7f3c041870", - " Result: 0", - "2475: SSTORE cc39b177dd3a7f50d4c09527584048378a692aed24d31d2eabeddb7f3c041870 <- 358c3de691bd19", - "6094: SSTORE 0 <- 1" -] -``` - - -## Dealing With Calls Between Contracts - -So far we have treated the storage as if there are only 2^256 cells. However, that is not true. Contracts -can call other contracts, and then the storage involved is the storage of the other contract. We can see -the address of the current contract in `log.contract.getAddress()`. This value is the execution context, -the contract whose storage we are using, even when we use code from another contract (by using -`CALLCODE` or `DELEGATECODE`). - -However, `log.contract.getAddress()` returns an array of bytes. We use `this.byteHex()` and `array2Hex()` -to convert this array to the hexadecimal representation we usually use to identify contracts. - -```javascript -tracer = function(tx) { - return debug.traceTransaction(tx, {tracer: - '{' + - 'retVal: [],' + - 'afterSload: false,' + - 'callStack: [],' + - - 'byte2Hex: function(byte) {' + - ' if (byte < 0x10) ' + - ' return "0" + byte.toString(16); ' + - ' return byte.toString(16); ' + - '},' + - - 'array2Hex: function(arr) {' + - ' var retVal = ""; ' + - ' for (var i=0; i:1:23(13) +``` -1. You have done a fast-sync, pivot block `P` where `P <= B`. -2. You have done a fast-sync, pivot block `P` where `P > B`. -3. You have done a full-sync, with pruning -4. You have done a full-sync, without pruning (`--gcmode=archive`) +The pruning behaviour, and consequently the state availability and tracing capability of +a node depends on its sync and pruning configuration. The 'oldest' block after which +state is immediately available, and before which state is not immediately available, +is known as the "pivot block". There are then several possible cases for a trace request +on a Geth node. -Here's what happens in each respective case: +For tracing a transaction in block `B` where the pivot block is `P` can regenerate the desired +state by replaying blocks from the last : -1. Geth will regenerate the desired state by replaying blocks from the closest point in - time before `B` where it has full state. This defaults to `128` blocks max, but you can - specify more in the actual call `... "reexec":1000 .. }` to the tracer. -2. Sorry, can't be done without replaying from genesis. -3. Same as 1) -4. Does not need to replay anything, can immediately load up the state and serve the request. +1. a fast-sync'd node can regenerate the desired state by replaying blocks from the most recent +checkpoint between `P` and `B` as long as `P` < `B`. If `P` > `B` there is no available checkpoint +and the state cannot be regenerated without replying the chain from genesis. +2. a fully sync'd node can regenerate the desired state by replaying blocks from the last available +full state before `B`. A fully sync'd node re-executes all blocks from genesis, so checkpoints are available +across the entire history of the chain. However, database pruning discards older data, moving `P` to a more +recent position in the chain. If `P` > `B` there is no available checkpoint and the state cannot be +regenerated without replaying the chain from genesis. + +3. A fully-sync'd node without pruning (i.e. an archive node configured with `--gcmode=archive`) +does not need to replay anything, it can immediately load up any state and serve the request for any `B`. + +The time taken to regenerate a specific state increases with the distance between `P` and `B`. If the distance +between `P` and `B` is large, the regeneration time can be substantial. + +## Summary + +This page covered the concept of EVM tracing and how to generate traces with the default opcode-based tracers using RPC. +More advanced usage is possible, including using other built-in tracers as well as writing [custom tracing](/docs/dapp/custom-tracer) code in Javascript +and Go. The API as well as the JS tracing hooks are defined in [the reference](/docs/rpc/ns-debug#debug_traceTransaction). + + +[transactions]: https://ethereum.org/en/developers/docs/transactions +[evm]: https://ethereum.org/en/developers/docs/evm