Transaction Tracing
Transaction tracing enables a deeper look into smart contract inner calls and account creation calls. It is inspired by the OpenEthereum (Parity) node trace module, which is used in a lot of projects.
For now, the following JSON-RPC methods are implemented:
Currently, it is deployed on these endpoints for the Opera mainnet and testnet:
Start a New Go-Opera Tracing Node
Because of stored traces, a tracing node requires slightly bigger storage (around a quarter more). For more information, please follow the README.md file in the txtracing branch.
Building
You can check out and build the tx tracing release version from the git repository.
Running
It's recommended to launch a new node from scratch with the CLI option --tracenode
as this flag has to be set to use stored transaction traces.
Enable JSON-RPC API with the option trace
:
A complete example command:
Genesis File
For the first start of the node, you have to specify the genesis file, which defines the blockchain and contains its history.
Please select a genesis file with a full history ("full-mpt" in the filename) if you need transaction traces for historical states. It's recommended to use the following genesis file, mainnet-5577-full-mpt.g, for a full-history tracing node.
Import, Export, and delete
Transaction traces are created on the node while processing transactions and then stored in a node database. You can export these traces and import them on other nodes.
This addition allows for responses to certain changes that may alter the trace content without requiring a complete resynchronization of the blockchain.
Another method is to delete traces, which will be recreated upon the next request to them from the RPC API. However, this approach is not recommended because recreating traces this way is slower when the first request targets a non-existent transaction trace.
All these operations must be performed while the node is stopped, as they involve modifying data in the core database. For both export and delete operations, you can specify a block range to limit the number of processed transaction traces.
The command for exporting is export txtraces <filename.gz> <from block> <to block>
:
For importing, it's:
No file needs to be specified when using the delete option. delete txtraces <from block> <to block>
Go-Opera Version Migration
This section provides information on migrating a transaction tracing API node to a newer version of go-opera. It will indicate whether a full node resynchronization is required due to stored transaction traces or if only a code update is necessary to upgrade from the previous version.
release/txtracing/1.1.1-rc.3 — no resync from 1.1.1-rc.2, just update
release/txtracing/1.1.2-rc.2 — don't use this version
release/txtracing/1.1.2-rc.5 — resync from all previous versions and see notes below
When using version 1.1.2, you need to add the db.preset argument for the starting Opera command. You can view the available options for this parameter with the opera help command. For standard conditions, please use the following option:
db.preset=ldb-1
trace_block
Returns traces created at the given block.
Parameters
Quantity
orTag
— Integer of a block number, or the string'earliest'
,'latest'
, or'pending'
.
Returns
Array
— Block traces.
Example
Request:
Response:
trace_transaction
Returns all traces of the given transaction.
Parameters
Hash
— Transaction hash
Returns
Array
— Traces of given transaction
Example
Request:
trace_filter
Returns traces matching the given filter.
Parameters
Object
— The filter objectfromBlock
:Quantity
orTag
— (optional) From this block.toBlock
:Quantity
orTag
— (optional) To this block.fromAddress
:Array
— (optional) Sent from these addresses.toAddress
:Address
— (optional) Sent to these addresses.after
:Quantity
— (optional) The offset trace numbercount
:Quantity
— (optional) Integer number of traces to display in a batch.
Returns
Array
— Traces matching the given filter.
Example
Request:
trace_get
Returns trace at given position.
Parameters
Hash
— Transaction hash.Array
— Index positions of the traces.
Returns
Object
— Trace object
Example
Request:
debug_traceBlockByNumber
The method returns a full stack trace of all invoked opcodes of all transaction that were included in a block identified by a block number. The parent of this block must be present or it will fail.
Parameters
Number
- Block number to be traced.Object
- (optional) Options for the trace.tracer
:String
- (optional) Setting this will enable JavaScript-based transaction tracing, described below.timeout
:String
- (optional) Overrides the default timeout of 5 seconds for JavaScript-based tracing calls. Check documentation for ParseDuration for valid values.
Returns
Array
- A trace object per transaction inside the block.
Example
debug_traceBlockByHash
This method returns a full stack trace of all invoked opcodes of all transactions that were included in a block identified by a hash. The parent of this block must be present or it will fail.
Parameters
Hash
— Hash of the block to be traced.Object
— (optional) Options for the trace.tracer
:String
— (optional) Setting this will enable JavaScript-based transaction tracing, described below.timeout
:String
— (optional) Overrides the default timeout of 5 seconds for JavaScript-based tracing calls. Check documentation for ParseDuration for valid values.
Returns
Array
— A trace object per transaction inside the block.
Example
debug_traceTransaction
This method will try to run the transaction in the same way it was executed on the network. It will replay any transactions that occurred before it in the block and then attempt to execute the transaction corresponding to the given hash.
Parameters
Hash
— Hash of the transaction to be traced.Object
— (optional) Options for the trace.tracer
:String
— (optional) Setting this will enable JavaScript-based transaction tracing, described below.timeout
:String
— (optional) Overrides the default timeout of 5 seconds for JavaScript-based tracing calls. Check documentation for ParseDuration for valid values.
Returns
Object
— A trace object of the traced transaction.
Example
JavaScript-Based Tracer
Specifying the tracer
option in the second argument of the debug tracing calls enables JavaScript-based tracing. In this mode, tracer
is interpreted as a JavaScript expression that is expected to evaluate an object which must expose the result
and fault
methods. There exists 4 additional methods, namely: setup
, step
, enter
, and exit
. enter
and exit
must be present or omitted together.
Setup Method
Method setup
is invoked once, in the beginning when the tracer is being constructed for each transaction being traced. It takes in one argument, config
, which allows users to pass in options to the tracer. config
is to be JSON-decoded for usage and its default value is "{}"
.
Step Method
Method step
is a function that takes two arguments, log
and db
, and is called for each step of the EVM, or when an error occurs, as a transaction is traced.
log
has the following fields:
op
: Object, an OpCode object representing the current opcodestack
: Object, a structure representing the EVM execution stackmemory
: Object, a structure representing the contract’s memory spacecontract
: Object, an object representing the account executing the current operation
And the following methods:
getPC()
— returns a number with the current program countergetGas()
— returns a number with the amount of gas remaininggetCost()
— returns the cost of the opcode as a numbergetDepth()
— returns the execution depth as a numbergetRefund()
— returns the amount to be refunded as a numbergetError()
— returns information about the error if one occurred, otherwise returnsundefined
If an error is non-empty, all other fields should be ignored.
For efficiency, the same log
object is reused on each execution step, updated with current values; make sure to copy values you want to preserve beyond the current call.
log.op
has the following methods:
isPush()
— returns true if the opcode is a PUSHntoString()
— returns the string representation of the opcodetoNumber()
— returns the opcode’s number
log.memory
has the following methods:
slice(start, stop)
— returns the specified segment of memory as a byte slicegetUint(offset)
— returns the 32 bytes at the given offsetlength()
— returns the memory size
log.stack
has the following methods:
peek(idx)
— returns the idx-th element from the top of the stack (0 is the topmost element) as a big.Intlength()
— returns the number of elements in the stack
log.contract
has the following methods:
getCaller()
— returns the address of the callergetAddress()
— returns the address of the current contractgetValue()
— returns the amount of value sent from caller to contract as a big.IntgetInput()
— returns the input data passed to the contract
db
has the following methods:
getBalance(address)
— returns abig.Int
with the specified account’s balancegetNonce(address)
— returns a Number with the specified account’s noncegetCode(address)
— returns a byte slice with the code for the specified accountgetState(address, hash)
— returns the state value for the specified account and the specified hashexists(address)
— returns true if the specified address exists
If the step function throws an exception or executes an illegal operation at any point, it will not be called on any further VM steps, and the error will be returned to the caller.
Result Method
Method result
is a function that takes two arguments, ctx
and db
, and is expected to return a JSON-serializable value to return to the RPC caller.
ctx
is the context in which the transaction is executed and has the following fields:
type
— String, one of the two values,CALL
andCREATE
from
— Address, sender of the transactionto
— Address, target of the transactioninput
— Buffer, input transaction datagas
— Number, gas budget of the transactiongasUsed
- Number, amount of gas used in executing the transaction (excludes txdata costs)gasPrice
— Number, gas price configured in the transaction being executedintrinsicGas
— Number, intrinsic gas for the transaction being executedvalue
— big.Int, amount to be transferred in weiblock
— Number, block numberoutput
— Buffer, value returned from EVMtime
— String, execution runtime
Fault Method
Method fault
is a function that takes two arguments, log
and db
, just like step
, and is invoked when an error happens during the execution of an opcode which wasn’t reported in step
. The method log.getError()
has information about the error.
Ender and Exit Methods
Methods enter
and exit
are respectively invoked on stepping in and out of an internal call. More specifically, they are invoked on the CALL
variants, CREATE
variants, and also for the transfer implied by a SELFDESTRUCT
.
enter
takes a callFrame
object as argument which has the following methods:
getType()
— returns a string which has the type of the call framegetFrom()
— returns the address of the call frame sendergetTo()
— returns the address of the call frame targetgetInput()
— returns the input as a buffergetGas()
— returns a number that has the amount of gas provided for the framegetValue()
— returns abig.Int
with the amount to be transferred only if available, otherwiseundefined
exit
takes in a frameResult
object which has the following methods:
getGasUsed()
— returns amount of gas used throughout the frame as a NumbergetOutput()
— returns the output as a buffergetError()
— returns an error if one occurred during execution and undefined otherwise
Note that several values are Golang big.Int objects, not JavaScript numbers or JS bigints. As such, they have the same interface as described in the godocs. Their default serialization to JSON is as a JavaScript number; to serialize large numbers accurately, call .String()
on them. For convenience, big.NewInt(x)
is provided, and will convert a uint to a Go BigInt.