This repo describes the debug_traceTransaction method, which is available by querying a node running Erigon or Go Ethereum if the debug module is enabled.
The debug function in Erigon is very useful during the development of a smart contract to understand why a transaction is failing.
debug_traceTransaction
returns logs of low-level opcode that can be used to show what happens step by step during the process and what is the reason for it failing.
Developers can use this data to show the steps happening during a transaction.
In this guide we will see three ways to analyze a failed transaction:
- Using Geth VM Debug Trace Transaction page on Etherscan.
- Using tenderly.co, an Ethereum developer platform.
- Calling the
debug_traceTransaction
method.
Parameters:
transaction hash
- Hash of the transaction to trace.
Returns:
array
- Block traces.
-
result
- Transaction Trace Object, with the following fields:-
failed
- Boolean. -
gas
- Quantity. -
returnvalue
- Data. -
structlogs
- Array:-
entries
- Array. -
storagesbydepth
- Array.
-
-
cURL
curl -X POST 'ERIGON_NODE_URL' \
-H 'Content-Type: application/json' \
--data '{"method": "debug_traceTransaction", "params": ["0x1cd5d6379c7a06619acaf07a1a87116e5a476203b1798862ebb7144ecc5ebba9", {}],"id":1,"jsonrpc":"2.0"}'
web3.py
from web3 import Web3
node_url = "ERIGON_NODE_URL"
# Create the node connection
web3 = Web3.HTTPProvider(node_url)
# print debug trace transaction
debug = web3.make_request('debug_traceTransaction', ['0x1cd5d6379c7a06619acaf07a1a87116e5a476203b1798862ebb7144ecc5ebba9'])
#print(result) # print raw result
# Parse the result in a more readable way
for action in debug['result']['structLogs']:
for key, val in action.items():
print(key +':', val)
Note: you will need access to an Ethereum node running Erigon to call the
debug_traceTransaction
method. You can get one with Chainstack.
Let's analyze this transaction:
This transaction is made by an account to a smart contract, and you can see that it failed and was reverted.
The transaction sends this data to the smart contract (calls the transfer
function):
Function: transfer(address _to, uint256 _value)
MethodID: 0xa9059cbb
[0]: 000000000000000000000000876eabf441b2ee5b5b0554fd502a8e0600950cfa
[1]: 000000000000000000000000000000000000000000000000000005e5ac845ea0
raw data:
0xa9059cbb000000000000000000000000876eabf441b2ee5b5b0554fd502a8e0600950cfa000000000000000000000000000000000000000000000000000005e5ac845ea0
The first option is from the Geth VM Trace Transaction page on Etherscan.
This is essentially the same output that you would get by calling debug_traceTransaction
, but parsed nicely.
You can notice that the last operation is *REVERT at the step [241] and program counter (PC) 7243.
So this allows us to see that the transaction reverted for some reason, but we cannot see what that reason is.
tenderly.co is a Ethereum developer platform with deep functionality across debugging, testing, infra and more. This platform allows us to really analyze a transaction.
If we use the transaction hash to in the dashboard we can retrieve the data. In the overview tab we can already see what the problem is.
link to the transaction overview in tenderly
This shows us that the transaction was reverted because the account calling the transfer
function does not meet the condition of the modifier. In this case, the address sending the transaction (msg.sender) is not present in the whitelist mapping.
Then, if we go to the Debugger section, we can see the details, and you will notice that it shows the OPCODE
that we retrieve if we call the debug_traceTransaction
method.
It first shows the function that was called, with its relative OPCODE
. As well as the contract, caller, and input information. Notice that in this case, the information is already converted into decimal from HEX.
Stack trace information:
{
"[FUNCTION]": "transfer",
"[OPCODE]": "JUMPDEST",
"contract": {
"address": "0xee4458e052b533b1aabd493b5f8c4d85d7b263dc",
"balance": "0"
},
"caller": {
"address": "0x7e6f723fcb32bafc4131f710d342c3d051b280ee",
"balance": "12957438303317436"
},
"input": {
"_to": "0x876eabf441b2ee5b5b0554fd502a8e0600950cfa",
"_value": "6484000005792"
},
"[OUTPUT]": "0x",
"[ERROR]": "execution reverted",
"gas": {
"gas_left": 128380,
"gas_used": 5231,
"total_gas_used": 21620
}
}
The second step shows us why the transaction was reverted. It does not meet the condition of the whitelist modifier.
With its stack trace:
{
"[FUNCTION]": "transfer",
"[OPCODE]": "REVERT",
"[INPUT]": "0x",
"[OUTPUT]": "0x",
"[ERROR]": "execution reverted",
"gas": {
"gas_left": 0,
"gas_used": 0,
"total_gas_used": 150000
}
}
Now calling debug_traceTransaction
(from Postman), we can analyze what happens:
curl -X POST 'ERIGON_NODE_URL' \
-H 'Content-Type: application/json' \
--data '{"method": "debug_traceTransaction", "params": ["0x1cd5d6379c7a06619acaf07a1a87116e5a476203b1798862ebb7144ecc5ebba9", {}],"id":1,"jsonrpc":"2.0"}'
Here is the beginning of the response that you will receive by running the code above:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"structLogs": [
{
"pc": 0,
"op": "PUSH1",
"gas": 128380,
"gasCost": 3,
"depth": 1,
"stack": [],
"memory": []
},
{
"pc": 2,
"op": "PUSH1",
"gas": 128377,
"gasCost": 3,
"depth": 1,
"stack": [
"0x80"
],
"memory": []
},
...
You can see the full response in the
debug_trace_response.json
file in this Gist. It contains more than 5000 lines, and having it as a separate file keeps this guide a bit cleaner.
Then, you will find the REVERT
opcode at the end, where it describes the error:
{
"pc": 7243,
"op": "REVERT",
"gas": 123149,
"gasCost": 0,
"depth": 1,
"stack": [
"0xa9059cbb",
"0x96d",
"0x876eabf441b2ee5b5b0554fd502a8e0600950cfa",
"0x5e5ac845ea0",
"0x0",
"0x0",
"0x0"
],
"memory": [
"0000000000000000000000007e6f723fcb32bafc4131f710d342c3d051b280ee",
"0000000000000000000000000000000000000000000000000000000000000008",
"0000000000000000000000000000000000000000000000000000000000000080"
]
}
By decoding these parameters, we can understand why the transaction fails.
Still working on a way to decode this. I assume that it shows on what line of the smart contract is reverted. But I can't find a way to decode it.