trace_call vs debug_traceCall

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

  1. Object - Call options.

    • fromAddress - (optional) - The address the transaction is sent from.
    • toAddress - (optional when creating a new contract) - The address the transaction is directed to.
    • gasQuantity - (optional) Integer formatted as a hex string that represents the gas provided for the transaction execution.
    • gasPriceQuantity - (optional) Integer formatted as a hex string that represents the gas price used for each paid gas.
    • valueQuantity - (optional) Integer formatted as a hex string that represents the value sent with the given transaction.
    • dataData - (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.
  2. Array - Type of trace, one or more of: "vmTrace""trace""stateDiff".

    1. trace: Returns the basic trace for the given transaction.
    2. stateDiff: Provides information detailing all altered portions of the Ethereum state made due to the execution of the transaction.
    3. 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.

  1. Quantity or Tag - (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.
    1. latest: The latest block that the client has observed.
    2. 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:

  1. 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 the gasPrice 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.
  2. String - One of the following options:
    1. block hash
    2. block number (in hex)
    3. 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.
  3. Object - tracer
  • tracer - String to specify the type of tracer. Currently supports callTracer and prestateTracer (see below for definitions).
  • tracerConfig - Object to specify configurations for the tracer
    • onlyTopCall - boolean - setting this to true 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 getting revertReason).

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:

fieldtypedescription
typestringCALL, CREATE or SUICIDE
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
callsarray of call frameslist of sub-calls

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:

fieldtypedescription
balancestringbalance in wei
nonceuint64nonce
codestringhex-encoded bytecode
storagemap[string]stringstorage 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_callSimilar options of debug_traceCall
tracecallTrace
stateDiffprestateTracer

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

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 the onlyTopCall option to true. This avoids extra processing if only the top-level info is required (like getting the revert reason). However, this is not possible using trace_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 in trace_call. (However, both methods accept the block number in hex format and the tags like latest and earliest).

  • trace_call is accessible through Erigon while debug_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 containing trace and stateDiff 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.

ReadMe