reth EIP-7928 report: see how a Hackerbane report is structured. Open the report → (opens in a new tab)

· Barney Chambers

EIP-7928 block-level access lists in the go-ethereum and reth reviews

EIP-7928 commits a block-level access list in the block header, so clients that build different lists reject each other's blocks. The go-ethereum report records two open High findings against the execution spec, and the reth report records one open High when system predeploys have no code, with every finding in both reports still open.

EIP-7928 adds a block-level access list to every Amsterdam block. The client writes down which accounts and storage slots the block touched, and the values after each step, then hashes that list into the header. Another client that builds a different list computes a different hash and rejects the block. Equal account balances can still sit under two different list hashes.

Pre-execution system calls use index 0. Transactions use 1 through n. Post-execution system calls and EIP-4895 withdrawals share index n+1. The hash follows the order of that work and the rule for netting two writes at the same index. A bad list can still fail when the client compares that hash with the list it rebuilds, and the compare happens after the client has already done the work.

Hackerbane published two reviews of that surface. HACK-AR-2026-GETH-EIP-7928 reads ethereum/go-ethereum at 5b5c9d06810f8dfcc092e8d1c35b34c11fa9b8fe. The report date is 22 September 2026. HACK-AR-2026-RETH-EIP-7928 reads paradigmxyz/reth at 1136e1140023de2468e395a2633cf90cd3d03780, with revm 4535a5786fb90b25eebe2d5bb49d3954f1e6d546, alloy-evm 0.39.0, and alloy-eip7928 0.4.10. The report date is 20 September 2026. Each finding below is taken from the report for that pin. Code after those commits was outside both reviews.

go-ethereum

The go-ethereum cover counts are Critical 0, High 2, Medium 0, Low 1, Informational 2. On this pin, geth disagrees with the execution-specs client (EELS) on the access-list hash of the cross-index fixtures named below. Of 208 filled execution-spec test files, 1207 cases pass and 3 fail. Those 3 failures are access-list hash mismatches. Empty code at the 7002, 7251, and 8282 system contracts returns the error empty system contract.

HACK-01 is High (7.5), status PENDING. EIP-7928 and EELS apply_body set the access index to the transaction count plus one, process withdrawals, then run four system calls: 7002, 7251, and the two 8282 calls. StateProcessor.Process runs those four calls first, then Beacon.Finalize withdrawals, still at that same index. When a withdrawal credits one of those predeploys and the dequeue call forwards the balance, EELS records the balance after the forward and geth records the balance after the withdrawal. A geth builder seals the geth list. An EELS-conformant validator rejects it. The reverse holds as well. The report's proof is the filled fixture test_bal_withdrawals_and_dequeues_net_balance_at_last_index, in a forward-all variant and a forward-half variant.

HACK-02 is High (7.5), status PENDING. The spec says each write is compared with the storage value from immediately before the current index. EELS keeps one builder for index n+1 and nets every post-execution call against the start of that index. Geth prepares a fresh list for each system call and Merge keeps the later value when the same slot and index collide. A slot written and then restored across two calls at n+1 is encoded as a write of the last value. The execution-spec fixture expects that slot among the storage reads. The third failing case is test_bal_post_execution_calls_net_storage_at_last_index. The report says default production layouts were not shown to collide on one slot. The fixture substitutes bytecode so both calls run the same body. The merge on this pin is still last-write-wins for every Amsterdam block.

HACK-03 is Low (2.5), status PENDING. EIP-7928 caps the item count by the block gas limit divided by the item cost. Geth enforces that in ValidateSize after the list is already in memory. DecodeRLP appends every item with no count cap, and the Engine API decodes engine_newPayloadV5 before that check. The report measured 1.5 million empty accounts, 40.5 MiB of RLP, a heap increase of about 19 MiB, then a reject. That machine did not stall. eth/71 is separately capped at 1024 lists and 2 MiB.

HACK-04 is Informational (0.0), status PENDING. AssembleBlock writes the access-list hash and attaches the list without calling ValidateSize. getPayload can return a list that fails the size bound. Import then rejects the same node's block. The report records no chain split.

HACK-05 is Informational (0.0), status PENDING. If EncodeRLP fails, Hash returns the zero hash. The empty-list hash is the keccak of 0xc0. A non-zero header hash mismatches, and the block is rejected. The report has no vector on this pin that makes EncodeRLP fail.

Chapter 8 of the go-ethereum report says no patch commit was reviewed and that the assessment does not include a remediation review. HACK-01 through HACK-05 are PENDING on 5b5c9d06.

reth

The reth cover counts are Critical 0, High 1, Medium 1, Low 2, Informational 7. Eleven findings are recorded and eleven are outstanding. Verified fixes: 0. The report says reth at this pin does not split from EELS on any input reachable on a correctly configured Amsterdam chain. It also says no product remediation was performed for the report.

HACK-RETH-01 is High, status Open. alloy-evm 0.39.0 runs the four post-execution calls, 7002, 7251, and both 8282 contracts, with no check that the target has code. Empty bytecode becomes a successful stop, the request list is empty, and the block continues. EELS raises an invalid block. Geth blocktest on the same fixtures reports empty system contract. The report's prerequisites are an Amsterdam activation, or Prague for 7002 and 7251, where at least one of those predeploys has no code. Mainnet, and any chain that deployed the contracts before the fork, does not meet them. On a chain that does, reth accepts blocks EELS and geth reject, a reth builder produces those blocks, and the other clients refuse to build. The report records no live mainnet theft path. It also records that alloy-rs/evm#400 covers the two 8282 addresses only and is unmerged into this pin.

HACK-RETH-02 is Medium, status Open. If two commits land on the same field at one access index, and there was no write at an earlier index, BalWrites takes its baseline from the commit being applied. That baseline is the first commit's post-value, where the spec wants the value from before the index. A second write of the same post-value drops an entry the spec keeps. A write that restores the old value keeps an entry the spec drops. The report says this arm is unreachable on alloy-evm 0.39.0, which commits each account once per index. A reth-shaped dump reported no same-index field duplicates. The report says two withdrawals to one address are already combined in the withdrawal accounting, so that case cannot occur on this pin. A sealed access-list round trip through the block executor is unrun in this report. If a later executor did commit twice at one index, the rebuilt list would disagree with EELS.

HACK-RETH-03 is Low, status Open. The eth/71 BlockAccessLists decoder walks the outer list with no item cap and copies each item. The report measured a 10 MiB payload of one-byte items decoding to 10,485,760 entries, about 657 MB of resident memory, 64.2 times the input, in 0.33 seconds on the test machine. No stall was observed. Serving already caps at 1024 items. The receive side truncates after the decode. The report says PR 26841 was closed unmerged.

HACK-RETH-04 is Low, status Open. A payload timestamp from Osaka until just before Amsterdam makes engine_newPayloadV5 pass the version check. An attributes timestamp from Prague until just before Amsterdam makes engine_forkchoiceUpdatedV4 pass it. The Engine API requires error -38005 in both windows. A misconfigured consensus client can receive VALID, INVALID, or SYNCING, or a built payload, in place of error -38005. The block is still validated as a pre-Amsterdam block. The report records no consensus split between two correct consensus clients. Live Engine RPC was not run. The review had no reth binary and did not run hive.

The seven Informational findings are open as well.

  • HACK-RETH-05, Informational. BlockAccessIndex is a u64. The spec type is uint32. An index of 2^32 or the maximum u64 decodes, and the rebuilt-hash compare rejects the block. The report observed no accepted invalid block.
  • HACK-RETH-06, Informational. A comment says zero-amount withdrawals are skipped. The loop records them, which is what EIP-7928 requires. The report says the current code is correct. An edit that changed the loop to match the comment would drop those recipients and split from EELS.
  • HACK-RETH-07, Informational. ExecutionPayloadV4::from_block_unchecked writes the 32-byte access-list hash into the field that must hold access-list bytes. getPayloadV6 uses the _with_bal helper and does not call this one. The report says current envelope construction does not use the broken path.
  • HACK-RETH-08, Informational. The parallel state root skips accounts whose access-list entry does not change the state root. Deleting an empty account that still has storage removes it from canonical state and leaves it in the BAL-derived state, so the state roots disagree and the block is rejected. The report says EIP-7523 treats that pre-state as absent on post-merge chains, including mainnet.
  • HACK-RETH-09, Informational. A received list is decoded before the item bound. The bound counts addresses and storage keys. The report's probe puts 10,000 nonce_changes on one address, counts that as one item, and gets a successful gas-limit check at a gas limit of 30,000,000. The hash compare rejects a non-canonical list after execution. The caller is an authenticated Engine client. The eth/71 path is a separate finding.
  • HACK-RETH-10, Informational. The builder hashes the list it built and skips the item-bound check in finish. The report's impact is a block that reth would build and then reject, including on its own validation. It records no accepted invalid block. It also says the path is unreachable above about 4.2 million gas, and that the review did not build a block under that gas limit.
  • HACK-RETH-11, Informational. Sorting, uniqueness, one list per slot, and the index bound are enforced by the rebuilt hash, after execution. Workers may run against a non-canonical list. The canonical rebuild differs, the hash compare fails, and the payload is invalid. The report says the verdict is correct and that no accepted invalid block was observed.

The reth report also records what held. The rebuilt-hash compare rejects a lying received list. Workers read only entries below their own index. Aside from the named divergences, the traced EIP-7928 rows match EELS on the verdict and the access-list bytes. Geth and EELS agree that empty system contracts invalidate the block. The report treats HACK-RETH-01 as a reth and alloy-evm miss against those two oracles.

The retest table shows 0 retested and 11 awaiting fixes. The report's release line is to avoid using this pin as the only execution client on a network that might omit system predeploys. On a correctly configured chain, HACK-RETH-01 is not reached.

What both reports leave open

The two pins fail in different places. Geth runs the four post-execution system calls before withdrawals at index n+1, where EELS runs withdrawals first, and geth merges same-index writes last-write-wins. Those two are High, and they show up as access-list hash mismatches on the filled fixtures. Reth's High is the missing code check on the system predeploys, and it applies when a chain activates without that code. Reth's same-index netting bug is rated Medium. On the report's evidence, the current executor commits each account once per index.

Neither report records a reviewed patch commit or a verified fix. The go-ethereum findings are PENDING. The reth findings are Open.

The full write-ups, scopes, and evidence lists are in the two reports: go-ethereum EIP-7928 and reth EIP-7928.

How a review works. Request an audit.