Skip to content

execution/vm: include SELFDESTRUCT beneficiary in BAL regardless of value transfer#21333

Merged
AskAlexSharov merged 7 commits into
mainfrom
worktree-bal-devnet-7-issue
May 22, 2026
Merged

execution/vm: include SELFDESTRUCT beneficiary in BAL regardless of value transfer#21333
AskAlexSharov merged 7 commits into
mainfrom
worktree-bal-devnet-7-issue

Conversation

@taratorio

Copy link
Copy Markdown
Member

added a test for this to EEST ethereum/execution-specs#2893


Summary

makeSelfdestructGasFn in execution/vm/operations_acl.go previously gated MarkAddressAccess(beneficiary, false) on the destructing contract having a non-zero balance, and on the zero-balance path additionally marked the gas-calc Empty() reads internal so the beneficiary would not appear in the BAL purely from that read.

Per EIP-7928, "Beneficiary addresses for SELFDESTRUCT" are in scope for inclusion unconditionally — the SELFDESTRUCT is itself a state access on the beneficiary, independent of any value transfer. The pre-fix gate caused a BAL hash mismatch in the specific case where a SELFDESTRUCT targets SYSTEM_ADDRESS (0xff…fffe) with the destructing contract at zero balance: versionedio.go::AsBlockAccessList's "drop SystemAddress unless it experienced state access itself" filter then dropped the entry because no non-revertable access mark was ever recorded, while other clients (which mark the beneficiary unconditionally) kept it. Result: BAL hash mismatch, block rejected, sync stall.

Fix

execution/vm/operations_acl.go::makeSelfdestructGasFn: record the beneficiary access unconditionally:

// Per EIP-7928, SELFDESTRUCT is a state access on the beneficiary
// independently of any value transfer, so record it unconditionally.
evm.IntraBlockState().MarkAddressAccess(address, false)

The pre-existing SnapshotVersionedReadKeys / MarkNewReadsInternal helpers in execution/state/intra_block_state.go were only used by the now-removed zero-balance branch and are dropped as dead code (−35 lines).

execution/state/versionedio.go::AsBlockAccessList's SystemAddress filter is left unchanged — its && !account.nonRevertableUserAccess clause is now correctly satisfied by the unconditional mark above.

Test coverage

New file execution/execmodule/bal_selfdestruct_systemaddress_test.go adds three regression tests via blockgen.GenerateChain against chain.AllProtocolChanges (Amsterdam active at genesis):

  • TestBALIncludesSystemAddressOnSelfdestructToItWithZeroBalance — the bug scenario: CREATE tx with init code PUSH20 SYSTEM_ADDRESS; SELFDESTRUCT and zero contract balance. Asserts the resulting BAL contains a SystemAddress entry with every change-set empty.
  • TestBALIncludesSystemAddressOnSelfdestructToItWithNonZeroBalance — guard for the non-zero balance variant.
  • TestBALIncludesOrdinaryBeneficiaryOnSelfdestructWithZeroBalance — guard that ordinary EOA beneficiaries (where the SystemAddress filter doesn't apply) are unaffected.

Test plan

  • go test -count=1 -run='TestBALIncludes' ./execution/execmodule/ — 3/3 pass.
  • TDD verified: with the pre-fix if !balance.IsZero() guard restored, TestBALIncludesSystemAddressOnSelfdestructToItWithZeroBalance fails with "BAL must include a SystemAddress entry"; the other two still pass (confirming the bug is specific to SystemAddress + zero balance).
  • make lint — 0 issues.
  • make eest-spec-statetests-devnet — 65,202 / 65,202 tests pass.
  • make eest-spec-blocktests-devnet — 82,941 / 82,941 tests pass (parallel exec).
  • Fresh resync from block 0 against the BAL devnet through previously-divergent blocks: 0 BAL mismatches.
  • EEST upstream coverage: matching test test_bal_selfdestruct_to_system_address_zero_balance opened as a PR against ethereum/execution-specs devnets/bal/7 (link to follow).

@taratorio taratorio requested review from mh0lt and yperbasis as code owners May 21, 2026 09:42
@yperbasis yperbasis added the Glamsterdam https://eips.ethereum.org/EIPS/eip-7773 label May 21, 2026
@yperbasis yperbasis requested a review from Copilot May 21, 2026 10:05

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.

Pull request overview

This PR fixes Block Access List (BAL) generation for SELFDESTRUCT by ensuring the beneficiary is recorded as a state access unconditionally, even when no value is transferred (notably fixing the SYSTEM_ADDRESS + zero-balance edge case described in the PR). This aligns Erigon’s BAL behavior with the EIP-7928 requirement that SELFDESTRUCT implies a beneficiary state access independent of value transfer, preventing BAL hash mismatches and sync failures.

Changes:

  • Record SELFDESTRUCT beneficiary access unconditionally in makeSelfdestructGasFn (removing the previous zero-balance gating behavior).
  • Remove now-dead helper methods from IntraBlockState that only supported the prior zero-balance branch.
  • Add regression tests covering SYSTEM_ADDRESS and ordinary beneficiaries for both zero- and non-zero-balance SELFDESTRUCT scenarios.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
execution/vm/operations_acl.go Marks SELFDESTRUCT beneficiary as a non-revertable address access unconditionally for BAL correctness.
execution/state/intra_block_state.go Deletes unused read-snapshot/internal-marking helpers made obsolete by the new unconditional access marking.
execution/execmodule/bal_selfdestruct_systemaddress_test.go Adds regression tests ensuring BAL includes SYSTEM_ADDRESS (and ordinary beneficiaries) for SELFDESTRUCT, including the zero-balance case.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@yperbasis yperbasis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM — clear EIP-7928 alignment, the SnapshotVersionedReadKeys/MarkNewReadsInternal removal is correctly dead code (only the removed branch used them; MarkReadsInternal is still load-bearing for the readOnly CALL-with-value path in instructions.go), and the TDD + devnet evidence is exactly the right shape.

Two non-blocking nits for optional follow-up:

  1. TestBALIncludesSystemAddressOnSelfdestructToItWithNonZeroBalance only asserts NotNil(sysEntry). The body says "the BAL records a balance change on the SystemAddress beneficiary," but the test wouldn't actually catch a future regression that broke that — consider tightening with require.NotEmpty(t, sysEntry.BalanceChanges) so the assertion matches the comment's stated guarantee.

  2. The nested append([]byte{0x73}, append(addr.Bytes(), 0xff)...) init-code construction is repeated across all three tests; a tiny helper like pushAddrSelfdestruct(addr) would deduplicate. Pure style — feel free to ignore.

@taratorio

Copy link
Copy Markdown
Member Author

LGTM — clear EIP-7928 alignment, the SnapshotVersionedReadKeys/MarkNewReadsInternal removal is correctly dead code (only the removed branch used them; MarkReadsInternal is still load-bearing for the readOnly CALL-with-value path in instructions.go), and the TDD + devnet evidence is exactly the right shape.

Two non-blocking nits for optional follow-up:

  1. TestBALIncludesSystemAddressOnSelfdestructToItWithNonZeroBalance only asserts NotNil(sysEntry). The body says "the BAL records a balance change on the SystemAddress beneficiary," but the test wouldn't actually catch a future regression that broke that — consider tightening with require.NotEmpty(t, sysEntry.BalanceChanges) so the assertion matches the comment's stated guarantee.
  2. The nested append([]byte{0x73}, append(addr.Bytes(), 0xff)...) init-code construction is repeated across all three tests; a tiny helper like pushAddrSelfdestruct(addr) would deduplicate. Pure style — feel free to ignore.

addressed in 9cd53ea

@taratorio taratorio enabled auto-merge May 21, 2026 13:21
@taratorio taratorio added this pull request to the merge queue May 21, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 21, 2026
@taratorio taratorio enabled auto-merge May 22, 2026 04:54
@taratorio taratorio added this pull request to the merge queue May 22, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 22, 2026
@AskAlexSharov AskAlexSharov added this pull request to the merge queue May 22, 2026
@AskAlexSharov AskAlexSharov removed this pull request from the merge queue due to a manual request May 22, 2026
@AskAlexSharov AskAlexSharov enabled auto-merge May 22, 2026 07:35
@AskAlexSharov AskAlexSharov added this pull request to the merge queue May 22, 2026
Merged via the queue into main with commit d2e577d May 22, 2026
70 checks passed
@AskAlexSharov AskAlexSharov deleted the worktree-bal-devnet-7-issue branch May 22, 2026 11:00
Sahil-4555 pushed a commit to Sahil-4555/erigon that referenced this pull request Jun 3, 2026
…st (erigontech#21599)

minor tidy follow up to erigontech#21333
to move the tests to be EngineApiTester based
should've done it back then but was in a rush to fix the issue
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.

4 participants