Skip to content

feat(l1): implement debug_getBadBlocks with bad-block tracking#6949

Closed
edg-l wants to merge 1 commit into
glamsterdam-devnet-6from
feat/debug-get-bad-blocks-glamsterdam-devnet-6
Closed

feat(l1): implement debug_getBadBlocks with bad-block tracking#6949
edg-l wants to merge 1 commit into
glamsterdam-devnet-6from
feat/debug-get-bad-blocks-glamsterdam-devnet-6

Conversation

@edg-l

@edg-l edg-l commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Cherry-pick of #6948 onto glamsterdam-devnet-6.

Implements debug_getBadBlocks with real bad-block tracking: blocks rejected during engine_newPayload are persisted in a new BAD_BLOCKS storage table (deduped on (number, hash), sorted descending, capped at 16) and returned as [{ hash, block, rlp }] matching geth's shape.

Cherry-picked cleanly (auto-merge on rpc.rs/store.rs); builds and the test_bad_blocks storage test passes on this branch.

See #6948 for full details.

@edg-l edg-l requested a review from a team as a code owner July 2, 2026 11:39
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

⚠️ Known Issues — intentionally skipped tests

Source: docs/known_issues.md

Stateless blockchain EF-tests (zkevm bundle) skipped under Amsterdam v6.1.0

make -C tooling/ef_tests/blockchain test-stateless runs against the
tests-zkevm@v0.4.1 fixture bundle — currently the only published zkevm test
release. Those fixtures were filled against an older glamsterdam devnet and
re-execute every case under the for_amsterdam fork, so they lag the
glamsterdam-devnet v6.1.0 gas accounting this client now implements
(EIP-8037 / EIP-8038 / EIP-2780 / EIP-7976 / EIP-7981 …). Re-executing them
yields ~2790/2864 stale-gas failures ("Transaction execution unexpectedly
failed"), spread pervasively across every fork and through the EIP-8025 proof
suite, so there is no clean passing subset to keep.

Until a v6.1.0-aligned zkevm bundle is published, the entire bundle is skipped
for the stateless run via the fork_Amsterdam entry in the stateless-only
EXTRA_SKIPS (tooling/ef_tests/blockchain/tests/all.rs) — every test in this
Amsterdam-only bundle carries the [fork_Amsterdam-…] parametrization in its
test key. The skip is #[cfg(feature = "stateless")], so it does not touch
the non-stateless test-levm run. Coverage of these EIPs is retained by
test-levm, the engine EF-tests, and the state EF-tests, all of which execute
against the live v6.1.0 fixtures and pass.

Re-enable by removing the "fork_Amsterdam" skip once .fixtures_url_zkevm
points at a zkevm bundle filled for glamsterdam-devnet v6.1.0.

@github-actions github-actions Bot added the L1 Ethereum client label Jul 2, 2026
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

🤖 Kimi Code Review

Review Summary

The PR implements debug_getBadBlocks RPC endpoint to track and serve recently invalid blocks, matching geth's behavior. The implementation is generally correct and follows the codebase patterns, but there is a concurrency issue in the storage layer that should be addressed.


1. Race Condition in Bad Block Storage (Critical)

File: crates/storage/store.rs
Lines: 1326–1347

The add_bad_block implementation performs a non-atomic read-modify-write:

let mut bad_blocks = self.get_bad_blocks().await?;  // Read
// ... modify list ...
self.write_async(BAD_BLOCKS, ...).await  // Write

If two concurrent newPayload calls fail validation simultaneously, one update may overwrite the other, causing a bad block to be lost.

Recommendation: Add a mutex to serialize updates:

// In Store struct definition:
bad_blocks_lock: tokio::sync::Mutex<()>,

// In add_bad_block:
let _guard = self.bad_blocks_lock.lock().await;
let mut bad_blocks = self.get_bad_blocks().await?;
// ... rest of logic

Alternatively, document this as a known limitation for the debug endpoint, though explicit synchronization is preferred for correctness.


2. Unnecessary Clone on Success Path (Minor)

File: crates/networking/rpc/engine/payload.rs
Line: 1245

The block is cloned unconditionally before execution to preserve it for potential error handling. While the comment notes this is negligible compared to execution, you can avoid the clone in the success case by restructuring:

// Instead of cloning upfront:
match add_block(context, block.clone(), ...).await {
    Err(e) => { /* use block */ }
    Ok(_) => { /* wasted clone */ }
}

// Consider refactoring add_block to take &Block if possible,
// or accept the clone given it's debug-only and rare

Given add_block consumes the block, the current approach is acceptable but worth noting as a minor optimization opportunity.


3. Error Handling Granularity

File: crates/networking/rpc/debug/bad_blocks.rs
Lines: 28–32

If RpcBlock::build fails for any single bad block, the entire RPC request fails. Given this is a debug endpoint and the data is best-effort, consider logging and skipping invalid entries rather than failing the whole request:

let rpc_block = match RpcBlock::build(block.header, block.body, hash, true) {
    Ok(b) => b,
    Err(e) => {
        tracing::warn!(%hash, "Failed to build RPC block for bad block: {}", e);
        continue;
    }
};

4. Storage Key Constant Visibility

File: crates/storage/store.rs
Line: 121

BAD_BLOCKS_KEY is defined in store.rs but referenced in the table documentation in tables.rs. Consider exposing it in tables.rs alongside BAD_BLOCKS for consistency, or document that the key is defined in the store module.


5. Test Coverage (Good)

The test in test/tests/storage/store_tests.rs correctly validates:

  • Empty store behavior
  • Descending sort order
  • Duplicate detection
  • Bounded eviction (LRU-like behavior)

Verdict

Approve with changes requested - Please address the race condition in add_bad_block (Item 1) before merging. The other items are optional improvements.

The EVM integration points look correct: bad blocks are only recorded for validation failures (InvalidBlock, Evm), not for sync-related errors (ParentNotFound), which is the correct semantic for this debug endpoint.


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

@edg-l

edg-l commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Closing — pushing the commit directly to glamsterdam-devnet-6 instead.

@edg-l edg-l closed this Jul 2, 2026
@github-project-automation github-project-automation Bot moved this to Done in ethrex_l1 Jul 2, 2026
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

🤖 Codex Code Review

  1. Store::add_bad_block is a non-atomic read/modify/write on a single key, so concurrent invalid engine_newPayload* calls can lose entries. The method reads the current vector, mutates it locally, then writes it back at crates/storage/store.rs. Those writes are triggered from each invalid payload path at crates/networking/rpc/engine/payload.rs and crates/networking/rpc/engine/payload.rs. A simple interleaving of two bad payloads can produce [] -> [A] and [] -> [B], with the second write dropping A. This should be serialized explicitly, or moved into the already-serialized block worker path.

  2. debug_getBadBlocks accepts arbitrary parameters because parse ignores params entirely at crates/networking/rpc/debug/bad_blocks.rs. Other zero-arg debug methods reject extra params, so this is inconsistent RPC behavior and can hide client-side bugs. It should reject non-empty params the same way debug_chainConfig does.

Outside of those points, the change looks straightforward. I did not find EVM/gas-accounting or consensus-rule regressions in the touched paths.


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

@edg-l edg-l deleted the feat/debug-get-bad-blocks-glamsterdam-devnet-6 branch July 2, 2026 11:41
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

Lines of code report

Total lines added: 67
Total lines removed: 0
Total lines changed: 67

Detailed view
+--------------------------------------------------+-------+------+
| File                                             | Lines | Diff |
+--------------------------------------------------+-------+------+
| ethrex/crates/networking/rpc/debug/bad_blocks.rs | 34    | +34  |
+--------------------------------------------------+-------+------+
| ethrex/crates/networking/rpc/debug/mod.rs        | 4     | +1   |
+--------------------------------------------------+-------+------+
| ethrex/crates/networking/rpc/engine/payload.rs   | 1378  | +3   |
+--------------------------------------------------+-------+------+
| ethrex/crates/networking/rpc/rpc.rs              | 1386  | +2   |
+--------------------------------------------------+-------+------+
| ethrex/crates/storage/api/tables.rs              | 25    | +1   |
+--------------------------------------------------+-------+------+
| ethrex/crates/storage/store.rs                   | 3322  | +26  |
+--------------------------------------------------+-------+------+

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

🤖 Claude Code Review

Review: feat(l1): implement debug_getBadBlocks with bad-block tracking (#6949)

This is a cherry-pick of #6948 onto glamsterdam-devnet-6. Overall the implementation is clean and matches geth's debug_getBadBlocks shape reasonably well. Two correctness/perf concerns worth addressing, plus minor nits.

1. Read-modify-write race in Store::add_bad_block (correctness)

crates/storage/store.rs:1327-1346

pub async fn add_bad_block(&self, block: Block) -> Result<(), StoreError> {
    let mut bad_blocks = self.get_bad_blocks().await?;   // separate read txn
    ...
    self.write_async(BAD_BLOCKS, BAD_BLOCKS_KEY.to_vec(), bad_blocks.encode_to_vec()).await  // separate write txn
}

read_async/write_async each open independent transactions (store.rs:1470-1502) — there's no lock spanning the read-modify-write cycle. If two invalid payloads are handled concurrently (plausible: try_execute_payload dispatches block execution to a single worker via block_worker_channel, but the async continuations that call add_bad_block after the worker replies are not serialized against each other), both calls can read the same base list and the second write silently clobbers the first — losing a bad-block entry despite the dedup/cap logic looking correct in isolation.

Contrast with the existing INVALID_CHAINS table (store.rs:1306), which stores one value per key (bad_block_hash -> value) and has no such hazard — this PR introduces the first "single-key aggregate list" pattern in this file, so the race isn't pre-existing.

Impact is limited to a debug-only RPC (no consensus impact), but since the docstring explicitly promises capping/dedup guarantees, consider either serializing add_bad_block behind a Mutex, or doing the read-modify-write inside a single write transaction if the storage backend supports read-your-writes within one txn.

2. Unconditional block clone on the payload hot path (performance)

crates/networking/rpc/engine/payload.rs:1242-1245

let bad_block_candidate = block.clone();
match add_block(context, block, bal, make_witness).await {

This clones the entire block (header + body, including all transactions) on every engine_newPayload call, even though bad_block_candidate is only consumed in 2 of the 6 match arms (InvalidBlock, EvmError) — the common case is Ok(witness), where the clone is pure waste. For mainnet-sized blocks with hundreds of transactions, this is a non-trivial allocation/copy added to the hot path of every payload, not just invalid ones. The comment calls it "negligible next to block execution," but block execution is dispatched to a separate worker thread/channel, so this clone runs synchronously before that work even starts.

Since add_block moves block into a channel (ctx.block_worker_channel.send(...), payload.rs:1187-1188), avoiding the clone entirely requires either passing the block back through the channel on failure, or restructuring so the clone only happens after a failure is known. Given this is a cherry-pick, this may be acceptable to defer, but it's worth flagging as a follow-up since it affects every valid payload too.

Minor notes

  • Eviction is by block number, not recency of insertion (store.rs:1338-1339: sort_by(|a,b| b.header.number.cmp(&a.header.number))). A newly-encountered bad block with a low number (e.g., a replayed/old invalid block) gets evicted immediately if the cache is already full of higher-numbered entries, even though it was just observed. This differs from geth's LRU-by-arrival semantics. Likely intentional given the docstring, but worth confirming it's the desired behavior rather than an oversight.
  • Permanent decode failure risk: get_bad_blocks (store.rs:1350-1355) propagates Vec::<Block>::decode errors via ?. Since add_bad_block also calls get_bad_blocks internally, a single corrupted/incompatible entry (e.g. after a schema change) would make both add_bad_block and debug_getBadBlocks fail permanently until the table is manually cleared. Consider treating a decode error as "start fresh" (log + Ok(Vec::new())) rather than hard-failing, since this is best-effort debug tooling.
  • Test coverage (test_bad_blocks in store_tests.rs) is solid for the storage layer (ordering, dedup, cap/eviction) but there's no test exercising the RPC handler in bad_blocks.rs (e.g., RpcBlock::build output shape, RLP hex encoding). Not blocking for a cherry-pick, but worth adding alongside feat(l1): implement debug_getBadBlocks with bad-block tracking #6948 if not already covered there.

Everything else (table registration, RLP round-trip via existing Vec<T> encode/decode, RPC wiring in rpc.rs) looks correct and idiomatic.


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

@greptile-apps

greptile-apps Bot commented Jul 2, 2026

Copy link
Copy Markdown

Greptile Summary

This PR implements debug_getBadBlocks with real bad-block tracking: blocks rejected during engine_newPayload with InvalidBlock or EvmError are stored in a new BAD_BLOCKS column family (capped at 16, sorted by descending block number) and served as [{ hash, block, rlp }] matching geth's shape.

  • Storage layer (store.rs, tables.rs): new BAD_BLOCKS column family with add_bad_block/get_bad_blocks using a single-key RLP-encoded list; deduplication on (number, hash) with truncation to 16 entries.
  • Engine layer (engine/payload.rs): blocks are cloned before add_block so invalid ones can be persisted; only the two truly-INVALID branches (InvalidBlock, EvmError) call add_bad_block — SYNCING and internal-error paths are correctly excluded.
  • RPC layer (debug/bad_blocks.rs, rpc.rs): new GetBadBlocksRequest handler retrieves stored blocks, re-encodes to hex RLP, and builds RpcBlock responses; the endpoint is registered under debug_getBadBlocks.

Confidence Score: 3/5

The block-tracking and routing logic is correct, but two defects in the storage and handler layers should be addressed before merging.

The non-atomic read-modify-write in add_bad_block can silently drop entries when two invalid newPayload calls are processed concurrently. Separately, a single RpcBlock::build failure inside the debug_getBadBlocks handler will abort the entire response, making all stored bad blocks invisible. Both are real defects in newly introduced code paths.

crates/storage/store.rs (concurrency in add_bad_block) and crates/networking/rpc/debug/bad_blocks.rs (error propagation in the response loop) need attention before this lands.

Important Files Changed

Filename Overview
crates/storage/store.rs Adds add_bad_block and get_bad_blocks using a single-key RLP-encoded list; the read-modify-write in add_bad_block is not atomic and can silently drop entries under concurrent newPayload calls.
crates/networking/rpc/debug/bad_blocks.rs New handler for debug_getBadBlocks; a single RpcBlock::build failure in the loop aborts the entire response, hiding all other stored bad blocks from the caller.
crates/networking/rpc/engine/payload.rs Adds bad_block_candidate clone before add_block and calls add_bad_block in the two INVALID error branches (InvalidBlock, EvmError); SYNCING and internal-error branches are correctly excluded.
crates/storage/api/tables.rs Adds BAD_BLOCKS column-family constant and bumps the TABLES array size from 20 to 21; straightforward and correct.
crates/networking/rpc/rpc.rs Routes debug_getBadBlocks to GetBadBlocksRequest::call; no issues.
test/tests/storage/store_tests.rs Adds test_bad_blocks covering empty store, out-of-order insertion, deduplication, and the 16-entry eviction bound; good coverage of the happy path.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant CL as Consensus Layer
    participant RPC as engine_newPayload
    participant Chain as add_block
    participant Store as BAD_BLOCKS table

    CL->>RPC: engine_newPayload(block)
    RPC->>RPC: clone block as bad_block_candidate
    RPC->>Chain: add_block(block)
    alt InvalidBlock or EvmError
        Chain-->>RPC: Err INVALID
        RPC->>Store: add_bad_block(bad_block_candidate)
        Note over Store: read, dedup, push, sort desc, truncate 16, write
        RPC-->>CL: PayloadStatus INVALID
    else ParentNotFound or ParentStateNotFound
        Chain-->>RPC: Err SYNCING
        RPC-->>CL: PayloadStatus SYNCING
    else success
        Chain-->>RPC: Ok witness
        RPC-->>CL: PayloadStatus VALID
    end

    CL->>RPC: debug_getBadBlocks
    RPC->>Store: get_bad_blocks
    Store-->>RPC: Vec of Block
    loop each Block
        RPC->>RPC: hash + encode_to_vec + RpcBlock build
    end
    RPC-->>CL: array of hash block rlp
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant CL as Consensus Layer
    participant RPC as engine_newPayload
    participant Chain as add_block
    participant Store as BAD_BLOCKS table

    CL->>RPC: engine_newPayload(block)
    RPC->>RPC: clone block as bad_block_candidate
    RPC->>Chain: add_block(block)
    alt InvalidBlock or EvmError
        Chain-->>RPC: Err INVALID
        RPC->>Store: add_bad_block(bad_block_candidate)
        Note over Store: read, dedup, push, sort desc, truncate 16, write
        RPC-->>CL: PayloadStatus INVALID
    else ParentNotFound or ParentStateNotFound
        Chain-->>RPC: Err SYNCING
        RPC-->>CL: PayloadStatus SYNCING
    else success
        Chain-->>RPC: Ok witness
        RPC-->>CL: PayloadStatus VALID
    end

    CL->>RPC: debug_getBadBlocks
    RPC->>Store: get_bad_blocks
    Store-->>RPC: Vec of Block
    loop each Block
        RPC->>RPC: hash + encode_to_vec + RpcBlock build
    end
    RPC-->>CL: array of hash block rlp
Loading
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
crates/storage/store.rs:1327-1346
**Non-atomic read-modify-write race condition**

`add_bad_block` reads the current list, mutates it in memory, then writes it back — all without any mutual exclusion. If two `engine_newPayload` calls arrive concurrently for different invalid blocks, both tasks can read the same initial list, produce diverging in-memory mutations, and the last `write_async` wins, silently dropping one entry. The scenario is uncommon in practice (CLs usually serialise newPayload) but is a real data-loss path for any concurrent caller. A tokio `Mutex` guarding the read-modify-write cycle (or wrapping the entire operation in a storage-level transaction if the backend supports it) would close the window.

### Issue 2 of 3
crates/networking/rpc/debug/bad_blocks.rs:29-37
**Single failing bad block silences the entire endpoint**

`RpcBlock::build(…)?` propagates any conversion error with `?`, aborting the loop immediately. If even one stored bad block produces an `RpcBlock` build failure (e.g., because of a field that `RpcBlock` cannot represent, or a future schema change), the whole `debug_getBadBlocks` response becomes an internal error and all other stored bad blocks are invisible to the caller. Consider skipping the failing entry with a `warn!` log (matching geth's best-effort approach) so a single corrupt entry cannot break the endpoint for all callers.

### Issue 3 of 3
crates/storage/store.rs:1331-1333
The deduplication check calls `b.hash()` (a keccak256 over the full block header) on every element of the stored list on each `add_bad_block` invocation. For a list capped at 16 entries this is acceptable, but the work is avoidable: the caller already has `block_hash` in scope — storing it alongside the block (e.g., as a `(Block, H256)` tuple) would let you dedup with a simple equality comparison.

```suggestion
        // Consider storing the hash alongside each block to avoid re-hashing here.
        if bad_blocks
            .iter()
            .any(|b| b.header.number == block_number && b.hash() == block_hash)
```

Reviews (1): Last reviewed commit: "feat(l1): implement debug_getBadBlocks R..." | Re-trigger Greptile

Comment thread crates/storage/store.rs
Comment on lines +1327 to +1346
pub async fn add_bad_block(&self, block: Block) -> Result<(), StoreError> {
let mut bad_blocks = self.get_bad_blocks().await?;
let block_number = block.header.number;
let block_hash = block.hash();
if bad_blocks
.iter()
.any(|b| b.header.number == block_number && b.hash() == block_hash)
{
return Ok(());
}
bad_blocks.push(block);
bad_blocks.sort_by(|a, b| b.header.number.cmp(&a.header.number));
bad_blocks.truncate(MAX_BAD_BLOCKS);
self.write_async(
BAD_BLOCKS,
BAD_BLOCKS_KEY.to_vec(),
bad_blocks.encode_to_vec(),
)
.await
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Non-atomic read-modify-write race condition

add_bad_block reads the current list, mutates it in memory, then writes it back — all without any mutual exclusion. If two engine_newPayload calls arrive concurrently for different invalid blocks, both tasks can read the same initial list, produce diverging in-memory mutations, and the last write_async wins, silently dropping one entry. The scenario is uncommon in practice (CLs usually serialise newPayload) but is a real data-loss path for any concurrent caller. A tokio Mutex guarding the read-modify-write cycle (or wrapping the entire operation in a storage-level transaction if the backend supports it) would close the window.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/storage/store.rs
Line: 1327-1346

Comment:
**Non-atomic read-modify-write race condition**

`add_bad_block` reads the current list, mutates it in memory, then writes it back — all without any mutual exclusion. If two `engine_newPayload` calls arrive concurrently for different invalid blocks, both tasks can read the same initial list, produce diverging in-memory mutations, and the last `write_async` wins, silently dropping one entry. The scenario is uncommon in practice (CLs usually serialise newPayload) but is a real data-loss path for any concurrent caller. A tokio `Mutex` guarding the read-modify-write cycle (or wrapping the entire operation in a storage-level transaction if the backend supports it) would close the window.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +29 to +37
for block in bad_blocks {
let hash = block.hash();
let rlp = format!("0x{}", hex::encode(block.encode_to_vec()));
let rpc_block = RpcBlock::build(block.header, block.body, hash, true)?;
results.push(BadBlock {
hash,
block: rpc_block,
rlp,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Single failing bad block silences the entire endpoint

RpcBlock::build(…)? propagates any conversion error with ?, aborting the loop immediately. If even one stored bad block produces an RpcBlock build failure (e.g., because of a field that RpcBlock cannot represent, or a future schema change), the whole debug_getBadBlocks response becomes an internal error and all other stored bad blocks are invisible to the caller. Consider skipping the failing entry with a warn! log (matching geth's best-effort approach) so a single corrupt entry cannot break the endpoint for all callers.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/networking/rpc/debug/bad_blocks.rs
Line: 29-37

Comment:
**Single failing bad block silences the entire endpoint**

`RpcBlock::build(…)?` propagates any conversion error with `?`, aborting the loop immediately. If even one stored bad block produces an `RpcBlock` build failure (e.g., because of a field that `RpcBlock` cannot represent, or a future schema change), the whole `debug_getBadBlocks` response becomes an internal error and all other stored bad blocks are invisible to the caller. Consider skipping the failing entry with a `warn!` log (matching geth's best-effort approach) so a single corrupt entry cannot break the endpoint for all callers.

How can I resolve this? If you propose a fix, please make it concise.

Comment thread crates/storage/store.rs
Comment on lines +1331 to +1333
if bad_blocks
.iter()
.any(|b| b.header.number == block_number && b.hash() == block_hash)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The deduplication check calls b.hash() (a keccak256 over the full block header) on every element of the stored list on each add_bad_block invocation. For a list capped at 16 entries this is acceptable, but the work is avoidable: the caller already has block_hash in scope — storing it alongside the block (e.g., as a (Block, H256) tuple) would let you dedup with a simple equality comparison.

Suggested change
if bad_blocks
.iter()
.any(|b| b.header.number == block_number && b.hash() == block_hash)
// Consider storing the hash alongside each block to avoid re-hashing here.
if bad_blocks
.iter()
.any(|b| b.header.number == block_number && b.hash() == block_hash)
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/storage/store.rs
Line: 1331-1333

Comment:
The deduplication check calls `b.hash()` (a keccak256 over the full block header) on every element of the stored list on each `add_bad_block` invocation. For a list capped at 16 entries this is acceptable, but the work is avoidable: the caller already has `block_hash` in scope — storing it alongside the block (e.g., as a `(Block, H256)` tuple) would let you dedup with a simple equality comparison.

```suggestion
        // Consider storing the hash alongside each block to avoid re-hashing here.
        if bad_blocks
            .iter()
            .any(|b| b.header.number == block_number && b.hash() == block_hash)
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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.

1 participant