The differences between the trace_call method by OpenEthereum and the debug_traceCall method by Geth
Introduction
Geth and OpenEthereum are two popular Ethereum clients. In this article, we'll compare and contrast the trace_call method offered by OpenEthereum with the debug_traceCall method offered by Geth. Both of these methods are used for transaction tracing.
NOTE
The
trace_call
method was initially supported by the OpenEthereum client but now OpenEthereum has been deprecated and a new client Erigon is supporting the trace methods.Going forward, we will only use the name of the Erigon Ethereum client when associating with the trace methods.
Prerequisites
Before reading this article you should know about Ethereum Clients and EVM Traces.
The trace_call method
The trace_call method executes the given call (transaction) and returns a number of possible traces for it. It’s helpful for debugging transactions and analyzing state changes due to a transaction. Under the hood,trace_call is only supported by OpenEthereum or Erigon clients, but if you’re using an Alchemy API key we’ll automatically route the request for you so you don’t have to worry about the node client.
Here are the parameters and response payloads for trace_call
Parameters
-
Object
- Call options.from
:Address
- (optional) - The address the transaction is sent from.to
:Address
- (optional when creating a new contract) - The address the transaction is directed to.gas
:Quantity
- (optional) Integer formatted as a hex string that represents the gas provided for the transaction execution.gasPrice
:Quantity
- (optional) Integer formatted as a hex string that represents the gas price used for each paid gas.value
:Quantity
- (optional) Integer formatted as a hex string that represents the value sent with the given transaction.data
:Data
- (optional) 4-byte hash of the method signature followed by encoded parameters. This basically represents which function to call in the smart contract along with the function parameters, if it’s just a value transfer then this field can be empty.
-
Array
- Type of trace, one or more of:"vmTrace"
,"trace"
,"stateDiff"
.trace
: Returns the basic trace for the given transaction.stateDiff
: Provides information detailing all altered portions of the Ethereum state made due to the execution of the transaction.vmTrace
: Provides a full trace of the Ethereum state throughout the execution of the transaction, including for any subcalls.
vmTrace
is no longer supported by Alchemy.
Quantity
orTag
- (optional) Integer formatted as a hex string that represents a block number, or the string'earliest'
or'latest'
. If this parameter is supplied, the transaction is replayed in the context of the given parameter.latest
: The latest block that the client has observed.earliest
: The lowest number block that the client has available. Intuitively, you can think of this as the first block created.
Example Request and Response
The trace_call
method returns an array of traces. The structure of these traces is explained in Types of Trace Actions and their Structure. Below you can find an example.
Request
curl https://eth-mainnet.g.alchemy.com/v2/demo \
-X POST \
-H "Content-Type: application/json" \
-d '{"method":"trace_call",
"params":[{
"from": "0x6f1FB6EFDf50F34bFA3F2bC0E5576EdD71631638",
"to": "0x6b175474e89094c44da98b954eedeac495271d0f",
"value": "0x0",
"data": "0x70a082310000000000000000000000006E0d01A76C3Cf4288372a29124A26D4353EE51BE"},
["trace"]],
"id":1,
"jsonrpc":"2.0"}]'
const ethers = require("ethers");
(async () => {
const provider = new ethers.providers.JsonRpcProvider(
"https://eth-mainnet.g.alchemy.com/v2/demo"
);
const response = await provider.send("trace_call", [
{
from: "0x6f1FB6EFDf50F34bFA3F2bC0E5576EdD71631638"
to: "0x6b175474e89094c44da98b954eedeac495271d0f",
data: "0x70a082310000000000000000000000006E0d01A76C3Cf4288372a29124A26D4353EE51BE",
},
["trace"],
"latest",
]);
console.log(response);
})();
from web3 import HTTPProvider
client = HTTPProvider('https://eth-mainnet.g.alchemy.com/v2/demo')
result = client.make_request('trace_call', [{
"to": "0x6b175474e89094c44da98b954eedeac495271d0f",
"data": "0x70a082310000000000000000000000006E0d01A76C3Cf4288372a29124A26D4353EE51BE"
},["trace"], "latest"])
print(result)
Response
{
"output": "0x0000000000000000000000000000000000000000000000000858898f93629000",
"stateDiff": null,
"trace": [
{
"action": {
"from": "0x0000000000000000000000000000000000000000",
"callType": "call",
"gas": "0x1dcd1148",
"input": "0x70a082310000000000000000000000006e0d01a76c3cf4288372a29124a26d4353ee51be",
"to": "0x6b175474e89094c44da98b954eedeac495271d0f",
"value": "0x0"
},
"result": {
"gasUsed": "0xa2a",
"output": "0x0000000000000000000000000000000000000000000000000858898f93629000"
},
"subtraces": 0,
"traceAddress": [],
"type": "call"
}
],
"vmTrace": null
}
The debug_traceCall method
The debug_traceCall method executes the given call (transaction) and returns a number of possible traces for it. It’s helpful for debugging transactions and analyzing state changes due to a transaction. Under the hood,debug_traceCall is only supported by OpenEthereum or Erigon clients, but if you’re using an Alchemy API key we’ll automatically route the request for you so you don’t have to worry about the node client.
Here are the parameters and response payloads for debug_traceCall
Parameters
The parameters for the debug_traceCall
method are:
Object
- Transaction object with the following fields:from
- string - The address the transaction is sent from.to
- string, [required] - The address the transaction is directed to.gasPrice
- string - Integer of thegasPrice
used for each paid gas.value
- string - Integer of the value sent with the given transaction.data
- string - Hash of the method signature and encoded parameters.
String
- One of the following options:- block hash
- block number (in hex)
- block tag (one of the following):
pending
- A sample next block built by the client on top of the latest and containing the set of transactions usually taken from the local mempool. Intuitively, you can think of these as blocks that have not been mined yet.latest
- The most recent block observed by the client, this block may be re-orged out of the canonical chain even under healthy/normal conditions.safe
- The most recent crypto-economically secure block, cannot be re-orged outside of manual intervention driven by community coordination. Intuitively, this block is “unlikely” to be re-orged. Only available on Ethereum Goerli.finalized
- The most recent crypto-economically secure block, that has been accepted by >2/3 of validators. Cannot be re-orged outside of manual intervention driven by community coordination. Intuitively, this block is very unlikely to be re-orged. Only available on Ethereum Goerli.earliest
- The lowest numbered block the client has available. Intuitively, you can think of this as the first block created.
- Object - tracer
tracer
- String to specify the type of tracer. Currently supportscallTracer
andprestateTracer
(see below for definitions).tracerConfig
- Object to specify configurations for the traceronlyTopCall
- boolean - setting this totrue
will only trace 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 is required (useful for gettingrevertReason
).
callTracer
callTracer
The callTracer
tracks all the call frames executed during a transaction. The result will be a nested list of call frames. They form a tree with the top-level call at the root and sub-calls as children of the higher levels.
It’s similar to the trace
option of the trace_call method. Each call frame has the following fields:
field | type | description |
---|---|---|
type | string | CALL, CREATE or SUICIDE |
from | string | address |
to | string | address |
value | string | hex-encoded amount of value transfer |
gas | string | hex-encoded gas provided for call |
gasUsed | string | hex-encoded gas used during call |
input | string | call data |
output | string | return data |
error | string | error, if any |
revertReason | string | Solidity revert reason, if any |
calls | array of call frames | list of sub-calls |
prestateTracer
prestateTracer
The prestateTracer
replays the transaction and tracks every part of state that is touched during that transaction.
This is similar to the stateDiff
option of the trace_call method. The result is an object. The keys are addresses of accounts. The value is an object with the following fields:
field | type | description |
---|---|---|
balance | string | balance in wei |
nonce | uint64 | nonce |
code | string | hex-encoded bytecode |
storage | map[string]string | storage slots of the contract |
As you've seen that the callTracer
and prestateTracer
options of debug_traceCall are similar to trace
and stateDiff
options of debug_traceCall, here's a table depicting this:
Options of trace_call | Similar options of debug_traceCall |
---|---|
trace | callTrace |
stateDiff | prestateTracer |
Example Request and Response
Request
curl https://eth-mainnet.g.alchemy.com/v2/demo \
-X POST \
-H "Content-Type: application/json" \
--data '{"method":"debug_traceCall","params":[{"from":null,"to":"0x6b175474e89094c44da98b954eedeac495271d0f","data":"0x70a082310000000000000000000000006E0d01A76C3Cf4288372a29124A26D4353EE51BE"}, "latest"],"id":1,"jsonrpc":"2.0"}'
const ethers = require("ethers");
(async () => {
const provider = new ethers.providers.JsonRpcProvider(
"https://eth-mainnet.g.alchemy.com/v2/demo"
);
const response = await provider.send("debug_traceCall", [
{
from: "0xe5cb067e90d5cd1f8052b83562ae670ba4a211a8",
to: "0xdc66567a990b7fa10730459537620857c9e03287",
data: "0xae169a50000000000000000000000000000000000000000000000000000000000000000e",
},
"0xF118CE",
{ tracer: "callTracer", tracerConfig: { onlyTopCall: false } },
]);
console.log(response);
})();
from web3 import Web3, HTTPProvider
provider = Web3.HTTPProvider("https://eth-mainnet.g.alchemy.com/v2/demo")
result = provider.make_request('debug_traceCall', [{
"to": "0x6b175474e89094c44da98b954eedeac495271d0f",
"data": "0x70a082310000000000000000000000006E0d01A76C3Cf4288372a29124A26D4353EE51BE"
}, "latest"])
print(result)
Response
{
"type": "CALL",
"from": "0xe5cb067e90d5cd1f8052b83562ae670ba4a211a8",
"to": "0xdc66567a990b7fa10730459537620857c9e03287",
"value": "0x0",
"gas": "0x7fffffffffffad2b",
"gasUsed": "0x19dd6",
"input": "0xae169a50000000000000000000000000000000000000000000000000000000000000000e",
"output": "0x0000000000000000000000000000000000000000000000000000000000000001",
"calls": [
{
"type": "CALL",
"from": "0xdc66567a990b7fa10730459537620857c9e03287",
"to": "0xa2b885e7065ea59a3251489715ca80de5ff642f8",
"gas": "0x7dffffffffff9207",
"gasUsed": "0x1bb3",
"input": "0x6352211e000000000000000000000000000000000000000000000000000000000000000e",
"output": "0x000000000000000000000000e5cb067e90d5cd1f8052b83562ae670ba4a211a8"
},
{
"type": "CALL",
"from": "0xdc66567a990b7fa10730459537620857c9e03287",
"to": "0xf418588522d5dd018b425e472991e52ebbeeeeee",
"gas": "0x7dffffffffff5b32",
"gasUsed": "0xa4e",
"input": "0x70a08231000000000000000000000000dc66567a990b7fa10730459537620857c9e03287",
"output": "0x0000000000000000000000000000000000000000000011b962d6ea64e3500000"
}
]
}
Difference between trace_call
and debug_traceCall
trace_call
and debug_traceCall
Since trace_call is OpenEthereum's (Now Erigon's) equivalent to Geth's debug_traceCall there are not many differences between them but there are some minor differences which are mentioned below:
-
You can choose to trace only the main call (the top-level call) in
debug_traceCall
by setting theonlyTopCall
option totrue
. This avoids extra processing if only the top-level info is required (like getting the revert reason). However, this is not possible usingtrace_call
as you always get back the complete trace. -
You can replay a transaction in a particular block by providing the block hash in
debug_traceCall
but not intrace_call
. (However, both methods accept the block number in hex format and the tags likelatest
andearliest
). -
trace_call
is accessible through Erigon whiledebug_traceCall
is accessible through Geth. -
By using
trace_call
you can get the simple trace for a transaction (trace) and the state difference (stateDiff) in just one request by putting an array containingtrace
andstateDiff
options in trace_call's second parameter (["trace", "stateDiff"]
) as mentioned below:
// Request using ethers.js in node.js
const ethers = require("ethers");
(async () => {
const provider = new ethers.providers.JsonRpcProvider(
"https://eth-mainnet.g.alchemy.com/v2/demo"
);
const response = await provider.send("trace_call", [
{
from: "0xe5cb067e90d5cd1f8052b83562ae670ba4a211a8",
to: "0xdc66567a990b7fa10730459537620857c9e03287",
data: "0xae169a50000000000000000000000000000000000000000000000000000000000000000e",
},
["trace", "stateDiff"], // getting both "trace" and "stateDiff" in one request
"latest",
]);
console.log(response);
})();
In response for this request you will get both trace
and stateDiff
:
{
"output": "0x",
"stateDiff": { // ---> stateDiff
"0xe5cb067e90d5cd1f8052b83562ae670ba4a211a8": {
"balance": {
"*": {
"from": "0x43d1b8fda906d05",
"to": "0x1c0671087043bf963a878705f"
}
},
"code": "=",
"nonce": {
"*": {
"from": "0x21",
"to": "0x22"
}
},
"storage": {}
}
},
"trace": [ // // ---> trace
{
"action": {
"from": "0xe5cb067e90d5cd1f8052b83562ae670ba4a211a8",
"callType": "call",
"gas": "0x1dcd122c",
"input": "0xae169a50000000000000000000000000000000000000000000000000000000000000000e",
"to": "0xdc66567a990b7fa10730459537620857c9e03287",
"value": "0x0"
},
"result": {
"gasUsed": "0x0",
"output": "0x"
},
"subtraces": 0,
"traceAddress": [],
"type": "call"
}
],
"vmTrace": {
"code": "0x",
"ops": []
}
}
But by using debug_traceCall you can either get the traces using callTracer
or the state difference using prestateTracer
in one request.
Conclusion
In conclusion, trace_call and debug_traceCall are two important methods for transaction tracing. They are accessible through different Ethereum clients and are slightly different from each other.