Skip to content

execution/state: the reader-side and EVM-side destruct scans use different lower bounds #23559

Description

@awskii

versionedStateReader's wipe scan and versionedReadCore's no-cell scan answer the same question — did an in-block SELFDESTRUCT erase this field — from two different lower bounds.

  • Reader side: destructScanFloor returns scanEverything (-1) when the value has no in-block cell, so a destruct the block-begin system tx recorded at TxIndex -1 counts (execution/state/versionmap.go:674-693).
  • EVM side: versionedReadCore's no-cell branch calls FindDoneSelfDestructInRange(addr, 0, s.txIndex, true) (execution/state/read_paths.go:740), so the same destruct does not.

Both also carry their own copy of the per-path bump — reader side CodeHashPath only, EVM side CodeHashPath and BalancePath — and their own "no cell" test.

Why it is inert today

The only writer of a SelfDestruct cell at -1 is Normalize's EIP-161 pass (writeset_normalize.go:479), and it fires only for an address whose write set carries Balance, Nonce and CodeHash and ends balance == 0 && nonce == 0 && codeHash.IsEmpty() (writeset_normalize.go:470-472). Every field the reader-side scan wipes off that destruct already holds the value the wipe writes.

DeleteAccountFields (versionedio.go:1062) drops balance/nonce/incarnation/codeHash and leaves Code and Storage, so those are the two paths where the bounds could differ on a value. Code has to be empty for the pass to fire at all. Storage would need the block-begin system tx to write a slot for an address that ends that tx EIP-161-empty, and the system contracts it calls carry code and nonce >= 1.

What to do

One function for both, with scanEverything as the no-cell floor, the per-path bump, and UnknownDep as the only "no cell". Moving the EVM bound changes what the validator sees, so it needs its own test: a block-begin destruct followed by an in-block revival, read back through both paths and compared.

Also from the same review

Test hygiene in the same files, all in #23072: the genesis preamble in execution/tests/statedb_chain_test.go is worth sharing, the calcFees fixture in exec3_finalize_test.go is copied three times, and preBlockStateReader (revival_reader_test.go:33) duplicates fallthroughStateReader (versionedio_test.go:545) in the same package.

Found by Copilot on #23072, #23072 (comment).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions