Summary
debug_traceTransaction returns {"result": null} (HTTP 200) for a large fraction of X Layer mainnet transactions, even though the transactions exist, succeeded on-chain, and are traceable via other methods. Through some investigation this appears to be caused by the gasless (zero-fee) transaction feature interacting badly with the transaction-replay path used by the RPC tracer.
Environment
xlayer-reth-node Version: 0.1.0-dev (dbc1495) # tag v0.0.6.3
Upstream Reth Version: 5101851b
Observed behavior
For a transaction that is not the first non-deposit transaction in its block, debug_traceTransaction returns null:
# Example on X Layer mainnet: tx at block 64,564,751, index 5
curl -s -X POST "$RPC" -H 'Content-Type: application/json' -d '{
"jsonrpc":"2.0","id":1,"method":"debug_traceTransaction",
"params":["0xfeba105bc2c9a813712fe78f6619be7bea4601ddc8df99943f843737ffe8cd06",
{"tracer":"callTracer"}]
}'
# -> {"jsonrpc":"2.0","id":1,"result":null}
The transaction clearly exists and succeeded:
eth_getTransactionByHash / eth_getTransactionReceipt return it (status: 0x1, transactionIndex: 0x5).
debug_traceBlockByNumber on the same block returns a full, correct trace for all transactions in the block, including this one.
- The parity-style
trace_transaction on the same hash does not return null but surfaces an error instead:
{"jsonrpc":"2.0","id":1,"error":{"code":-32000,"message":"max fee per gas less than block base fee"}}
So the state and history are present; only the single-transaction trace path fails.
Reproduction pattern (root cause)
Tracing each transaction in block 64,564,751 individually with debug_traceTransaction shows a clear boundary:
| index |
tx type |
effective gas price |
debug_traceTransaction |
| 0 |
126 (deposit) |
0 |
OK |
| 1 |
2 (EIP-1559) |
maxFeePerGas = 0 |
OK (traced directly) |
| 2..8 |
mixed |
— |
null |
Index 1 is a gasless transaction: a type 0x2 tx with maxFeePerGas = 0 / maxPriorityFeePerGas = 0, included in a block whose baseFeePerGas = 20000000.
- Tracing index 1 directly works, because the tracer disables the base-fee check for the target transaction.
- Tracing any transaction at index ≥ 2 fails, because
debug_traceTransaction first replays the preceding transactions in the block to reconstruct the pre-state — and replaying the gasless index-1 tx goes through the normal execution path with base-fee validation enabled. The zero-fee tx is rejected with max fee per gas less than block base fee, the replay aborts, and debug_traceTransaction swallows the error and returns null.
In other words: any transaction that is preceded in its block by a gasless (zero-fee) transaction cannot be traced with debug_traceTransaction / trace_transaction / trace_get / trace_replayTransaction. Since gasless transactions are common, this affects a large share of trace requests. Block-level tracers (debug_traceBlockByNumber / debug_traceBlockByHash) are unaffected because they execute the block through the block executor, which already applies the gasless base-fee relaxation.
Suggested cause / fix
The gasless base-fee relaxation is applied in the block executor (OpBlockExecutor::execute_transaction_without_commit) and is also re-implemented for the flashblocks builder. The builder code even documents this explicitly — see crates/builder/src/flashblocks/context.rs, transact_maybe_gasless / is_gasless:
"Mirrors the gasless detection in the upstream block executor … The flashblocks builder executes pool transactions directly via Evm::transact rather than through the block executor, so the detection and base-fee relaxation have to be replicated here, otherwise zero-priced (whitelisted) transactions would be rejected by base-fee validation even when gasless is enabled."
The RPC tracing/replay path (the eth/debug/trace namespaces, delegated to the upstream OpEthApi replay_transactions_until) is a third place that executes transactions via Evm::transact outside the block executor, and it does not replicate the gasless detection. As a result, preceding gasless transactions are rejected during replay.
Two possible directions:
- Apply the same gasless detection (
set_gasless(true) when the tx is a whitelisted zero-fee tx) when replaying preceding transactions in the RPC tracing path, mirroring transact_maybe_gasless.
- Disable base-fee validation for all transactions replayed during tracing (the tracer already disables it for the target transaction; extending this to the replayed predecessors would also resolve it and is arguably correct for tracing in general).
Impact
debug_traceTransaction, trace_transaction, trace_get, and trace_replayTransaction return null / error for any transaction positioned after a gasless transaction in the same block, despite the data being fully available on the node.
Summary
debug_traceTransactionreturns{"result": null}(HTTP 200) for a large fraction of X Layer mainnet transactions, even though the transactions exist, succeeded on-chain, and are traceable via other methods. Through some investigation this appears to be caused by the gasless (zero-fee) transaction feature interacting badly with the transaction-replay path used by the RPC tracer.Environment
Observed behavior
For a transaction that is not the first non-deposit transaction in its block,
debug_traceTransactionreturnsnull:The transaction clearly exists and succeeded:
eth_getTransactionByHash/eth_getTransactionReceiptreturn it (status: 0x1,transactionIndex: 0x5).debug_traceBlockByNumberon the same block returns a full, correct trace for all transactions in the block, including this one.trace_transactionon the same hash does not returnnullbut surfaces an error instead:{"jsonrpc":"2.0","id":1,"error":{"code":-32000,"message":"max fee per gas less than block base fee"}}So the state and history are present; only the single-transaction trace path fails.
Reproduction pattern (root cause)
Tracing each transaction in block
64,564,751individually withdebug_traceTransactionshows a clear boundary:debug_traceTransactionIndex 1 is a gasless transaction: a
type 0x2tx withmaxFeePerGas = 0/maxPriorityFeePerGas = 0, included in a block whosebaseFeePerGas = 20000000.debug_traceTransactionfirst replays the preceding transactions in the block to reconstruct the pre-state — and replaying the gasless index-1 tx goes through the normal execution path with base-fee validation enabled. The zero-fee tx is rejected withmax fee per gas less than block base fee, the replay aborts, anddebug_traceTransactionswallows the error and returnsnull.In other words: any transaction that is preceded in its block by a gasless (zero-fee) transaction cannot be traced with
debug_traceTransaction/trace_transaction/trace_get/trace_replayTransaction. Since gasless transactions are common, this affects a large share of trace requests. Block-level tracers (debug_traceBlockByNumber/debug_traceBlockByHash) are unaffected because they execute the block through the block executor, which already applies the gasless base-fee relaxation.Suggested cause / fix
The gasless base-fee relaxation is applied in the block executor (
OpBlockExecutor::execute_transaction_without_commit) and is also re-implemented for the flashblocks builder. The builder code even documents this explicitly — seecrates/builder/src/flashblocks/context.rs,transact_maybe_gasless/is_gasless:The RPC tracing/replay path (the
eth/debug/tracenamespaces, delegated to the upstreamOpEthApireplay_transactions_until) is a third place that executes transactions viaEvm::transactoutside the block executor, and it does not replicate the gasless detection. As a result, preceding gasless transactions are rejected during replay.Two possible directions:
set_gasless(true)when the tx is a whitelisted zero-fee tx) when replaying preceding transactions in the RPC tracing path, mirroringtransact_maybe_gasless.Impact
debug_traceTransaction,trace_transaction,trace_get, andtrace_replayTransactionreturnnull/ error for any transaction positioned after a gasless transaction in the same block, despite the data being fully available on the node.