Requirements
The examples in this guide call the debug JSON-RPC endpoints directly withcurl. You do not need an SDK or other libraries. To pretty-print the JSON responses, you can install jq:
What you can trace
Transaction execution
- Step-by-step opcode execution
- Contract call hierarchy
- Gas consumption breakdown
- State changes and storage access
Contract interactions
- Cross-contract calls and returns
- Event emission analysis
- Precompile usage tracking
- External library calls
Performance analysis
- Gas optimization opportunities
- Bottleneck identification
- Cache hit and miss patterns
- State access efficiency
Security analysis
- Suspicious operation detection
- Reentrancy pattern analysis
- Access control verification
- Vulnerability scanning
Available tracing methods
debug_traceStateAccess respects the node’s configured TraceTimeout. If the trace deadline elapses during replay or serialization, the call aborts with a context deadline exceeded error instead of running unbounded. Additionally, the per-module state-access log is capped at 4 MiB of retained key/value payload. When a module exceeds this cap, its trace dump sets "truncated": true: the reads/has fields then reflect only the retained prefix, while the stats (operation counts and durations) remain complete and accurate.debug_traceTransactionProfile | Trace plus timing/store-access profiling | Latency breakdown and DB-access analysis |
Transaction analysis example
This example traces an ERC-20 transfer transaction:- Transaction Details
- Basic Trace
- Gas Analysis
Debugging failed transactions
Use these steps to analyze and fix transaction failures:Step 1: Identify the problem
Step 2: Trace the execution
Step 3: Fix and test
Common debugging scenarios
Transaction reverted
Problem: The transaction failed with a revert. Solution: UsecallTracer to find the exact revert reason.
Out of gas
Problem: The transaction ran out of gas. Solution: Use the gas analysis tracer to optimize gas usage.Unexpected behavior
Problem: The transaction succeeded, but the result is wrong. Solution: Use the opcode tracer for step-by-step analysis.Slow performance
Problem: The transaction uses too much gas. Solution: Use the state access tracer to find inefficiencies.Quick reference
Essential commands
Common tracers
callTracer: Contract call hierarchyopcodeTracer: Opcode-level execution- Custom JS: Custom analysis logic
Pre-baked trace cache
RPC nodes can optionally pre-compute and cachedebug_trace* results in the background. The node then serves trace requests from a local on-disk cache instead of re-executing the block live on every call. You configure this opt-in feature through [evm] fields in app.toml. It is recommended for RPC nodes only.
When the feature is enabled, a background worker re-executes each committed block with the configured tracers. It stores the results in a Pebble database at <home>/data/trace_db. These methods serve results from this cache on a cache hit. Otherwise, they fall through to live re-execution:
debug_traceTransactiondebug_traceBlockByNumberanddebug_traceBlockByHash
When the cache is used
The node serves a request from the cache only when trace baking is enabled and the request uses a bakeable tracer configuration:- The tracer is one of
callTracer,prestateTracer, orflatCallTracer. - The request does not include a custom
tracerConfig. A per-calltracerConfig(for example,{"withLog": true}) is not part of the cache key. Any custom tracer config makes the request un-bakeable, so it falls through to live re-execution.
Configuration
These[evm] fields in app.toml control trace baking:
Tracer allowlist
Caller-suppliedtracer values on the debug_trace* endpoints are gated by [evm] config in app.toml. By default, only a fixed set of native geth tracers may be requested, and request-supplied JavaScript tracer source is rejected. This deviates from upstream geth, which accepts JavaScript tracers by default. The gate applies to debug_traceCall, debug_traceTransaction, debug_traceBlockByNumber/debug_traceBlockByHash, and debug_traceTransactionProfile. When no tracer is supplied, the default struct logger remains available.
Behavior notes:
- A tracer name that is not listed in
trace_allowed_tracersis rejected, unless it is JavaScript source andtrace_allow_js_tracersis enabled. - JavaScript tracer source is rejected unless
trace_allow_js_tracersis set totrue. - When
muxTraceris requested, its nested tracer names are validated recursively against the same allowlist, with a bounded nesting depth of 16. trace_bake_tracersnames are held to the same native-only rule and validated at startup, so a non-native or misspelled baked tracer fails startup instead of being evaluated as JavaScript source on every block.
The
trace_bake_tracers note above that eligible values are standard named tracers is subsumed by this native-only startup validation: entries must be native tracer names such as callTracer, prestateTracer, or flatCallTracer.Struct-logger output cap
The defaultdebug_trace* endpoints (debug_traceCall, debug_traceTransaction, and debug_traceBlockByNumber/debug_traceBlockByHash) use the built-in struct logger when no custom tracer is supplied. Traces that read many distinct storage slots can retain large amounts of output, so the node bounds the retained struct-logger output per traced transaction.
The max_trace_struct_log_bytes field under [evm] in app.toml controls this cap:
Behavior notes:
- The bound is applied per transaction, not per RPC call. Because geth builds a fresh struct logger for each transaction, a
debug_traceBlock*call over N transactions can retain up to N times this value (and the parallelized block-trace path holds several concurrent traces live). - A caller-supplied
Limitlarger thanmax_trace_struct_log_bytesis clamped down to the configured value. - A smaller caller-supplied
Limitis honored unchanged. - Custom tracers (for example
callTracer,prestateTracer, or JavaScript tracers) are unaffected, as are requests when the cap is disabled (0).
Removed legacy trace filters
The legacy
*ExcludeTraceFail endpoints have been removed. For block tracing, use debug_traceBlockByNumber or debug_traceBlockByHash. For EVM receipts, use eth_getTransactionReceipt. There is no block or filter method to discover synthetic logs from Cosmos-originated transactions. If you already know a synthetic transaction hash, enable sei_getTransactionReceipt to get its receipt and logs.Next steps
- JavaScript tracers: Custom analysis scripts
- Troubleshooting: Common issues and solutions
Start with
callTracer for general debugging. Then use specialized tracers for specific analysis needs.