Skip to content

feat(l1): add noopTracer for debug_traceTransaction and debug_traceBlockByNumber#6657

Open
ElFantasma wants to merge 5 commits into
mainfrom
feat/6645-noop-tracer
Open

feat(l1): add noopTracer for debug_traceTransaction and debug_traceBlockByNumber#6657
ElFantasma wants to merge 5 commits into
mainfrom
feat/6645-noop-tracer

Conversation

@ElFantasma
Copy link
Copy Markdown
Contributor

Motivation

The noopTracer is a standard Geth debug tracer that runs a transaction through the trace harness without recording any per-step data. It is mostly used for benchmarking trace-pipeline overhead and as a smoke test for the tracer dispatch machinery, and several downstream tools expect it to be available.

Description

Adds noopTracer as a third TracerType alongside callTracer and prestateTracer, wired through debug_traceTransaction and debug_traceBlockByNumber:

  • LEVM::trace_tx_noop in crates/vm/backends/levm/tracing.rs — executes the tx with LevmCallTracer::disabled() (same disabled tracer already used by trace_tx_prestate) and discards the result.
  • Evm::trace_tx_noop in crates/vm/tracing.rs — thin façade.
  • Blockchain::trace_transaction_noop / Blockchain::trace_block_noop in crates/blockchain/tracing.rs — rebuild parent state, replay earlier txs, run target tx(s) under the noop tracer with the usual timeout wrapping.
  • TracerType::NoopTracer variant in crates/networking/rpc/tracing.rs plus arms in both handlers. Single-tx returns {}; block returns [{ txHash, result: {} }, …] using the existing BlockTraceComponent wrapper.

No new types, no new files, no storage changes.

Closes #6645

How to Test

curl -X POST http://localhost:8545 -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"debug_traceTransaction","params":["<TX_HASH>",{"tracer":"noopTracer"}],"id":1}'
# Expected: {"jsonrpc":"2.0","id":1,"result":{}}

curl -X POST http://localhost:8545 -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"debug_traceBlockByNumber","params":["latest",{"tracer":"noopTracer"}],"id":1}'
# Expected: [{"txHash":"0x…","result":{}}, …]

Checklist

@ElFantasma ElFantasma requested a review from a team as a code owner May 14, 2026 15:38
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 14, 2026

⚠️ Known Issues — intentionally skipped tests

Source: docs/known_issues.md

Known Issues

Tests intentionally excluded from CI. Source of truth for the Known
Issues
section the L1 workflow appends to each ef-tests job summary
and posts as a sticky PR comment.

Hive — bal-devnet-6 Amsterdam consume-engine tests — 32 functions / 54 cases

Same root cause as the blockchain-runner skip list (see EF Tests —
Blockchain
below): snobal-devnet-6 fixtures expect bal-devnet-6 spec
semantics, but our impl runs ahead due to the bal-devnet-7-prep
set_delegation SELFDESTRUCT-style refund subtraction. These fixtures
are routed through hive's eels/consume-engine simulator and produce
the same failures. Excluded via KNOWN_EXCLUDED_TESTS (substring
match on test_<name>[fork_Amsterdam, anchoring to the Amsterdam fork
so legacy Prague/Osaka variants still run).

Affected EELS test functions (32)
  • test_auth_refund_block_gas_accounting
  • test_auth_refund_bypasses_one_fifth_cap
  • test_auth_state_gas_scales_with_cpsb
  • test_auth_with_calldata_and_access_list
  • test_auth_with_multiple_sstores
  • test_authorization_exact_state_gas_boundary
  • test_authorization_to_precompile_address
  • test_authorization_with_sstore
  • test_bal_7702_delegation_clear
  • test_bal_7702_delegation_create
  • test_bal_7702_delegation_update
  • test_bal_7702_double_auth_reset
  • test_bal_7702_double_auth_swap
  • test_bal_7702_null_address_delegation_no_code_change
  • test_bal_all_transaction_types
  • test_bal_selfdestruct_to_7702_delegation
  • test_bal_withdrawal_to_7702_delegation
  • test_duplicate_signer_authorizations
  • test_existing_account_auth_header_gas_used_uses_worst_case
  • test_existing_account_refund
  • test_existing_account_refund_enables_sstore
  • test_existing_auth_with_reverted_execution_preserves_intrinsic
  • test_many_authorizations_state_gas
  • test_mixed_auths_header_gas_used_uses_worst_case
  • test_mixed_new_and_existing_auths
  • test_mixed_valid_and_invalid_auths
  • test_multi_tx_block_auth_refund_and_sstore
  • test_multiple_refund_types_in_one_tx
  • test_simple_gas_accounting
  • test_sstore_state_gas_all_tx_types
  • test_transfer_with_all_tx_types
  • test_varying_calldata_costs

EF Tests — Stateless coverage narrowed to EIP-8025 optional-proofs

make -C tooling/ef_tests/blockchain test calls test-stateless-zkevm
instead of test-stateless. The zkevm@v0.3.3 fixtures are filled against
bal@v5.6.1, out of sync with current bal spec; the broad target trips ~549
fixtures. Re-broaden once the zkevm bundle is regenerated.

Why and resolution path

PR #6527 broadened
test-stateless to extract the entire for_amsterdam/ tree from the
zkevm bundle and run all of it under --features stateless; combined with
this branch's bal-devnet-6+ semantics (and bal-devnet-7-prep
set_delegation re-application) that scope produces ~549
GasUsedMismatch / ReceiptsRootMismatch /
BlockAccessListHashMismatch failures.

test-stateless-zkevm filters cargo to the eip8025_optional_proofs
suite, which still validates the stateless harness without the bal-version
mismatch.

Re-broaden by switching test: back to test-stateless in
tooling/ef_tests/blockchain/Makefile once the zkevm bundle is regenerated
against the current bal spec.

EF Tests — Blockchain bal-devnet-6 (Amsterdam fork) — 74 tests

snobal-devnet-6 fixtures expect bal-devnet-6 spec semantics, but our impl
runs ahead due to the bal-devnet-7-prep set_delegation SELFDESTRUCT-style
refund subtraction. Skipped in
tooling/ef_tests/blockchain/tests/all.rs::SKIPPED_BASE, anchored on
[fork_Amsterdam so legacy Prague / Osaka variants still run.

Bucket breakdown (74 total) and resolution path
EIP Bucket Count
EIP-7702 set_code_txs 24
EIP-7702 set_code_txs_2 15
EIP-7702 gas 1
EIP-8037 state_gas_set_code 17
EIP-8037 state_gas_pricing 1
EIP-8037 state_gas_sstore 1
EIP-7928 block_access_lists_eip7702 8
EIP-7928 block_access_lists 1
EIP-7778 gas_accounting 3
EIP-7708 transfer_logs 1
EIP-7976 refunds 1
EIP-1344 chainid (Amsterdam fork-transition fixture) 1
Total 74

Re-enable once we either:

  • (a) bump fixtures to a snobal-devnet-7 release that locks in the new
    accounting; or
  • (b) revert the bal-devnet-7-prep subtraction for bal-devnet-6
    compatibility.
Full test list (74)

EIP-7702 — for_amsterdam/prague/eip7702_set_code_tx/set_code_txs/

  • delegation_clearing
  • delegation_clearing_and_set
  • delegation_clearing_failing_tx
  • delegation_clearing_tx_to
  • eoa_tx_after_set_code
  • ext_code_on_chain_delegating_set_code
  • ext_code_on_self_delegating_set_code
  • ext_code_on_self_set_code
  • ext_code_on_set_code
  • many_delegations
  • nonce_overflow_after_first_authorization
  • nonce_validity
  • reset_code
  • self_code_on_set_code
  • self_sponsored_set_code
  • set_code_multiple_valid_authorization_tuples_same_signer_increasing_nonce
  • set_code_multiple_valid_authorization_tuples_same_signer_increasing_nonce_self_sponsored
  • set_code_to_log
  • set_code_to_non_empty_storage_non_zero_nonce
  • set_code_to_self_destruct
  • set_code_to_self_destructing_account_deployed_in_same_tx
  • set_code_to_sstore
  • set_code_to_sstore_then_sload
  • set_code_to_system_contract

EIP-7702 — for_amsterdam/prague/eip7702_set_code_tx/set_code_txs_2/

  • call_pointer_to_created_from_create_after_oog_call_again
  • call_to_precompile_in_pointer_context
  • contract_storage_to_pointer_with_storage
  • delegation_replacement_call_previous_contract
  • double_auth
  • pointer_measurements
  • pointer_normal
  • pointer_reentry
  • pointer_resets_an_empty_code_account_with_storage
  • pointer_reverts
  • pointer_to_pointer
  • pointer_to_precompile
  • pointer_to_static
  • pointer_to_static_reentry
  • static_to_pointer

EIP-7702 — for_amsterdam/prague/eip7702_set_code_tx/gas/

  • account_warming

EIP-8037 — for_amsterdam/amsterdam/eip8037_state_creation_gas_cost_increase/state_gas_set_code/

  • auth_refund_block_gas_accounting
  • auth_refund_bypasses_one_fifth_cap
  • auth_with_calldata_and_access_list
  • auth_with_multiple_sstores
  • authorization_exact_state_gas_boundary
  • authorization_to_precompile_address
  • authorization_with_sstore
  • duplicate_signer_authorizations
  • existing_account_auth_header_gas_used_uses_worst_case
  • existing_account_refund
  • existing_account_refund_enables_sstore
  • existing_auth_with_reverted_execution_preserves_intrinsic
  • many_authorizations_state_gas
  • mixed_auths_header_gas_used_uses_worst_case
  • mixed_new_and_existing_auths
  • mixed_valid_and_invalid_auths
  • multi_tx_block_auth_refund_and_sstore

EIP-8037 — state_gas_pricing/

  • auth_state_gas_scales_with_cpsb

EIP-8037 — state_gas_sstore/

  • sstore_state_gas_all_tx_types

EIP-7928 — for_amsterdam/amsterdam/eip7928_block_level_access_lists/block_access_lists_eip7702/

  • bal_7702_delegation_clear
  • bal_7702_delegation_create
  • bal_7702_delegation_update
  • bal_7702_double_auth_reset
  • bal_7702_double_auth_swap
  • bal_7702_null_address_delegation_no_code_change
  • bal_selfdestruct_to_7702_delegation
  • bal_withdrawal_to_7702_delegation

EIP-7928 — block_access_lists/

  • bal_all_transaction_types

EIP-7778 — for_amsterdam/amsterdam/eip7778_block_gas_accounting_without_refunds/gas_accounting/

  • multiple_refund_types_in_one_tx
  • simple_gas_accounting
  • varying_calldata_costs

EIP-7708 — for_amsterdam/amsterdam/eip7708_eth_transfer_logs/transfer_logs/

  • transfer_with_all_tx_types

EIP-7976 — for_amsterdam/amsterdam/eip7976_increase_calldata_floor_cost/refunds/

  • gas_refunds_from_data_floor

EIP-1344 — for_amsterdam/istanbul/eip1344_chainid/chainid/

  • chainid (Amsterdam fork-transition fixture)

@github-actions github-actions Bot added the L1 Ethereum client label May 14, 2026
@github-actions
Copy link
Copy Markdown

🤖 Kimi Code Review

The PR implements noopTracer support correctly, following existing patterns in the codebase. The implementation properly executes transactions without recording trace data, matching geth's behavior.

Minor observations:

  1. crates/blockchain/tracing.rs:113-114 - The Arc<Mutex<VM>> wrapping is necessary if timeout_trace_operation spawns blocking tasks (requiring Send), though it adds slight overhead for sequential execution. This is the correct defensive approach for async timeout handling.

  2. crates/blockchain/tracing.rs:108 - Consider verifying that rerun_block(&block, Some(0)) correctly initializes the block state without executing any transactions (i.e., applies only the block reward/coinbase state changes if applicable).

  3. crates/vm/backends/levm/tracing.rs:108 - Ensure LevmCallTracer::disabled() (not shown in diff) properly disables all tracing hooks to avoid performance overhead during execution.

  4. Error handling consistency - The mutex poisoning error conversion to EvmError::Custom at line 116 is acceptable, though consider using a more specific error variant if one exists for synchronization failures.

Security/Performance:

  • The reexec parameter bounds prevent unbounded state reconstruction (DoS vector), and the timeout parameter prevents long-running traces. Both are handled correctly.
  • No state corruption risks identified; the mutable database reference is properly scoped within each transaction execution.

Verdict: LGTM. The implementation correctly follows the established patterns for tracer integration and properly handles the async timeout requirements.


Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 14, 2026

Lines of code report

Total lines added: 101
Total lines removed: 0
Total lines changed: 101

Detailed view
+-------------------------------------------+-------+------+
| File                                      | Lines | Diff |
+-------------------------------------------+-------+------+
| ethrex/crates/blockchain/tracing.rs       | 237   | +48  |
+-------------------------------------------+-------+------+
| ethrex/crates/networking/rpc/tracing.rs   | 278   | +22  |
+-------------------------------------------+-------+------+
| ethrex/crates/vm/backends/levm/tracing.rs | 314   | +15  |
+-------------------------------------------+-------+------+
| ethrex/crates/vm/tracing.rs               | 83    | +16  |
+-------------------------------------------+-------+------+

@github-actions
Copy link
Copy Markdown

🤖 Claude Code Review

Now I have all the context needed for a thorough review. Let me compile the findings.


Review: feat(l1): add noopTracer for debug_traceTransaction and debug_traceBlockByNumber

Overall Assessment

The implementation is correct and closely mirrors the patterns established by callTracer and prestateTracer. The core logic is sound, the serde mapping is accurate, and no security or consensus issues are introduced. A few minor points below.


Correctness

State accumulation in trace_block_noop — correct.
vm.rerun_block(&block, Some(0)) runs prepare_block (system calls, etc.) and stops before tx 0, matching the pattern in trace_block_calls (line 67) and trace_block_prestate (line 195). Because LEVM::trace_tx_noop borrows db mutably and vm.execute() commits state into db.current_accounts_state, each subsequent tx in the loop correctly sees the accumulated state from all previous txs. This is consistent with how trace_tx_prestate is handled.

Serde mapping — correct.
#[serde(rename_all = "camelCase")] on TracerType maps NoopTracer"noopTracer", which matches Geth's tracer name. ✓

Empty return value — correct.
serde_json::json!({}) per-tx matches Geth's noopTracer output format. ✓

Error propagation — correct.
vm.execute()? propagates real VM errors; transaction reverts surface as Ok(ExecutionResult::Reverted{...}) inside the VM, not as Err, so the noop path handles them the same way as the other tracers.


Issues

1. Missing explanatory comment on rerun_block call (crates/blockchain/tracing.rs, new trace_block_noop)

The analogous methods have comments that explain the Some(0) argument:

  • trace_block_calls (line 67): "// Run anything necessary before executing the block's transactions (system calls, etc)"
  • trace_block_prestate (line 195): "// Run system calls but stop before tx 0"

trace_block_noop has none, making it easy to misread as a no-op call or as incorrectly starting at tx 0. Adding the same comment would make the intent immediately clear.


2. Asymmetric return type in trace_block_noop (crates/blockchain/tracing.rs, line 117)

The two existing block tracers return Vec<(H256, T)>:

  • trace_block_callsResult<Vec<(H256, CallTrace)>, ChainError>
  • trace_block_prestateResult<Vec<(H256, PrestateResult)>, ChainError>

trace_block_noop returns Result<Vec<H256>, ChainError> — dropping the tuple — which forces the RPC handler to reconstruct BlockTraceComponent manually rather than using the existing From<(H256, TxTrace)> impl. Returning Result<Vec<(H256, ())>, ChainError> or even Result<BlockTrace<serde_json::Value>, ChainError> would keep the API surface uniform and allow the RPC handler to use the same .map(Into::into).collect() pipeline as the other arms. As written it works, but it creates a one-off pattern that's easy to diverge from in future tracers.


3. block.as_ref() — redundant but harmless (crates/blockchain/tracing.rs, line 133)

let tx_hash = block.as_ref().body.transactions[index].hash();

Arc<Block> already Derefs to Block, so .as_ref() is unnecessary. This is copied verbatim from trace_block_calls (line 77) and trace_block_prestate (line 204), so it's at least consistent — but both the old and new sites could drop .as_ref().


Style / Minor

  • The error strings "Transaction not Found" and "Block not Found" (capital F) appear in trace_transaction_noop lines 101 and 105. These are directly copied from trace_transaction_calls (lines 31, 35) and trace_transaction_prestate (lines 160, 164), so they are consistent within the codebase — no new inconsistency introduced.

  • trace_tx_noop at the LEVM level (crates/vm/backends/levm/tracing.rs:97) correctly reuses LevmCallTracer::disabled(), the same disabled tracer used by trace_tx_prestate. No duplication of tracer logic. ✓


Summary

No bugs, no security issues, no consensus risk. Two actionable suggestions: add the rerun_block explanatory comment to trace_block_noop, and consider aligning the return type of trace_block_noop with Vec<(H256, T)> for API consistency. Everything else is correct and idiomatic for this codebase.


Automated review by Claude (Anthropic) · sonnet · custom prompt

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 14, 2026

Greptile Summary

This PR adds noopTracer as a third TracerType option for debug_traceTransaction and debug_traceBlockByNumber, completing the standard Geth tracer triad alongside callTracer and prestateTracer. The implementation is wired consistently through all four layers (LEVM, Evm façade, Blockchain, RPC handler) following the same Arc/Mutex/timeout pattern established by the existing tracers.

  • LEVM::trace_tx_noop reuses LevmCallTracer::disabled() (same as trace_tx_prestate) and simply calls vm.execute() without capturing any output, correctly exercising the trace harness without recording per-step data.
  • TracerType::NoopTracer is deserialized from "noopTracer" (correct via rename_all = "camelCase") and returns {} for single-transaction requests or [{txHash, result:{}}] for block requests, matching Geth's specified output format.
  • State accumulation across transactions in trace_block_noop is correct — each trace_tx_noop call mutates &mut self.db in-place, so subsequent transactions see the prior transactions' state changes.

Confidence Score: 5/5

Safe to merge — the noopTracer is a thin, read-only addition that runs transactions through the existing EVM harness without any state commits to storage or new data structures.

The four changed files each follow the established pattern for existing tracers character for character. State management, timeout wrapping, Arc/Mutex usage, serde deserialization, and JSON output format are all consistent with the callTracer and prestateTracer implementations that are already in production. No edge cases in the new code paths deviate from the existing tracers.

No files require special attention.

Important Files Changed

Filename Overview
crates/vm/backends/levm/tracing.rs Adds LEVM::trace_tx_noop — runs the transaction with LevmCallTracer::disabled() and discards the result; mirrors trace_tx_prestate exactly but without the pre-snapshot capture and post-state build, which is correct for a noop tracer.
crates/vm/tracing.rs Thin Evm::trace_tx_noop facade that delegates to LEVM::trace_tx_noop, consistent with the existing trace_tx_calls / trace_tx_prestate pattern.
crates/blockchain/tracing.rs Adds trace_transaction_noop and trace_block_noop following the exact same Arc/Mutex/timeout pattern as the existing call and prestate tracers; state correctly accumulates across transactions in the block variant.
crates/networking/rpc/tracing.rs Adds TracerType::NoopTracer variant (deserializes correctly from noopTracer via rename_all = camelCase) and wires it through both RPC handlers; response format ({} / [{txHash, result:{}}]) matches Geth's noopTracer output.

Reviews (1): Last reviewed commit: "feat(l1): add noopTracer for debug_trace..." | Re-trigger Greptile

@github-actions
Copy link
Copy Markdown

🤖 Codex Code Review

No blocking findings.

Residual risk:

  1. The new noopTracer branches at crates/networking/rpc/tracing.rs and crates/networking/rpc/tracing.rs add public RPC behavior without regression coverage. Because block mode depends on sequential VM state mutation in crates/blockchain/tracing.rs, I’d add tests for both endpoints, ideally with a block where tx 2 depends on tx 1’s writes, to lock in both the {} response shape and the per-tx state progression.

The implementation itself is aligned with the existing call/prestate tracing flow; I didn’t spot an obvious EVM/gas-accounting, consensus, or security regression in the noop path from source inspection.

I couldn’t run cargo check in this sandbox because ~/.rustup / ~/.cargo are read-only here and the workspace needs git-backed dependencies.


Automated review by OpenAI Codex · gpt-5.4 · custom prompt

@ElFantasma
Copy link
Copy Markdown
Contributor Author

Follow-up commit 705073046 folds in two of the points raised:

  • Added the // Run system calls but stop before tx 0 comment in trace_block_noop to match the other block tracers.
  • Changed trace_block_noop to return Vec<(H256, ())> for symmetry with trace_block_calls / trace_block_prestate; RPC handler now uses the existing BlockTraceComponent From impl.

Skipped intentionally:

  • block.as_ref() redundancy (Claude review): kept verbatim from the call/prestate tracers since Arc<Block> already Derefs. Cleaning it up cleanly would mean editing all three block tracers, which is out of scope for this PR — happy to do it as a separate cleanup.
  • RPC-level regression tests (Codex review): no precedent in this crate — neither callTracer nor prestateTracer have RPC-handler tests, only LEVM-backend tests of their substantive behavior. There isn't substantive behavior to test at the LEVM level for noopTracer (it's vm.execute() with LevmCallTracer::disabled(), which is already exercised by trace_tx_prestate). Adding RPC-level harness scaffolding here would set a new precedent for one tracer; better as a separate "add tracer test harness" PR if the team wants it.

Also: kept #[allow(clippy::enum_variant_names)] instead of renaming variants to Call/Prestate/Noop — that route would require three per-variant #[serde(rename = "...")] attributes to keep the wire format ("callTracer", etc.) intact, which trades one allow for three renames.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

L1 Ethereum client

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Implement noopTracer for debug_trace*

1 participant