feat(tests): EIP-7928 catch per-tx no-op SSTORE after prior tx write#2946
Merged
danceratopz merged 3 commits intoJun 2, 2026
Merged
Conversation
abea5a6 to
932b002
Compare
Adds `test_bal_intra_tx_round_trip_after_prior_tx_write` to verify that a per-tx no-op SSTORE is classified as a read even when an earlier transaction in the same block already wrote to the slot. Two txs call a contract that does `SSTORE(slot=1, 0xff); SSTORE(slot=1, 0x42); STOP`. Tx 1 changes slot 1 from 0 to 0x42 (real change). Tx 2 round-trips 0x42 -> 0xff -> 0x42 (per-tx no-op). Per EIP-7928 §Storage, "MUST check the pre-transaction value" — and pre-transaction is per-tx, not per-block — so only tx 1 must appear in storage_changes. Regression test for the bal-devnet-7 chain split (slot 96338 / EL block 94626). Reproduced live on a 2-node nethermind + erigon kurtosis devnet running the bal-devnet-7 images: erigon rejects with `block access list mismatch` and forks off.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## forks/amsterdam #2946 +/- ##
================================================
Coverage 90.50% 90.50%
================================================
Files 535 535
Lines 32407 32407
Branches 3011 3011
================================================
Hits 29331 29331
Misses 2559 2559
Partials 517 517
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
danceratopz
reviewed
Jun 2, 2026
Member
danceratopz
left a comment
There was a problem hiding this comment.
Thanks for finding and adding this test, @pk910!
Just a couple of minor nits below.
Co-authored-by: danceratopz <danceratopz@gmail.com>
6 tasks
Sahil-4555
pushed a commit
to Sahil-4555/erigon
that referenced
this pull request
Jun 2, 2026
…ist (erigontech#21574) ## Summary Fixes an EIP-7928 block-access-list (BAL) bug where erigon recorded spurious `storage_changes` for no-op SSTOREs, producing a BAL hash that diverged from the block header. The node rejected otherwise-valid blocks with `block access list mismatch` and forked off the canonical chain (observed on bal-devnet-7 at block 94626). ## Root cause `(*VersionedIO).AsBlockAccessList` filtered no-op storage writes only for the **first** write to a slot; any later write was recorded unconditionally. EIP-7928 requires comparing **each** write against the slot's value immediately before its block-access index — a write storing the value the slot already holds is a no-op and must stay a read, not appear in `storage_changes`. So when a transaction wrote a slot the value an earlier transaction in the same block had already set, the parallel executor's BAL gained a spurious `storage_change`, diverging from the canonical BAL. ## Fix `addStorageUpdate` now compares every storage write against the slot's most recent recorded value (falling back to the pre-block read value for the first write) and skips no-ops, in a single pass. ## Testing - **Unit** — `TestVersionedIO_StorageNoOpWriteAfterChangeOmittedFromBAL`. - **Integration (parallel exec)** — `TestEngineApiBALStorageNoOpWriteOmitted`: a contract doing `SSTORE(B); SSTORE(A)` called twice in one block (the second call a per-tx no-op). Red without the fix, green with it. - **EEST `blocktests-devnet` shard** (`devnets/bal/7`) — passes 82,941 / 0 failures under parallel exec. - **Upstream spec test** — the new test in ethereum/execution-specs#2946 (`test_bal_intra_tx_round_trip_after_prior_tx_write`), filled against the matching devnet spec and run via `evm blocktest`: **passes with the fix, fails with `block access list mismatch` without it.** - **Devnet** — a node previously stuck at block 94626 now validates it and syncs to the network head.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🗒️ Description
Adds
test_bal_intra_tx_round_trip_after_prior_tx_writeto verify that a per-tx no-op SSTORE is classified as a read even when an earlier transaction in the same block already wrote to the slot.Two txs call a contract that does
SSTORE(slot=1, 0xff); SSTORE(slot=1, 0x42); STOP. Tx 1 changes slot 1 from 0 to 0x42 (real change). Tx 2 round-trips 0x42 -> 0xff -> 0x42 (per-tx no-op). Per EIP-7928 §Storage, "MUST check the pre-transaction value" — and pre-transaction is per-tx, not per-block — so only tx 1 must appear in storage_changes.Regression test for the bal-devnet-7 chain split (slot 96338 / EL block 94626). Reproduced live on a 2-node nethermind + erigon kurtosis devnet running the bal-devnet-7 images: erigon rejects with
block access list mismatchand forks off.🔗 Related Issues or PRs
N/A.
✅ Checklist
just statictype(scope):.mkdocs servelocally and verified the auto-generated docs for new tests in the Test Case Reference are correctly formatted.@ported_frommarker.Cute Animal Picture