Skip to content

feat(evm): archive Amsterdam headers and block access lists - #596

Merged
abernatskiy merged 4 commits into
masterfrom
feat/evm-amsterdam-archive
Oct 13, 2026
Merged

abernatskiy merged 4 commits into
masterfrom
feat/evm-amsterdam-archive

Conversation

@abernatskiy

@abernatskiy abernatskiy commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

What

The Amsterdam (Glamsterdam) upgrade, live on Sepolia since block 11,856,337, adds two header fields and a new per-block state record. This PR makes the EVM dumper accept post-upgrade blocks and archive both.

  • Header fields count towards block-hash verification. blockAccessListHash (EIP-7928) and slotNumber (EIP-7843) are appended after requestsHash, each only when present. Without this, every post-upgrade block fails --verify-block-hash.
  • Block access lists (EIP-7928). New --with-block-access-lists fetches each block's list with eth_getBlockAccessListByBlockHash, and stores it in the raw block as blockAccessList_.
    • Only blocks whose header carries blockAccessListHash are asked for a list. For earlier blocks, reth tries to rebuild the list by re-execution and fails outside its state history.
  • Access-list verification. New --verify-block-access-list-hash checks keccak256(rlp(list)) against the header's blockAccessListHash. A mismatch marks the block invalid, so it is retried like other verification failures.
    • Storage keys and values are encoded as integers. Accounts and changes keep the order the RPC returns them in.
    • Together with --verify-block-hash, this makes the access list the only state record that is verifiable end to end.
  • Response schema. BlockAccessList validates the JSON-RPC shape returned by reth: per account, storageChanges, storageReads, balanceChanges, nonceChanges and codeChanges, each with a block access index.

mapRawBlock picks header fields one by one, so ingest output is unchanged. Normalizing the new data is left for a follow-up.

  • Invalid blocks no longer stall the caught-up dumper (from review). On the finalized polling path, a block that stays invalid used to be re-polled forever, silently. Now each attempt is logged, and after 5 retries the stream fails with the block's reason, as getBlocks() already did. The latest path keeps polling with a warning: at the chain tip, a provider may still be catching up.

Testing

  • New fixture: Sepolia block 11,856,337, the first Amsterdam block, with its block, receipts and access list.
    • The generic per-fixture suite now covers it: block hash, transactions root, receipts root, logs bloom, withdrawals root, sender recovery and access-list hash.
    • Negative control: dropping slotNumber from the header encoding fails the block-hash test.
  • Tampering tests: forged slotNumber, forged blockAccessListHash, a changed storage write and a dropped storage read each break the relevant hash. A list missing a change kind fails the schema. The empty list hashes to keccak256(rlp([])).
  • Rpc integration: a list is fetched and verified; a tampered list marks the block invalid; without verification the list is kept as is; pre-Amsterdam blocks never trigger the access-list call.
  • End to end: evm-dump over Sepolia 11,856,317–11,856,356 (20 blocks either side of the fork) to a local directory. Run with receipts, call traces, state diffs, every --verify-* flag, --call-frame-validation reject, --with-block-access-lists and --verify-block-access-list-hash.
    • Exactly 20 access-list calls, one per post-fork block.
    • Every post-fork raw block carries slotNumber, blockAccessListHash and a blockAccessList_ that an independent Python RLP encoder hashes back to the header value.
    • evm-ingest maps all 40 blocks.
  • evm-rpc suite: 395 passed, 29 skipped. Every package downstream of evm-rpc builds.

Provider notes

As of reth v2.7.0, reth serves eth_getBlockAccessListByBlockHash / ByBlockNumber. The Geth and Erigon 3.7.1 endpoints checked do not, so the access-list method may need to be routed to a different provider from the rest of the dump.

Execution clients only have to keep access lists for about two weeks. A dumper that falls further behind than that can no longer fetch them for the oldest blocks. The response for a pruned list hasn't been observed yet. A null marks the block invalid; after 5 retries the dump fails, naming the unavailable list. An error fails the batch.

🤖 Generated with Claude Code

abernatskiy and others added 2 commits October 12, 2026 14:14
Blocks from the Amsterdam (Glamsterdam) upgrade carry two new header
fields, blockAccessListHash (EIP-7928) and slotNumber (EIP-7843). Both
now count towards block-hash verification; before, every post-upgrade
block failed it.

The dumper can also fetch each block's EIP-7928 access list
(--with-block-access-lists) and check it against the header commitment
(--verify-block-access-list-hash). The list is stored in the raw block
as blockAccessList_. Only blocks whose header commits to a list are
asked for one: for earlier blocks reth tries to rebuild the list by
re-execution and fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Persistent access-list mismatches can silently stall polling ingestion without bounded retries or an error.

1 open finding
What changed in this PR

Adds Amsterdam support to EVM archiving, including new header commitments and optional EIP-7928 block access-list retrieval and verification.

Changes:

  • Encodes Amsterdam header fields and block access-list hashes.
  • Adds RPC, normalization, and dumper support for access lists.
  • Adds fixture-based verification and integration tests.
File Description
evm/​evm-rpc/​src/​verification.ts Encodes Amsterdam headers and access lists.
evm/​evm-rpc/​src/​rpc.ts Fetches and verifies access lists.
evm/​evm-rpc/​src/​rpc-data.ts Defines Amsterdam RPC schemas.
evm/​evm-rpc/​src/​types.ts Extends block and request types.
evm/​evm-rpc/​src/​chain-utils.ts Exposes access-list hashing.
evm/​evm-normalization/​src/​raw.ts Archives access lists in raw blocks.
evm/​evm-dump/​src/​dumper.ts Adds access-list CLI options.
evm/​evm-rpc/​test/​verification.test.ts Tests header and access-list commitments.
evm/​evm-rpc/​test/​rpc.block-access-list.test.ts Tests RPC retrieval and verification.
evm/​evm-rpc/​test/​helpers/​fixture-loader.ts Loads access-list fixtures.
common/​changes/​@subsquid/​evm-rpc/​feat-evm-amsterdam-archive_2026-10-12-05-14.json Records the RPC feature release.
common/​changes/​@subsquid/​evm-normalization/​feat-evm-amsterdam-archive_2026-10-12-05-14.json Records the normalization release.
common/​changes/​@subsquid/​evm-dump/​feat-evm-amsterdam-archive_2026-10-12-05-14.json Records the dumper release.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread evm/evm-rpc/src/rpc.ts
…valid

Once caught up, the dumper reads through PollStream, which cut a batch at
an invalid block and polled it again forever: no retry limit, no log,
and the block's error never surfaced. A persistent verification failure
(a bad access list, rejected call frames) stalled the dumper silently.

Every invalid poll is now logged with its reason. On the finalized path
a block still invalid after 5 retries, the budget getBlocks() gives the
same case, fails the stream with that reason and the RPC URL. At the
head of the latest stream a provider may still be catching up, so that
path keeps polling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

A pruned null access-list response can consume the dumper’s effectively unbounded RPC retry budget and stall archival indefinitely.

1 open finding
1 resolved since last review

🧠 Review effort: Balanced

Comment thread evm/evm-rpc/src/rpc.ts Outdated
A null access list, whether pruned or from an upstream that does not
have the block yet, became a RetryError on the shared client. evm-dump
runs that client with an effectively unbounded retry budget, so the
call could hang without ever reaching a bounded retry or an error.

Like a null state-diff trace, it now marks the block invalid, with a
reason. getBlocks() and the finalized poll stream retry such a block 5
times, then fail with that reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The implementation is internally consistent and covers commitment encoding, RPC integration, failure handling, and negative cases.

0 open findings

1 resolved since last review

🧠 Review effort: Balanced

@abernatskiy
abernatskiy merged commit f9287b3 into master Oct 13, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants