debug_traceTransaction returns null for transactions preceded by a gasless (zero-fee) tx in the same block
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 17
- Forks
- 6
- PR merge metrics
- No merged PRs in 30d
Description
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_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.- The parity-style
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,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_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 — 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::transactrather 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, mirroringtransact_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.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the RPC tracing replay entry point, replay_transactions_until, and compare its transaction execution with crates/builder/src/flashblocks/context.rs, especially transact_maybe_gasless and is_gasless. Reproduce the provided block 64,564,751 case with debug_traceTransaction, then verify that transactions after a gasless transaction trace successfully without changing block-level tracing behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100