Skip to content

execution/types, stagedsync: EIP-7928 phantom-read early reject#21495

Draft
yperbasis wants to merge 2 commits into
mainfrom
yperbasis/bal-phantom-read-reject
Draft

execution/types, stagedsync: EIP-7928 phantom-read early reject#21495
yperbasis wants to merge 2 commits into
mainfrom
yperbasis/bal-phantom-read-reject

Conversation

@yperbasis
Copy link
Copy Markdown
Member

Summary

Implements the EIP-7928 Security-Considerations mitigation against a malicious proposer declaring phantom storage_reads. The existing ValidateMaxItems only checks the overall BAL size bound post-execution — after the client has already paid the I/O cost the mitigation is meant to avoid. This PR adds the per-tx gas-feasibility budget check:

G_remaining >= R_remaining * BalItemCost   (BalItemCost = 2000)

where R_remaining is the number of declared storage_reads not yet observed in actual execution.

Changes

  • execution/types/block_access_list.go
    • BlockAccessList.CountDeclaredReads() intR_declared.
    • DeclaredReadTracker (NewDeclaredReadTracker(bal), ObserveRead(addr, slot), Remaining()) — idempotent so safe under the parallel executor's speculative re-execution.
    • EarlyRejectCheck(blockGasLimit, gasUsed, remainingDeclaredReads) error.
  • execution/stagedsync/exec3_parallel.go
    • blockExecutor gains blockGasLimit (captured from the fresh gasPool) and declaredReadTracker (built from the declared accessList already plumbed in).
    • New gasUsedSoFar() helper computes blockGasLimit - min(regular, state) so the check is conservative across both EIP-8037 dimensions.
    • After each tx validates and consumes its gas in nextResult, the finalized read set is scanned for StoragePath reads, attributed via ObserveRead, and EarlyRejectCheck runs. Failure routes through the existing invalidBlockResult path as ErrInvalidBlock.

Test plan

  • go test ./execution/types/ — new unit tests cover counting, draining, idempotency, undeclared-slot no-ops, empty BAL, gas boundary, and gas-accounting bug.
  • go test ./execution/stagedsync/ -short — no regressions.
  • make lint clean (twice).
  • make erigon integration — builds.
  • Sequential path (exec3_serial.go) — not yet wired; same hook applies, deferred to keep this PR focused. Will follow up.
  • End-to-end test driving a phantom-read block through the parallel executor — blockExecutor setup is heavy; the pure-logic unit tests cover the check, and the integration site is ~10 lines.

🤖 Generated with Claude Code

Implements the Security-Considerations mitigation from EIP-7928 against
malicious proposers declaring phantom storage_reads. Without this check
ValidateMaxItems only catches an oversized BAL post-execution, after the
client has already paid the I/O cost the mitigation is meant to avoid.

execution/types/block_access_list.go adds CountDeclaredReads,
DeclaredReadTracker (idempotent under speculative re-execution) and the
EarlyRejectCheck budget rule: G_remaining >= R_remaining * BalItemCost.

execution/stagedsync/exec3_parallel.go captures the block gas limit at
newBlockExec, builds a tracker from the declared accessList, and after
each tx validates and consumes its gas walks the StoragePath reads,
attributes them to declared slots, and calls EarlyRejectCheck. Failure
returns ErrInvalidBlock via the existing invalidBlockResult path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@yperbasis yperbasis added the Glamsterdam https://eips.ethereum.org/EIPS/eip-7773 label May 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Glamsterdam https://eips.ethereum.org/EIPS/eip-7773

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant