Skip to content

feat(l1): implement debug_getModifiedAccountsByNumber/Hash#6700

Open
azteca1998 wants to merge 6 commits into
mainfrom
feat/debug-get-modified-accounts
Open

feat(l1): implement debug_getModifiedAccountsByNumber/Hash#6700
azteca1998 wants to merge 6 commits into
mainfrom
feat/debug-get-modified-accounts

Conversation

@azteca1998
Copy link
Copy Markdown
Contributor

@azteca1998 azteca1998 commented May 21, 2026

Summary

  • Adds debug_getModifiedAccountsByNumber and debug_getModifiedAccountsByHash
  • Returns addresses modified between two blocks by diffing state tries
  • Both endpoints share a diff_state_roots helper that iterates both tries
  • Note: O(n) brute-force comparison; a trie-diff algorithm would be more efficient at mainnet scale

Closes part of #6572

Test plan

  • Returns modified addresses between two consecutive blocks
  • Returns empty array when start == end
  • start > end returns error
  • Both by-number and by-hash variants work

Closes #6651

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

github-actions Bot commented May 21, 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.

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
Copy link
Copy Markdown

github-actions Bot commented May 21, 2026

Lines of code report

Total lines added: 202
Total lines removed: 0
Total lines changed: 202

Detailed view
+-------------------------------------------------------------+-------+------+
| File                                                        | Lines | Diff |
+-------------------------------------------------------------+-------+------+
| ethrex/crates/networking/rpc/debug/get_modified_accounts.rs | 192   | +192 |
+-------------------------------------------------------------+-------+------+
| ethrex/crates/networking/rpc/debug/mod.rs                   | 5     | +1   |
+-------------------------------------------------------------+-------+------+
| ethrex/crates/networking/rpc/rpc.rs                         | 1372  | +9   |
+-------------------------------------------------------------+-------+------+

@azteca1998 azteca1998 marked this pull request as ready for review May 26, 2026 14:34
@azteca1998 azteca1998 requested a review from a team as a code owner May 26, 2026 14:34
@ethrex-project-sync ethrex-project-sync Bot moved this to In Review in ethrex_l1 May 26, 2026
@github-actions
Copy link
Copy Markdown

🤖 Kimi Code Review

This is a well-structured implementation of the debug_getModifiedAccountsBy* endpoints. The lockstep trie traversal logic is correct and memory-efficient.

Minor issues:

  1. Variable naming (crates/networking/rpc/debug/get_modified_accounts.rs:186):
    In the final match arm where hashes are equal, use e_hash instead of _e_hash. Even though the value equals s_hash, it is semantically significant (indicating the key exists in both tries), and removing the underscore prefix clarifies that the binding is intentionally used via the equality check in the guard arms above.

  2. Documentation consistency (crates/networking/rpc/debug/get_modified_accounts.rs:20):
    The doc comment references iter_accounts_from, but the code calls storage.iter_accounts(). Verify the correct method name or update the comment to match the actual API.

Security & Correctness:

  • The spawn_blocking usage (line 137) is appropriate for long-lived synchronous trie I/O.
  • The ordering check start_number > end_number (line 62) prevents negative ranges, though for ByHash you correctly compare the resolved header numbers rather than the hash bytes themselves.
  • The divergence from geth (returning hashed addresses) is clearly documented, which is important for API consumers.

Testing:

The integration tests cover the critical paths: happy path, empty range, ordering validation, and unknown blocks. The helper module in test/tests/rpc/helpers.rs is a good refactor to reduce boilerplate.

Verdict:
Approve with minor naming cleanup. The core diffing algorithm is sound and the O(1) memory claim holds (aside from the output vector).


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

@github-actions
Copy link
Copy Markdown

🤖 Claude Code Review

Now I have enough context to write a thorough review.


PR Review: feat(l1): implement debug_getModifiedAccountsByNumber/Hash

Overview

This PR implements two debug_ namespace RPC endpoints that diff state tries between two blocks and return the set of accounts whose state changed. The implementation is clean and well-structured, with good test coverage for the happy path and major error cases. A few issues deserve attention before merging.


API Compatibility — Significant Divergence from Geth

The PR returns Vec<H256> of keccak256-hashed addresses rather than Vec<Address>. This divergence is documented in the module comment, but it's a breaking difference from the Ethereum JSON-RPC spec and geth's implementation. Any tooling, explorer, or client that calls this endpoint expecting plain addresses will receive opaque hashes instead and break silently.

Recommendation: Either surface this more prominently (e.g., in a CHANGELOG entry, docs update, or API-compatibility matrix), or track an issue for adding a preimage store so the API can eventually match geth. Right now a caller has no way to discover this divergence without reading the source.


Correctness Issues

1. Wrong error variant for user-supplied invalid blocks

In both handlers, a block that doesn't exist in the DB yields RpcErr::Internal:

// get_modified_accounts.rs ~line 64
.ok_or(RpcErr::Internal("Start block not found".to_string()))?;

RpcErr::Internal maps to HTTP 500 / JSON-RPC -32603. A missing block supplied by the caller is a user error, not a server error. Geth returns -32000 for this case with a descriptive message. RpcErr::BadParams (or a dedicated BlockNotFound variant if one is added) would be more appropriate and would give callers a correct signal.

2. No maximum block range guard

Geth limits debug_getModifiedAccountsByNumber to a 1000-block window by default. This implementation performs a full state-trie traversal across an unbounded range. On a chain with millions of accounts, a request spanning thousands of blocks could hold large amounts of DB read locks and run for minutes. Even for a debug endpoint, a missing upper-bound check is a DoS vector.

Suggested fix — add a constant and early check in GetModifiedAccountsByNumberRequest::handle:

const MAX_BLOCK_RANGE: u64 = 1000;

if end_number - start_number > MAX_BLOCK_RANGE {
    return Err(RpcErr::BadParams(format!(
        "block range must not exceed {MAX_BLOCK_RANGE} blocks"
    )));
}

3. Silent iterator truncation in diff_state_roots

iter_accounts uses map_while with AccountState::decode(...).ok()?, which silently stops the iterator on any decode error:

// store.rs:2501-2503 (existing code, but consumed here)
Ok(iter.content().map_while(|(path, value)| {
    Some((H256::from_slice(&path), AccountState::decode(&value).ok()?))
}))

If this triggers mid-iteration (e.g., on a corrupted node), diff_state_roots's merge-join loop sees the exhausted side as "no more accounts" and silently reports all remaining accounts on the other side as added/deleted. This is pre-existing behavior in store.rs, not introduced here — but since diff_state_roots is one of the first callers that interleaves two iterators simultaneously, the incorrect diff it produces could be particularly misleading. Worth flagging as a known limitation or adding an error-propagating variant of the iterator.


Logic / Algorithm

4. O(1) memory comment is misleading

The doc comment says:

Both handlers traverse the state tries in lockstep … so memory is O(1) regardless of state size.

The iterator step is O(1), but the output Vec<H256> modified is O(m) where m is the number of modified accounts — O(n) in the worst case. A more accurate description: "iterator overhead is O(1); output is proportional to the number of modified accounts."

5. Ordering error message wording

"start block (X) must be older than end block (Y)" — "older than" is ambiguous (does older mean lower number? Or earlier in canonical time?). Prefer the more explicit "start block number (X) must be less than or equal to end block number (Y)" or mirror geth's exact message for tooling compatibility.


Test Coverage Gaps

6. No test for debug_getModifiedAccountsByHash with an unknown hash

get_modified_accounts_unknown_block_errors only covers the by-number variant. An equivalent test for by-hash should be added.

7. Recipient not verified in diff

get_modified_accounts_by_number_includes_sender checks only that the sender is present. The recipient (0xAA) and potentially the fee recipient's balance change too. A broader assertion would give stronger confidence in the diff completeness.

8. Test plan items not all checkable automatically

The PR description lists four test-plan items, but the start > end returns error cases are checked by get_modified_accounts_start_after_end_errors (good). The start == end returns empty case is also covered. However, the test_plan is not linked to individual test names, making it hard to audit during review.


Minor / Style

  • helpers.rs opens with #![allow(dead_code)] at the module level. This is fine for a test helper module where different test files use different subsets, but it masks any helpers that become genuinely dead. A lighter approach is #[allow(dead_code)] per item if feasible.

  • The diff_state_roots_async wrapper is a clean pattern for moving blocking I/O off the async thread. No issues.

  • AccountState derives PartialEq and Copy, so the comparison s_state != e_state in the merge-join is a struct field-by-field comparison (nonce, balance, storage_root, code_hash). This is correct and cheap.


Summary

Severity Item
Medium Wrong error variant (Internal vs. BadParams) for invalid user-supplied blocks
Medium No maximum block-range limit — potential DoS for expensive state traversals
Medium API divergence (hashed vs. plain addresses) needs clearer external documentation
Low O(1) memory claim in doc comment is inaccurate
Low Misleading ordering error message
Low Missing test: by-hash variant with unknown hash
Low Missing test: recipient in diff

The core merge-join algorithm is correct and the spawn_blocking usage is appropriate. The main blockers before merging are the missing range limit and the wrong error variant for user-supplied invalid blocks.


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

@github-actions
Copy link
Copy Markdown

🤖 Codex Code Review

Findings:

  1. pending tags are implicitly accepted here, but the handler can only read canonical headers, so debug_getModifiedAccountsByNumber("pending", ...) is not actually supported and will either fail with ...header not found or diff the wrong state. The new code parses generic BlockIdentifiers and resolves pending to a block number, then immediately calls canonical-only header lookup. This should either reject pending up front or implement pending-state resolution explicitly. Refs: get_modified_accounts.rs, get_modified_accounts.rs, block_identifier.rs, store.rs, store.rs

  2. The new RPC can silently return an incomplete diff if trie iteration hits a malformed account entry. diff_state_roots() trusts iter_accounts(), but iter_accounts_from() uses map_while(... AccountState::decode(&value).ok()? ...), which stops iteration on the first decode failure instead of surfacing an error. For a state-diff API, especially in blockchain debugging, partial success is the wrong failure mode. Refs: get_modified_accounts.rs, store.rs

The lockstep trie walk itself looks reasonable and the endpoint is memory-efficient. I couldn’t run the targeted tests locally because cargo attempted a rustup/toolchain write under a read-only filesystem and failed before compilation.


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

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 26, 2026

Greptile Summary

Implements debug_getModifiedAccountsByNumber and debug_getModifiedAccountsByHash by performing a merge-join diff over two state trie iterators, emitting hashed keys that differ between the two roots. The sync trie traversal is correctly offloaded via tokio::task::spawn_blocking.

  • The diff algorithm is a clean O(N+M) merge join that streams both tries in hashed-key order, correctly handling added, removed, and modified accounts.
  • The response type is Vec<H256> (keccak256-hashed addresses) rather than Vec<Address> as specified by geth and the Ethereum debug namespace — this is documented as an intentional divergence but breaks compatibility with any standard Ethereum tooling.
  • Test coverage is solid: happy path, empty range, reversed order, unknown block, and cross-variant consistency are all exercised.

Confidence Score: 3/5

The core merge-join logic and async dispatch are correct, but the response serializes keccak256 hashes instead of addresses, making the endpoints incompatible with any standard Ethereum tooling that calls these debug methods.

The return type divergence from the Ethereum spec is a present, user-visible defect: callers receive 32-byte hashed keys where they expect 20-byte addresses, and they have no way to reverse the hash. The diff algorithm itself, the spawn_blocking offload, and the test suite are all well-implemented, but the API contract mismatch would affect anyone integrating this with standard tools.

crates/networking/rpc/debug/get_modified_accounts.rs — specifically the response type and the block-not-found error classification

Important Files Changed

Filename Overview
crates/networking/rpc/debug/get_modified_accounts.rs New file implementing debug_getModifiedAccountsByNumber/Hash; returns Vec (hashed keys) instead of the spec-defined Vec, breaking geth API compatibility; merge-join diff algorithm is otherwise correct
crates/networking/rpc/debug/mod.rs Adds module declaration for get_modified_accounts; no issues
crates/networking/rpc/rpc.rs Wires debug_getModifiedAccountsByNumber and debug_getModifiedAccountsByHash into the RPC dispatcher; no issues
test/tests/rpc/debug_get_modified_accounts_tests.rs Integration tests covering happy path, empty range, start-after-end error, and unknown block error; tests are well-structured but test the hashed-key response shape rather than actual addresses
test/tests/rpc/helpers.rs New shared test helpers for RPC integration tests; setup_single_transfer_block, rpc_call, and rpc_call_expect_err are clean and reusable
test/tests/rpc/mod.rs Registers the two new test modules; no issues

Sequence Diagram

sequenceDiagram
    participant C as Client
    participant R as RPC Dispatcher
    participant H as GetModifiedAccountsHandler
    participant S as Store
    participant T as spawn_blocking

    C->>R: debug_getModifiedAccountsByNumber(start, end)
    R->>H: parse(params)
    H->>S: resolve_block_number(start_block)
    H->>S: resolve_block_number(end_block)
    H->>S: get_block_header(start_number) → start_root
    H->>S: get_block_header(end_number) → end_root
    H->>T: diff_state_roots(storage, start_root, end_root)
    T->>S: iter_accounts(start_root) → start_iter
    T->>S: iter_accounts(end_root) → end_iter
    loop merge-join both iterators
        T->>T: compare hashed keys, emit modified H256s
    end
    T-->>H: "Vec<H256>"
    H-->>C: JSON array of hashed addresses
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/networking/rpc/debug/get_modified_accounts.rs:11-14
**Incompatible response type — hashes instead of addresses**

The Ethereum JSON-RPC spec and geth both define the return type as `Vec<Address>` (20-byte addresses). This implementation returns `Vec<H256>` (32-byte keccak256 hashes of those addresses), so any caller using a standard Ethereum client library (`web3.js`, `ethers.js`, `cast debug getModifiedAccountsByNumber`, etc.) will receive 32-byte values where it expects 20-byte addresses and will either fail to parse them or misinterpret them silently. The doc comment itself explains the gap, but the gap itself is the issue: without a preimage store, callers have no way to reverse the hash back to an address, making the response practically unusable for the common task of "which accounts changed?"

### Issue 2 of 3
crates/networking/rpc/debug/get_modified_accounts.rs:59-64
**`RpcErr::Internal` used for a user-caused block-not-found condition**

When `start_number` or `end_number` resolves successfully (the block tag/number itself is valid) but the corresponding block header doesn't exist in storage, the handler returns `RpcErr::Internal` with "Start block header not found". `RpcErr::Internal` is intended for unexpected server-side failures; a missing block at a requested number is a client error and would be better surfaced as `RpcErr::BadParams` (or the equivalent) so callers can distinguish "the RPC server broke" from "the block you asked for doesn't exist".

### Issue 3 of 3
crates/networking/rpc/debug/get_modified_accounts.rs:152-168
**`peek()` borrows held across mutable `next()` calls in the match loop**

Each match arm in the `loop` calls `start_iter.peek()` and/or `end_iter.peek()`, binding references that belong to the respective `Peekable` wrapper. Several arms subsequently call `start_iter.next()` or `end_iter.next()` while the peeked references are still nominally in scope. This compiles today under NLL because the borrow checker sees that the references are last used before `next()` is called, but the pattern is fragile: any future refactor that adds even a single read of a peeked reference after the `next()` call will introduce a compile error that is non-obvious to diagnose. Consider extracting copies of the keys before the match body to make the lifetime boundaries explicit.

Reviews (1): Last reviewed commit: "fix(rpc): correct, stream, and spawn_blo..." | Re-trigger Greptile

Comment on lines +11 to +14
/// **Divergence from geth**: geth returns `Vec<Address>` by looking original
/// addresses up through its preimage store. ethrex has no preimage store, so
/// the response is `Vec<H256>` of *hashed* addresses (`keccak256(address)`).
/// Callers that know the address they care about can hash it themselves to
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 Incompatible response type — hashes instead of addresses

The Ethereum JSON-RPC spec and geth both define the return type as Vec<Address> (20-byte addresses). This implementation returns Vec<H256> (32-byte keccak256 hashes of those addresses), so any caller using a standard Ethereum client library (web3.js, ethers.js, cast debug getModifiedAccountsByNumber, etc.) will receive 32-byte values where it expects 20-byte addresses and will either fail to parse them or misinterpret them silently. The doc comment itself explains the gap, but the gap itself is the issue: without a preimage store, callers have no way to reverse the hash back to an address, making the response practically unusable for the common task of "which accounts changed?"

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

Comment:
**Incompatible response type — hashes instead of addresses**

The Ethereum JSON-RPC spec and geth both define the return type as `Vec<Address>` (20-byte addresses). This implementation returns `Vec<H256>` (32-byte keccak256 hashes of those addresses), so any caller using a standard Ethereum client library (`web3.js`, `ethers.js`, `cast debug getModifiedAccountsByNumber`, etc.) will receive 32-byte values where it expects 20-byte addresses and will either fail to parse them or misinterpret them silently. The doc comment itself explains the gap, but the gap itself is the issue: without a preimage store, callers have no way to reverse the hash back to an address, making the response practically unusable for the common task of "which accounts changed?"

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

Comment on lines +59 to +64
.await?
.ok_or(RpcErr::Internal("End block not found".to_string()))?;

if start_number > end_number {
return Err(RpcErr::BadParams(format!(
"start block ({start_number}) must be older than end block ({end_number})"
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 RpcErr::Internal used for a user-caused block-not-found condition

When start_number or end_number resolves successfully (the block tag/number itself is valid) but the corresponding block header doesn't exist in storage, the handler returns RpcErr::Internal with "Start block header not found". RpcErr::Internal is intended for unexpected server-side failures; a missing block at a requested number is a client error and would be better surfaced as RpcErr::BadParams (or the equivalent) so callers can distinguish "the RPC server broke" from "the block you asked for doesn't exist".

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

Comment:
**`RpcErr::Internal` used for a user-caused block-not-found condition**

When `start_number` or `end_number` resolves successfully (the block tag/number itself is valid) but the corresponding block header doesn't exist in storage, the handler returns `RpcErr::Internal` with "Start block header not found". `RpcErr::Internal` is intended for unexpected server-side failures; a missing block at a requested number is a client error and would be better surfaced as `RpcErr::BadParams` (or the equivalent) so callers can distinguish "the RPC server broke" from "the block you asked for doesn't exist".

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

Comment on lines +152 to +168
let mut modified = Vec::new();

loop {
match (start_iter.peek(), end_iter.peek()) {
(None, None) => break,
(Some((s_hash, _)), None) => {
modified.push(*s_hash);
start_iter.next();
}
(None, Some((e_hash, _))) => {
modified.push(*e_hash);
end_iter.next();
}
(Some((s_hash, _)), Some((e_hash, _))) if s_hash < e_hash => {
// Account in start but not yet (and never, since iters are
// monotonic) seen in end at this position — deleted.
modified.push(*s_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 peek() borrows held across mutable next() calls in the match loop

Each match arm in the loop calls start_iter.peek() and/or end_iter.peek(), binding references that belong to the respective Peekable wrapper. Several arms subsequently call start_iter.next() or end_iter.next() while the peeked references are still nominally in scope. This compiles today under NLL because the borrow checker sees that the references are last used before next() is called, but the pattern is fragile: any future refactor that adds even a single read of a peeked reference after the next() call will introduce a compile error that is non-obvious to diagnose. Consider extracting copies of the keys before the match body to make the lifetime boundaries explicit.

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

Comment:
**`peek()` borrows held across mutable `next()` calls in the match loop**

Each match arm in the `loop` calls `start_iter.peek()` and/or `end_iter.peek()`, binding references that belong to the respective `Peekable` wrapper. Several arms subsequently call `start_iter.next()` or `end_iter.next()` while the peeked references are still nominally in scope. This compiles today under NLL because the borrow checker sees that the references are last used before `next()` is called, but the pattern is fragile: any future refactor that adds even a single read of a peeked reference after the `next()` call will introduce a compile error that is non-obvious to diagnose. Consider extracting copies of the keys before the match body to make the lifetime boundaries explicit.

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

Add debug_getModifiedAccountsByNumber and debug_getModifiedAccountsByHash
which return addresses modified between two blocks. Compares state tries
of start and end blocks to find accounts that were created, modified, or
deleted.

Note: returns hashed addresses (converted to Address) since ethrex does
not maintain a preimage store. The brute-force trie comparison is O(n)
in total accounts; a trie-diff algorithm would be more efficient for
mainnet scale.

Part of #6572
Execute a transfer, then query getModifiedAccountsByNumber and
getModifiedAccountsByHash. Assert modified accounts are returned.
Three real issues with the original handler:

- Wrong return values. The handler emitted `Address::from(hash)`, which
  takes the low 20 bytes of the 32-byte hashed-address key. That value
  isn't the original address — without a preimage store (ethrex has
  none) you can't recover it. Switch the response type to `Vec<H256>`
  of hashed addresses and document the divergence from geth on the
  type. The new `*_includes_sender` integration test asserts the sender's
  `keccak256(address)` appears in the result; it would have failed
  against the original truncation.

- Blocking I/O on the async runtime. `Store::iter_accounts` opens four
  long-lived locked DB transactions and walks the trie synchronously
  (same reason snap-sync's validators use `spawn_blocking`). The handler
  now runs the diff on a blocking thread.

- Full state materialised twice. The original collected every account
  from both tries into `HashSet<(H256, Vec<u8>)>` keyed on a stringified
  debug-format value — O(state) memory and millions of `format!()`
  allocations on mainnet. Replace with a streaming merge of the two
  iterators (both yield in hashed-key order from the trie traversal),
  comparing `AccountState` by value. O(1) extra memory.

Test setup boilerplate moves out of the misnamed `debug_trace_tests.rs`
into the shared `rpc::helpers` module introduced in #6701; the file is
renamed to `debug_get_modified_accounts_tests.rs`. Integration coverage
now spans: sender-appears-in-diff, by-number/by-hash equivalence,
empty-range, start-after-end (both variants), and unknown-block.
@azteca1998 azteca1998 force-pushed the feat/debug-get-modified-accounts branch from eaaf276 to 78b3dcf Compare May 27, 2026 15:22
… in debug_getModifiedAccounts

- Replace RpcErr::Internal with RpcErr::WrongParam for block-not-found
  conditions (these are client errors, not server failures).
- Extract copies from peek() before calling next() so the Peekable
  borrows are cleanly released and future refactors won't hit
  borrow-checker surprises.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

L1 Ethereum client

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

Implement debug_getModifiedAccountsByNumber/ByHash

2 participants