Skip to content

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

Merged
edg-l merged 1 commit into
mainfrom
feat/l1/block-hash-in-block-metric-log
Jun 4, 2026
Merged

edg-l merged 1 commit into
mainfrom
feat/l1/block-hash-in-block-metric-log

Conversation

@edg-l

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

Copy link
Copy Markdown
Contributor

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

github-actions Bot commented May 27, 2026 •

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 changed — crates/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 signature — block_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

greptile-apps Bot commented May 27, 2026

Copy link
Copy Markdown

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

github-actions Bot commented May 27, 2026 •

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 | 2639  | +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

@github-project-automation github-project-automation Bot moved this to In Review in ethrex_l1 Jun 2, 2026
@edg-l
edg-l force-pushed the feat/l1/block-hash-in-block-metric-log branch from 3bb6e79 to 7df4937 Compare June 4, 2026 09:26
@edg-l
edg-l enabled auto-merge June 4, 2026 09:32
@edg-l
edg-l added this pull request to the merge queue Jun 4, 2026
Merged via the queue into main with commit 6953f01 Jun 4, 2026
53 checks passed
@edg-l
edg-l deleted the feat/l1/block-hash-in-block-metric-log branch June 4, 2026 10:23
@github-project-automation github-project-automation Bot moved this from In Review to Done in ethrex_l1 Jun 4, 2026
skylenet pushed a commit to ethpandaops/benchmarkoor that referenced this pull request Jun 5, 2026
## What

Implements the ethrex block-log parser in `pkg/blocklog` (replacing the
stub), so ethrex runs produce per-block metrics like the other clients.

It parses ethrex's per-block execution metric header (emitted on the
`engine_newPayload` path) into the shared `{ "block": { "hash": ... },
... }` shape:

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

Captured fields: `block.number`, `block.hash`, `block.gas_used_mgas`,
`block.gas_used_pct`, `block.tx_count`, `throughput.ggas_per_sec`,
`timing.total_ms`. ANSI is stripped; the hash is optional in the regex
so the line still parses on builds that don't log it (it just won't
match a test without `block.hash`).

## Dependency

Matching a log to a test requires `block.hash`, and ethrex does not yet
include the hash on this line. That is addressed upstream in
**lambdaclass/ethrex#6736**, which adds the hash exactly where this
parser expects it (`BLOCK <num> 0x<hash> | ...`). Once that lands and
reaches the ethrex image used here, ethrex runs will start attaching
per-block metrics.

Marking this draft until the ethrex change is merged and released.

## Notes

- ethrex follows this header with separate `|-
validate/exec/merkle/store` lines; since the collector matches one
payload per line by hash, only the header is captured. Folding in the
phase breakdown would need ethrex to emit a single structured line
(follow-up).
- Test coverage mirrors `reth_test.go`: hash / no-hash / zero-gas / ANSI
/ non-matching lines. `gofmt`, `go vet`, and `go test ./pkg/blocklog/`
pass.
edg-l added a commit that referenced this pull request Jun 5, 2026
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](https://github.com/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.)

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`).

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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

L1 Ethereum client

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants