ethereum / ethereum/execution-specs
fill-stateful: replace debug-namespace chain rewind with an engine API anchor-block rewind
- Dominant language
- Python
- Stars
- 1.2k
- Forks
- 505
- Avg merge
- 2d 14h
- Merged PRs (30d)
- 116
Description
## Problem
The per-test chain rewind in `fill_stateful` relies on the non-standard `debug` namespace (`debug_setHead` on geth, `debug_resetHead` on e.g. Nethermind, see: #3127), falling back to no rewind at all for clients with neither. In the fallback (and `resetHead`) modes, `latest` stays at the previous test's tip until the next test submits its first block, so any state read during test setup (deploy checks, funding checks, nonces) can return state from the abandoned fork.
## Proposal
Use the anchor-block technique from [NethermindEth/gas-benchmarks](https://github.com/NethermindEth/gas-benchmarks): rewind with plain `engine_forkchoiceUpdated` (FCU) and drop the `debug` namespace entirely.
During session pre-run, insert two distinct empty sibling blocks on top of the start block: an **anchor block** `A` (rewind target, never built on) and a **build-base block** `B` (parent of every test's first block).
```text
start_block ──┬── A (anchor: FCU target between tests)
└── B (build base: tests build here)
```
Per-test cycle:
```text
during test N: after test N (rewind):
start_block ──┬── A start_block ──┬── A ◄── FCU head
└── B ── t1 ── t2 └── B ── t1 ── t2
▲ (side chain, abandoned)
head
```
Both `A` and `B` are empty, so after the FCU to `A` the head state equals the start-block state and `latest` is immediately correct.
The two-block construction is what makes the rewind legal: clients reject an FCU to a canonical **ancestor** of the current head. The target must be a fork off some parent. Since `A` and `B` are siblings, `A` is never an ancestor of a test chain built on `B`, so the FCU to `A` is always an ordinary side-chain reorg, which every client supports. `A` and `B` must have distinct hashes (e.g. differing extraData). (Most/All) clients will reject FCU to `B`.
## Notes
- Tests now build on `B`, so the pre-run fixture must record `A` and `B` for fixture consumers to replay. (We might be able to get rid of recording `A`?)
- Keep the teardown verification (FCU returns `VALID`, `latest == A`) to catch live-chain reorgs.
Contributor guide
Research direction
Start in the fill_stateful pre-run fixture and per-test teardown flow; trace how debug_setHead and debug_resetHead are currently selected and how fixture consumers receive block data. Replace that path with engine_forkchoiceUpdated using distinct anchor and build-base blocks, then verify teardown returns VALID and latest equals A while setup reads the start-block state.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend-api-design, testing
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100