Skip to content

feat(l1): log block hash in per-block execution metric line#6736

Open
edg-l wants to merge 1 commit into
mainfrom
feat/l1/block-hash-in-block-metric-log
Open

feat(l1): log block hash in per-block execution metric line#6736
edg-l wants to merge 1 commit into
mainfrom
feat/l1/block-hash-in-block-metric-log

Conversation

@edg-l
Copy link
Copy Markdown
Contributor

@edg-l edg-l commented May 27, 2026

Motivation

The per-block execution metric line emitted on the engine_newPayload / add_block_pipeline path logs the block number but not the block hash:

[METRIC] BLOCK 24358000 | 1.234 Ggas/s | 567.00 ms | 150 txs | 700 Mgas (93%)

The block number alone is not a stable key for correlating these metrics with a specific block: benchmarking and replay harnesses commonly roll back and re-import at the same height, so the same block number maps to many distinct blocks. The block hash is the only content-unique identifier.

This also helps ethpandaops/benchmarkoor, which captures client block logs and associates them with tests strictly by block hash ({ "block": { "hash": "0x..." } }). Without the hash on this line, ethrex runs there can't attach per-block metrics; with it, a log parser can match them. (benchmarkoor drives blocks via engine_newPayload, which goes through this exact pipeline log.)

Change

Thread the already-available block.hash() into print_add_block_pipeline_logs and include it in the metric header:

[METRIC] BLOCK 24358000 0x<hash> | 1.234 Ggas/s | 567.00 ms | 150 txs | 700 Mgas (93%)

The hash was already computed in this function on the witness/BAL branches; it is now computed once before the block is moved into store_block and reused, so there is no extra hashing on the common path. Uses the existing {:#x} block-hash convention (see dev/block_producer.rs).

Log-consumer check

Verified the existing in-tree log parsers are unaffected:

  • tooling/log_analysis/log_analysis.ipynb — regex is hardcoded to the BLOCK EXECUTION THROUGHPUT (N): ... line (print_add_block_logs), a different line that this PR does not touch.
  • tooling/import_benchmark/parse_bench.py — keys off the first ) in the line; the hash is inserted before the trailing (...%), so its parsing is unchanged.

No other consumers of this line exist in the repo (Grafana panels read Prometheus metrics, not logs).

@edg-l edg-l requested a review from a team as a code owner May 27, 2026 15:35
@github-actions
Copy link
Copy Markdown

⚠️ 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.

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-7 semantics 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.

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

🤖 Kimi Code Review

Review Summary: This is a good refactoring that eliminates redundant block hash calculations and improves observability. No issues found.

Detailed Feedback:

Performance Optimization (Line 1936)

  • Good: Moving let block_hash = block.hash(); to line 1936 eliminates duplicate hash computations. Previously block.hash() was called twice (lines 1942 and 1954 in the original), and since block hashing involves Keccak256 operations, this provides a minor but worthwhile performance improvement.

Code Quality (Lines 1951-1956)

  • Good: The refactored if let chaining using && is idiomatic modern Rust and reduces nesting. The new structure:
    if let Some(bal) = &produced_bal
        && let Err(err) = self.storage.store_block_access_list(block_hash, bal)
    is cleaner than the nested approach and correctly handles the error case without redundant hash computation.

Function Signature Change (Line 2045)

  • Good: Adding block_hash: H256 to print_block_execution_metrics is appropriate since H256 implements Copy, making the parameter pass-by-value efficient and safe.

Observability Improvement (Lines 2133-2134)

  • Good: Including block_hash in the metrics output (using {:#x} for hex formatting) significantly improves debuggability when correlating metrics with specific blocks. The format string correctly preserves all existing fields while adding the hash identifier.

Ethereum-Specific Verification

  • Correctness: block.hash() is deterministic and infallible for valid block headers, so hoisting the computation is safe.
  • BAL Storage: Using the pre-computed hash for store_block_access_list maintains the existing behavior while being more efficient.

Suggestion (Minor):
Consider whether the full 64-character hex hash is necessary for metrics readability, or if a truncated form (e.g., format!("{:.8}", block_hash)) would suffice for the metrics line while keeping logs concise. However, full hash is safer for uniqueness.

Verdict: LGTM. The changes improve performance, reduce code duplication, and enhance observability without introducing any safety issues.


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

@github-actions
Copy link
Copy Markdown

🤖 Claude Code Review

Review: feat(l1): log block hash in per-block execution metric line

One file changedcrates/blockchain/blockchain.rs | +9 / -7


Summary

This PR adds the block hash to the per-block execution metric line and incidentally cleans up two duplicate block.hash() calls that previously lived in separate conditional branches.


Code Quality

Deduplication of block.hash() — The original code computed the hash independently in:

  • The witness/logger branch (if let Some(logger) = logger && ...)
  • The BAL-storage branch (if let Some(bal) = &produced_bal)

Moving it before both branches is the right call, especially since store_block later consumes block by move, making any later call impossible without cloning.

Let-chain refactoring (lines ~1955–1959) — The flattened form is idiomatic and matches the if let ... && let ... style already used just above it. No functional change, clean improvement.

Format string{:#x} for H256 is consistent with the project convention (confirmed in dev/block_producer.rs). No concern here.

Function signatureblock_hash: H256 is added after block_number: u64, which is the natural ordering. H256 is Copy, so passing by value is correct.


Potential Concerns

Hash computation is now unconditional. Previously block.hash() was only called when at least one of the two conditional branches was entered. Now it is always computed. This is required since the metric line always needs it — but it's worth acknowledging: on a path where neither the logger nor the BAL branch is active, this adds one Keccak256 of the RLP-encoded header per block. At per-block granularity this is negligible, but it is a behavioral change from the original.

Log consumer compatibility. The PR body asserts parse_bench.py is unaffected because it "keys off the first )". This is plausible — the hash is inserted between the block number and the first |, well before the (XX%) suffix. It would be good to confirm this by glancing at the actual parser, but the reasoning is sound. The log_analysis.ipynb note is correct (that regex targets a different line entirely).

No test for the new log format. The metric line format is not covered by unit tests (this appears to be the convention in this file), so a format regression would only be caught at runtime. This is a pre-existing gap, not something introduced by this PR.


Summary

The change is correct, well-scoped, and well-motivated. The deduplication of block.hash() is a genuine improvement, the let-chain flattening is idiomatic, and the hash format matches the project convention. The only minor flag is that hash computation is now unconditional — but since the metric line always needs it, this is the only reasonable implementation. No blocking issues.


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

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 27, 2026

Greptile Summary

Adds the block hash to the per-block [METRIC] BLOCK … log line emitted on the engine_newPayload / add_block_pipeline path, enabling block-hash-based correlation in external benchmarking tools such as benchmarkoor.

  • block_hash is now computed once before store_block consumes the block, replacing the two separate per-branch computations that existed before; this is both necessary (block is moved before the logging call) and a minor de-duplication.
  • The BAL if-let is refactored from a nested if let into a let-chain, keeping the logic identical with a smaller nesting level.
  • print_add_block_pipeline_logs gains a block_hash: H256 parameter, formatted with {:#x} consistent with the existing convention in the codebase.

Confidence Score: 5/5

Safe to merge — the change is additive and purely affects log output.

The change is small and well-scoped: it hoists a hash computation that was already happening in one or two conditional branches to just before the point where the block is moved (which made the pre-computation necessary anyway), then threads it through the logging function. No data path, storage operation, or error handling is affected. The format-string change is backward-compatible with the confirmed log consumers described in the PR.

No files require special attention.

Important Files Changed

Filename Overview
crates/blockchain/blockchain.rs Moves block_hash computation before store_block (which consumes the block), passes it to print_add_block_pipeline_logs, and adds it to the METRIC log header. Also refactors the BAL if-let into a let-chain. No logic issues found.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[add_block_pipeline] --> B[execute_block_pipeline]
    B --> C[Extract gas_used, gas_limit,\nblock_number, transactions_count]
    C --> D["block_hash = block.hash()\n(computed once here)"]
    D --> E{logger && accumulated_updates?}
    E -- Yes --> F[generate_witness\nstore_witness using block_hash]
    E -- No --> G{produced_bal?}
    F --> G
    G -- Yes --> H[store_block_access_list\nusing block_hash]
    G -- No --> I[store_block\nconsumes block]
    H --> I
    I --> J{perf_logs_enabled?}
    J -- Yes --> K["print_add_block_pipeline_logs\n[METRIC] BLOCK {num} {#x hash} | ..."]
    J -- No --> L[return]
    K --> L
Loading

Reviews (1): Last reviewed commit: "feat(l1): log block hash in per-block ex..." | Re-trigger Greptile

@github-actions
Copy link
Copy Markdown

Lines of code report

Total lines added: 2
Total lines removed: 0
Total lines changed: 2

Detailed view
+----------------------------------------+-------+------+
| File                                   | Lines | Diff |
+----------------------------------------+-------+------+
| ethrex/crates/blockchain/blockchain.rs | 2553  | +2   |
+----------------------------------------+-------+------+

@github-actions
Copy link
Copy Markdown

🤖 Codex Code Review

No findings.

The diff in blockchain.rs looks safe: it hoists block.hash() into a single local, preserves the witness/BAL storage behavior, and only extends the perf log header in print_add_block_pipeline_logs to include the block hash. I don’t see any impact on execution semantics, gas accounting, consensus rules, trie/storage correctness, or security-sensitive paths; this is effectively an observability-only change plus a minor deduplication of hashing work.

I couldn’t complete a compile check in this environment: the rustup shim tries to write under a read-only path, and an offline direct-cargo attempt fails because the git dependency libssz is not cached locally. So this review is based on static inspection rather than a successful build.


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

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.

2 participants