diff --git a/docs/_dapp/mobile-accounts.md b/docs/_dapp/mobile-accounts.md index 6ee50fc813..9d4bd39219 100644 --- a/docs/_dapp/mobile-accounts.md +++ b/docs/_dapp/mobile-accounts.md @@ -1,5 +1,6 @@ --- title: Mobile Account Management +sort_key: G --- To provide Ethereum integration for your mobile applications, the very first thing you diff --git a/docs/_dapp/mobile.md b/docs/_dapp/mobile.md index 2f550d5286..efcc107451 100644 --- a/docs/_dapp/mobile.md +++ b/docs/_dapp/mobile.md @@ -1,5 +1,6 @@ --- title: Mobile API +sort_key: F --- The Ethereum blockchain along with its two extension protocols Whisper and Swarm was diff --git a/docs/_dapp/native-accounts.md b/docs/_dapp/native-accounts.md index 37b7288f2e..e06b372a03 100644 --- a/docs/_dapp/native-accounts.md +++ b/docs/_dapp/native-accounts.md @@ -1,5 +1,6 @@ --- title: Go Account Management +sort_key: D --- To provide Ethereum integration for your native applications, the very first thing you diff --git a/docs/_dapp/native-bindings.md b/docs/_dapp/native-bindings.md index c8068207a1..1672e0addb 100644 --- a/docs/_dapp/native-bindings.md +++ b/docs/_dapp/native-bindings.md @@ -1,5 +1,6 @@ --- title: Go Contract Bindings +sort_key: E --- **[Please note, events are not yet implemented as they need some RPC subscription diff --git a/docs/_dapp/native.md b/docs/_dapp/native.md index a0d5fdc5fd..f248fc7522 100644 --- a/docs/_dapp/native.md +++ b/docs/_dapp/native.md @@ -1,5 +1,6 @@ --- title: Go API +sort_key: C --- The Ethereum blockchain along with its two extension protocols Whisper and Swarm was diff --git a/docs/_dapp/tracing-filtered.md b/docs/_dapp/tracing-filtered.md new file mode 100644 index 0000000000..97cbf3a859 --- /dev/null +++ b/docs/_dapp/tracing-filtered.md @@ -0,0 +1,345 @@ +--- +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 package used by Geth is [Goja](https://github.com/dop251/goja), 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 trancer 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