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).
versionedStateReader's wipe scan andversionedReadCore's no-cell scan answer the same question — did an in-block SELFDESTRUCT erase this field — from two different lower bounds.destructScanFloorreturnsscanEverything(-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).versionedReadCore's no-cell branch callsFindDoneSelfDestructInRange(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
CodeHashPathonly, EVM sideCodeHashPathandBalancePath— and their own "no cell" test.Why it is inert today
The only writer of a
SelfDestructcell at -1 isNormalize's EIP-161 pass (writeset_normalize.go:479), and it fires only for an address whose write set carries Balance, Nonce and CodeHash and endsbalance == 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
scanEverythingas the no-cell floor, the per-path bump, andUnknownDepas 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.gois worth sharing, thecalcFeesfixture inexec3_finalize_test.gois copied three times, andpreBlockStateReader(revival_reader_test.go:33) duplicatesfallthroughStateReader(versionedio_test.go:545) in the same package.Found by Copilot on #23072, #23072 (comment).