go-ethereum

skip to content

Built-in tracers

Last edited on April 5, 2024

Geth comes bundled with a choice of tracers that can be invoked via the tracing API. Some of these built-in tracers are implemented natively in Go, and others in Javascript. The default tracer is the opcode logger (otherwise known as struct logger) which is the default tracer for all the methods. Other tracers have to be specified by passing their name to the tracer parameter in the API call.

Struct/opcode logger

The struct logger (aka opcode logger) is a native Go tracer which executes a transaction and emits the opcode and execution context at every step. This is the tracer that will be used when no name is passed to the API, e.g. debug.traceTransaction(<txhash>). The following information is emitted at each step:

fieldtypedescription
pcuint64program counter
opbyteopcode to be executed
gasuint64remaining gas
gasCostuint64cost for executing op
memory[]byteEVM memory. Enabled via enableMemory
memSizeintSize of memory
stack[]uint256EVM stack. Disabled via disableStack
returnData[]byteLast call's return data. Enabled via enableReturnData
storagemap[hash]hashStorage slots of current contract read from and written to. Only emitted for SLOAD and SSTORE. Disabled via disableStorage
depthintCurrent call depth
refunduint64Refund counter
errorstringError message if any

Note that the fields memory, stack, returnData, and storage have dynamic size and depending on the exact transaction they could grow large in size. This is specially true for memory which could blow up the trace size. It is recommended to keep them disabled unless they are explicitly required for a given use case.

opcode config

  • enableMemory: BOOL. Setting this to true will enable memory capture (default = false).
  • disableStack: BOOL. Setting this to true will disable stack capture (default = false).
  • disableStorage: BOOL. Setting this to true will disable storage capture (default = false).
  • enableReturnData: BOOL. Setting this to true will enable return data capture (default = false).
  • debug: BOOL. Setting this to true will print output during capture end (default = false).
  • limit: INTEGER. Setting this to a positive integer will limit the number of steps captured (default = 0, no limit).

It is also possible to configure the trace by passing Boolean (true/false) values for four parameters that tweak the verbosity of the trace. By default, the EVM memory and Return data are not reported but the EVM stack and EVM storage are. To report the maximum amount of data:

enableMemory: true
disableStack: false
disableStorage: false
enableReturnData: true

An example call:

debug.traceTransaction('0xfc9359e49278b7ba99f59edac0e3de49956e46e530a53c15aa71226b7aa92c6f', {
  enableMemory: true,
  disableStack: false,
  disableStorage: false,
  enableReturnData: true
});

Return:

{ "gas":25523, "failed":false, "returnValue":"", "structLogs":[ { "pc":0, "op":"PUSH1", "gas":64580, "gasCost":3, "depth":1, "error":null, "stack":[ ], "memory":null, "storage":{ } }, { "pc":2, "op":"PUSH1", "gas":64577, "gasCost":3, "depth":1, "error":null, "stack":[ "0000000000000000000000000000000000000000000000000000000000000060" ], "memory":null, "storage":{ } }, ...

Native tracers

The following tracers are implemented in Go. This means they are much more performant than other tracers that are written in Javascript. The tracers are selected by passing their name to the tracer parameter when invoking a tracing API method, e.g. debug.traceTransaction(<txhash>, { tracer: 'callTracer' }).

4byteTracer

Solidity contract functions are addressed using the first four byte of the Keccak-256 hash of their signature. Therefore when calling the function of a contract, the caller must send this function selector as well as the ABI-encoded arguments as call data.

The 4byteTracer collects the function selectors of every function executed in the lifetime of a transaction, along with the size of the supplied call data. The result is a map[string]int where the keys are SELECTOR-CALLDATASIZE and the values are number of occurrences of this key. For example:

Example call:

debug.traceTransaction( "0x214e597e35da083692f5386141e69f47e973b2c56e7a8073b1ea08fd7571e9de", {tracer: "4byteTracer"})

Return:

{ "0x27dc297e-128": 1, "0x38cc4831-0": 2, "0x524f3889-96": 1, "0xadf59f99-288": 1, "0xc281d19e-0": 1 }

callTracer

The callTracer tracks all the call frames executed during a transaction, including depth 0. The result will be a nested list of call frames, resembling how EVM works. They form a tree with the top-level call at root and sub-calls as children of the higher levels. Each call frame has the following fields:

fieldtypedescription
typestringCALL or CREATE
fromstringaddress
tostringaddress
valuestringhex-encoded amount of value transfer
gasstringhex-encoded gas provided for call
gasUsedstringhex-encoded gas used during call
inputstringcall data
outputstringreturn data
errorstringerror, if any
revertReasonstringSolidity revert reason, if any
calls[]callframelist of sub-calls

Example Call:

> debug.traceTransaction("0x44bed3dc0f584b2a2ab32f5e2948abaaca13917eeae7ae3b959de3371a6e9a95", {tracer: 'callTracer'})

Return:

{ calls: [{ from: "0xc8ba32cab1757528daf49033e3673fae77dcf05d", gas: "0x18461", gasUsed: "0x60", input: "0x000000204895cd480cc8412691a880028a25aec86786f1ed2aa5562bc400000000000000c6403c14f35be1da6f433eadbb6e9178a47fbc7c6c1d568d2f2b876e929089c8d8db646304fd001a187dc8a6", output: "0x557904b74478f8810cc02198544a030d1829bb491e14fe1dd0354e933c5e87bd", to: "0x0000000000000000000000000000000000000002", type: "STATICCALL" }, { from: "0xc8ba32cab1757528daf49033e3673fae77dcf05d", gas: "0x181db", gasUsed: "0x48", input: "0x557904b74478f8810cc02198544a030d1829bb491e14fe1dd0354e933c5e87bd", output: "0x5fb393023b12544491a5b8fb057943b4ebf5b1401e88e44a7800000000000000", to: "0x0000000000000000000000000000000000000002", type: "STATICCALL" }], from: "0x35a9f94af726f07b5162df7e828cc9dc8439e7d0", gas: "0x1a310", gasUsed: "0xfcb6", input: "0xd1a2eab2000000000000000000000000000000000000000000000000000000000024aea100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000204895cd480cc8412691a880028a25aec86786f1ed2aa5562bc400000000000000c6403c14f35be1da6f433eadbb6e9178a47fbc7c6c1d568d2f2b876e929089c8d8db646304fd001a187dc8a600000000000000000000000000000000", to: "0xc8ba32cab1757528daf49033e3673fae77dcf05d", type: "CALL", value: "0x0" }

Things to note about the call tracer:

  • Calls to precompiles are also included in the result

  • In case a frame reverts, the field output will contain the raw return data

  • In case the top level frame reverts, its revertReason field will contain the parsed reason of revert as returned by the Solidity contract

callTracer config

callTracer accepts two options:

  • onlyTopCall: true instructs the tracer to only process the main (top-level) call and none of the sub-calls. This avoids extra processing for each call frame if only the top-level call info are required.
  • withLog: true instructs the tracer to also collect the logs emitted during each call.

Example invocation with the onlyTopCall flag:

> debug.traceTransaction('0xc73e70f6d60e63a71dabf90b9983f2cdd56b0cb7bcf1a205f638d630a95bba73', { tracer: 'callTracer', tracerConfig: { onlyTopCall: true } })

prestateTracer

The prestate tracer has two modes: prestate and diff. The prestate mode returns the accounts necessary to execute a given transaction. diff mode returns the differences between the transaction's pre and post-state (i.e. what changed because the transaction happened). The prestateTracer defaults to prestate mode. It reexecutes the given transaction and tracks every part of state that is touched. This is similar to the concept of a stateless witness, the difference being this tracer doesn't return any cryptographic proof, rather only the trie leaves. The result is an object. The keys are addresses of accounts. The value is an object with the following fields:

fieldtypedescription
balancestringbalance in Wei
nonceuint64nonce
codestringhex-encoded bytecode
storagemap[string]stringstorage slots of the contract

prestateTracer config

  • diffMode: BOOL. Setting this to true will enable diff mode (default = false).

In diff mode the result object will contain a pre and a post object:

  1. Any read-only access is omitted completely from the result. This mode is only concerned with state modifications.
  2. In pre you will find the state of an account before the tx started, and in post its state after tx execution finished.
  3. post will contain only the modified fields. e.g. if nonce of an account hasn't changed it will be omitted from post.
  4. Deletion (i.e. account selfdestruct, or storage clearing) will be signified by inclusion in pre and omission in post.
  5. Insertion (i.e. account creation or new slots) will be signified by omission in pre and inclusion in post.

To run this tracer in diff mode, pass tracerConfig: {diffMode: true} in the API call.

Example of prestate mode:

debug.traceCall(
  {
    from: '0x35a9f94af726f07b5162df7e828cc9dc8439e7d0',
    to: '0xc8ba32cab1757528daf49033e3673fae77dcf05d',
    data: '0xd1a2eab2000000000000000000000000000000000000000000000000000000000024aea100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000204895cd480cc8412691a880028a25aec86786f1ed2aa5562bc400000000000000c6403c14f35be1da6f433eadbb6e9178a47fbc7c6c1d568d2f2b876e929089c8d8db646304fd001a187dc8a600000000000000000000000000000000'
  },
  'latest',
  { tracer: 'prestateTracer' }
);

Return:

{ 0x0000000000000000000000000000000000000002: { balance: "0x0" }, 0x008b3b2f992c0e14edaa6e2c662bec549caa8df1: { balance: "0x2638035a26d133809" }, 0x35a9f94af726f07b5162df7e828cc9dc8439e7d0: { balance: "0x7a48734599f7284", nonce: 1133 }, 0xc8ba32cab1757528daf49033e3673fae77dcf05d: { balance: "0x0", code: "0x608060405234801561001057600080fd5b50600436106100885760003560e01c8063a9c2d... nonce: 1, storage: { 0x0000000000000000000000000000000000000000000000000000000000000000: "0x000000000000000000000000000000000000000000000000000000000024aea6", 0x59fb7853eb21f604d010b94c123acbeae621f09ce15ee5d7616485b1e78a72e9: "0x00000000000000c42b56a52aedf18667c8ae258a0280a8912641c80c48cd9548", 0x8d8ebb65ec00cb973d4fe086a607728fd1b9de14aa48208381eed9592f0dee9a: "0x00000000000000784ae4881e40b1f5ebb4437905fbb8a5914454123b0293b35f", 0xff896b09014882056009dedb136458f017fcef9a4729467d0d00b4fd413fb1f1: "0x000000000000000e78ac39cb1c20e9edc753623b153705d0ccc487e31f9d6749" } } }

Return (same call with {diffMode: True}):

{ post: { 0x35a9f94af726f07b5162df7e828cc9dc8439e7d0: { nonce: 1135 } }, pre: { 0x35a9f94af726f07b5162df7e828cc9dc8439e7d0: { balance: "0x7a48429e177130a", nonce: 1134 } } }

noopTracer

This tracer is noop. It returns an empty object and is only meant for testing the setup.

Javascript tracers

There are also a set of tracers written in Javascript. These are less performant than the Go native tracers because of overheads associated with interpreting the Javascript in Geth's Go environment.

bigram

bigramTracer counts the opcode bigrams, i.e. how many times 2 opcodes were executed one after the other.

Example:

debug.traceCall(
  {
    from: '0x35a9f94af726f07b5162df7e828cc9dc8439e7d0',
    to: '0xc8ba32cab1757528daf49033e3673fae77dcf05d',
    data: '0xd1a2eab2000000000000000000000000000000000000000000000000000000000024aea100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000204895cd480cc8412691a880028a25aec86786f1ed2aa5562bc400000000000000c6403c14f35be1da6f433eadbb6e9178a47fbc7c6c1d568d2f2b876e929089c8d8db646304fd001a187dc8a600000000000000000000000000000000'
  },
  'latest',
  { tracer: 'bigramTracer' }
);

Returns:

{ ADD-ADD: 1, ADD-AND: 2, ADD-CALLDATALOAD: 1, ADD-DUP1: 2, ADD-DUP2: 2, ADD-GT: 1, ADD-MLOAD: 1, ADD-MSTORE: 4, ADD-PUSH1: 1, ADD-PUSH2: 4, ADD-SLT: 1, ADD-SWAP1: 10, ADD-SWAP2: 1, ADD-SWAP3: 1, ADD-SWAP4: 3, ADD-SWAP5: 1, AND-DUP3: 2, AND-ISZERO: 4, ... }

evmdis

evmdisTracer returns sufficient information from a trace to perform evmdis-style disassembly

Example:

> debug.traceCall({from: "0x35a9f94af726f07b5162df7e828cc9dc8439e7d0", to: "0xc8ba32cab1757528daf49033e3673fae77dcf05d", data: "0xd1a2eab2000000000000000000000000000000000000000000000000000000000024aea100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000204895cd480cc8412691a880028a25aec86786f1ed2aa5562bc400000000000000c6403c14f35be1da6f433eadbb6e9178a47fbc7c6c1d568d2f2b876e929089c8d8db646304fd001a187dc8a600000000000000000000000000000000"}, 'latest', {tracer: 'evmdisTracer'})

Returns:

[{ depth: 1, len: 2, op: 96, result: ["80"] }, { depth: 1, len: 2, op: 96, result: ["40"] }, { depth: 1, op: 82, result: [] }, { depth: 1, op: 52, result: ["0"] }, { depth: 1, op: 128, result: ["0", "0"] }, { depth: 1, op: 21, result: ["1"] }, { depth: 1, len: 3, op: 97, result: ["10"] }, { depth: 1, op: 87, result: [] }, { depth: 1, op: 91, pc: 16, result: [] }, ...

opcount

opcountTracer counts the total number of opcodes executed and simply returns the number.

Example:

debug.traceCall(
  {
    from: '0x35a9f94af726f07b5162df7e828cc9dc8439e7d0',
    to: '0xc8ba32cab1757528daf49033e3673fae77dcf05d',
    data: '0xd1a2eab2000000000000000000000000000000000000000000000000000000000024aea100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000204895cd480cc8412691a880028a25aec86786f1ed2aa5562bc400000000000000c6403c14f35be1da6f433eadbb6e9178a47fbc7c6c1d568d2f2b876e929089c8d8db646304fd001a187dc8a600000000000000000000000000000000'
  },
  'latest',
  { tracer: 'opcountTracer' }
);

Returns:

1384

trigram

trigramTracer counts the opcode trigrams. Trigrams are the possible combinations of three opcodes this tracer reports how many times each combination is seen during execution.

Example:

debug.traceCall(
  {
    from: '0x35a9f94af726f07b5162df7e828cc9dc8439e7d0',
    to: '0xc8ba32cab1757528daf49033e3673fae77dcf05d',
    data: '0xd1a2eab2000000000000000000000000000000000000000000000000000000000024aea100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000204895cd480cc8412691a880028a25aec86786f1ed2aa5562bc400000000000000c6403c14f35be1da6f433eadbb6e9178a47fbc7c6c1d568d2f2b876e929089c8d8db646304fd001a187dc8a600000000000000000000000000000000'
  },
  'latest',
  { tracer: 'trigramTracer' }
);

Returns:

{ --PUSH1: 1, -PUSH1-MSTORE: 1, ADD-ADD-GT: 1, ADD-AND-DUP3: 2, ADD-CALLDATALOAD-PUSH8: 1, ADD-DUP1-PUSH1: 2, ADD-DUP2-ADD: 1, ADD-DUP2-MSTORE: 1, ADD-GT-ISZERO: 1, ADD-MLOAD-DUP6: 1, ADD-MSTORE-ADD: 1, ADD-MSTORE-PUSH1: 2, ADD-MSTORE-PUSH32: 1, ADD-PUSH1-KECCAK256: 1, ADD-PUSH2-JUMP: 2, ADD-PUSH2-JUMPI: 1, ADD-PUSH2-SWAP2: 1, ADD-SLT-PUSH2: 1, ... }

unigram

unigramTracer counts the frequency of occurrence of each opcode.

Example:

> debug.traceCall({from: "0x35a9f94af726f07b5162df7e828cc9dc8439e7d0", to: "0xc8ba32cab1757528daf49033e3673fae77dcf05d", data: "0xd1a2eab2000000000000000000000000000000000000000000000000000000000024aea100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000204895cd480cc8412691a880028a25aec86786f1ed2aa5562bc400000000000000c6403c14f35be1da6f433eadbb6e9178a47fbc7c6c1d568d2f2b876e929089c8d8db646304fd001a187dc8a600000000000000000000000000000000"}, 'latest', {tracer: 'unigramTracer'})

Returns:

{ ADD: 36, AND: 23, BYTE: 4, CALLDATACOPY: 1, CALLDATALOAD: 6, CALLDATASIZE: 2, CALLVALUE: 1, DIV: 9, DUP1: 29, DUP10: 2, DUP11: 1, DUP12: 3, DUP13: 2, ... }

State overrides

It is possible to give temporary state modifications to Geth in order to simulate the effects of eth_call or debug_traceCall. For example, some new bytecode could be deployed to some address temporarily just for the duration of the execution and then a transaction interacting with that address can be traced. This can be used for scenario testing or determining the outcome of some hypothetical transaction before executing for real.

Available state overrides are:

  • nonce: hexdecimal. The nonce of the account.
  • code: string. The bytecode of the account.
  • balance: hexdecimal. The balance of the account in Wei.
  • state: map[common.Hash]common.Hash. Clear all storage slots of the account and insert the ones given in the dictionary.
  • stateDiff: map[common.Hash]common.Hash. Update the given storage slots with new values.

Note, state and stateDiff can't be specified at the same time. If state is set, message execution will only use the data in the given state. Otherwise if statDiff is set, all diff will be applied first and then execute the call.

To do this, the tracer is written as normal, but the parameter stateOverrides is passed an address and some bytecode.

var code = //contract bytecode
var tracer = //tracer name
debug.traceCall({from: , to: , input: }, 'latest', {stateOverrides: {'0x...': {code: code}}, tracer: tracer})

Block overrides

Similar to State overrides, it is also possible to override some of the execution's block context to simulate the effects of eth_call or debug_traceCall, we support the following override options:

  • number: hexdecimal. The block number.
  • difficulty: hexdecimal. The block difficulty.
  • time: hexdecimal. The block timestamp.
  • gasLimit: hexdecimal. The block gas limit.
  • coinbase: common.Address. The block coinbase.
  • random: common.Hash. The block PREVRANDAO.
  • baseFee: hexdecimal. The block base fee.
  • blobBaseFee: hexdecimal. The block blob base fee.

Summary

This page showed how to use the tracers that come bundled with Geth. There are a set written in Go and a set written in Javascript. They are invoked by passing their names when calling an API method. State overrides can be used in combination with tracers to examine precisely what the EVM will do in some hypothetical scenarios.

DOWNLOADS

© 2013–2024. The go-ethereum Authors | Do-not-Track