erigontech / erigontech/erigon
execution/state: the reader-side and EVM-side destruct scans use different lower bounds
- Dominant language
- Go
- Stars
- 3.6k
- Forks
- 1.5k
- Avg merge
- 1d 16h
- Merged PRs (30d)
- 455
Description
`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, https://github.com/erigontech/erigon/pull/23072#discussion_r3842602175.
Contributor guide
Research direction
Start with destructScanFloor in execution/state/versionmap.go and the no-cell branch of versionedReadCore in execution/state/read_paths.go, then inspect the related normalization and account-field code in writeset_normalize.go and versionedio.go. Add the described block-begin destruct and in-block revival coverage, and verify both read paths return the same result.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- blockchain
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100