Add EIP: Execution-Layer Reorg State Retention Window#11601
Conversation
|
✅ All reviewers have approved. |
Co-authored-by: Andrew B Coathup <28278242+abcoathup@users.noreply.github.com>
|
The commit 143ba14 (as a parent of c63f1ce) contains errors. |
jochem-brouwer
left a comment
There was a problem hiding this comment.
I feel like this EIP should (if we had the chance to time travel back a few years ago) have lived in execution-specs or a similar repository (as the definition and this constant). This EIP also uses several constants from "current fork" which is not future-proof. The status cannot start as Living, and although I emoji-accepted the Informational status before I believe this is not correct as it violates current EIP-1 spec.
If we need this now, categorize it as Core and as Draft (and check my other comments). Not sure, but my feeling is that this constant/definition should have lived elsewhere that EIPs repository (which we should have addressed at Paris 😓 )
Co-authored-by: Jochem Brouwer <jochembrouwer96@gmail.com>
jochem-brouwer
left a comment
There was a problem hiding this comment.
We discussed previous objects and problems I had in sync and this now looks fine, LGTM, lets merge!! 🚢 🚀
eth-bot
left a comment
There was a problem hiding this comment.
All Reviewers Have Approved; Performing Automatic Merge...
) ## Summary Implements [EIP-8252](ethereum/EIPs#11601 `REORG_RETENTION_WINDOW = 262_144` blocks (~36.4 days) as the new default for full and blocks prune modes. ### Mode definitions | Mode | Blocks | History | Net effect on mainnet | |---|---|---|---| | `archive` | `KeepAllBlocksPruneMode` | sentinel | unchanged | | `full` (default) | `Distance(262_144)` *(was `DefaultBlocksPruneMode`)* | `Distance(262_144)` *(was `100_000`)* | tx + state pruned to last 262k blocks | | `blocks` | `KeepAllBlocksPruneMode` | `Distance(262_144)` *(was `100_000`)* | state pruned to last 262k blocks | | `minimal` | `Distance(100_000)` | `Distance(100_000)` | unchanged — pinned to new `MinimalPruneDistance` constant, deliberately sub-EIP-8252 | The `DefaultBlocksPruneMode` sentinel is also renamed in this PR to `KeepPostMergeBlocksPruneMode` to reflect what it actually does (drop pre-merge data, keep post-merge) now that it's no longer the default for any named mode. ### Fresh-sync impact New full nodes download substantially fewer transaction segments than 3.4's full mode. `buildBlackListForPruning` activates for distance-based `Blocks` and blacklists tx segments with `To <= frozenBlocks - 262_144` before the download loop. On mainnet today (head ~22M, merge ~15.5M) that's keeping ~262k blocks of tx instead of ~6.5M post-merge blocks — roughly a 25× reduction in transaction-segment disk footprint. Headers and bodies (excluding tx) follow their existing rules. ### Existing-datadir upgrades `EnsureNotChanged` got a generic retention-window compat path: - Accepts finite↔finite `Distance` changes on `History` or `Blocks` in either direction (covers the EIP-8252 default bump and any operator-initiated `--prune.distance` change). - Accepts the either-direction transition between a finite `Distance` and `KeepPostMergeBlocksPruneMode` (chain-history-expiry sentinel) on `Blocks` — so legacy full datadirs upgrade silently, and operators can revert with `--prune.distance.blocks=18446744073709551615` even after the auto-upgrade has rewritten the persisted value. - Rejects any transition involving `KeepAllBlocksPruneMode` — narrowing from "keep all blocks" is the one truly destructive direction and stays explicit. When a compatible transition is accepted, the persisted value is rewritten so subsequent restarts don't re-fire the warning. The pre-existing archive-default-bump compat branch is preserved for the orthogonal `{KeepPostMergeBlocksPruneMode, KeepPostMergeBlocksPruneMode} → ArchiveMode` legacy-archive case. ### `eth_capabilities` clamp Previously `eth_capabilities` advertised `receipts.oldestBlock=0` and `logs.oldestBlock=0` whenever `--persist.receipts` was set, regardless of whether the node could actually serve them. That was correct under the old `FullMode` shape (`Blocks=DefaultBlocksPruneMode` kept block bodies indefinitely), but not under the new finite-Distance shape — `eth_getBlockReceipts` walks block bodies and `getLogsV3` reads log indexes, both of which follow `prune.Blocks`. The RPC now reports `blocksOldest` for receipts/logs with the same `DeleteStrategy{window, N}` as blocks, so routing layers can correctly determine which node to ask. ### Caveat: frozen `.seg` file deletion is gated by erigontech#21306 Physical deletion of already-on-disk frozen transaction segments is not yet implemented (erigontech#21306). Upgraded full datadirs will record the new cutoff but keep the old `.seg` files on disk until that lands; freshly-synced full nodes use the blacklist at download time and avoid the data in the first place. So an upgraded full node will have *more* on-disk data than a freshly-synced one until erigontech#21306 ships. ### Other changes wrapped up in this PR - **`Distance.Enabled()` corrected** to return false for both sentinel values (`KeepPostMergeBlocksPruneMode` and `KeepAllBlocksPruneMode`). Previously only the former was excluded; the snapshotsync blacklist needed a local workaround that's now gone. - **`isTransactionsSegmentExpired` removed**, absorbed into `buildBlackListForPruning`. The download path now has one filter for tx segments instead of two parallel ones covering different mode shapes. `buildBlackListForPruning` uses `cc.IsPreMerge(res.From)` directly for the chain-history-expiry case rather than indirecting through `blocksRetentionCutoff` with a `head=0` trick. - **`blocksRetentionCutoff` extracted** and used by `isReceiptsSegmentPruned` so the per-mode cutoff logic lives in one place. - **`downloadFilteringApplies` extracted** to gate the slow `getMinimumBlocksToDownload` call with the same predicate `buildBlackListForPruning` uses internally; previously the gate was narrower than the function's own activation logic, which caused the `--prune.mode=archive --prune.distance.blocks=18446744073709551615` hybrid to silently skip pre-merge tx filtering. - **`Mode.String()` recognises legacy shapes** rather than rendering them as misleading "archive ..." strings: `{KeepPostMergeBlocksPruneMode, finite}` → `"full(legacy) --prune.distance=N"`; `{KeepAllBlocksPruneMode, finite}` → `"blocks --prune.distance=N"`. The fallback rendering still mirrors `FromCli` input so operator-supplied `--prune.mode=archive --prune.distance=…` round-trips visibly. - **`duDetectNodeType` blocks-mode gate** uses `DefaultPruneDistance` for the maturity check: a genesis tx segment is evidence of blocks mode only on a chain past the point where full would have pruned it. On chains in the `[MinimalPruneDistance, DefaultPruneDistance]` band, full and blocks are indistinguishable on disk; the fall-through defaults the ambiguous case to full, the more common mode. - **`duComputeEstimates` returns a 4th row** for blocks mode — mirrors full's state-history pruning but keeps all tx segments and receipt-related state, matching the runtime behaviour. - **`setIfNotExist` and `overwriteStoredMode` share a `writeBlockAmount` helper** that owns the on-disk serialisation. - Docs updated: `docs/site/docs/fundamentals/sync-modes.md`, `execution/commitment/qmtree/PRUNE_POLICY.md`, ChangeLog entry, regenerated `llms-full.txt` artifacts. Refs EIP-8252 (informational): ethereum/EIPs#11601 Closes erigontech#20447 ## Test plan - [x] `make lint` clean across multiple runs (non-deterministic linter) - [x] `go build ./...` clean - [x] `go test ./db/kv/prune/...`: 11-case `isRetentionWindowChange` table + 9 `EnsureNotChanged` integration tests (legacy minimal no-op, blocks-mode History bump with DB rewrite, full sentinel→finite acceptance, finite→`KeepPostMergeBlocksPruneMode` revert acceptance, `KeepAllBlocksPruneMode` narrowing rejected, archive identity, arbitrary-distance change, pre-existing archive-default-bump compat) + `TestModeString_LegacyShapes` - [x] `go test ./db/snapshotsync/...`: `TestBlackListForPruning` (asserts against the adjusted-cutoff value the function actually uses, so a regression in `adjustBlockPrune` would fail loudly), `TestBlackListForPruning_BlocksModeKeepsAllTransactions` (locks down the `Distance.Enabled()` fix), `TestBlackListForPruning_ChainHistoryExpiry` (covers the absorbed sentinel path against mainnet preverified), `TestDownloadFilteringApplies` (8-case table including the `{KeepPostMerge, KeepPostMerge}` hybrid) - [x] `go test ./cmd/utils/app/... -run TestDU`: boundary fixtures at `To` in the gap between minimal's and full's cutoffs validate per-mode pruning math; blocks-mode row in estimates; detector classification including the `[MinimalPruneDistance, DefaultPruneDistance]` ambiguity-band regression test - [x] `go test ./rpc/jsonrpc/... -run TestCapabilities`: legacy-full shape (chain-history-expiry sentinel) and finite-finite shape (EIP-8252) covered, including `--persist.receipts` interactions and `MergeHeight` clamping - [ ] Manual smoke test against a real mainnet datadir (deferred — happy to do on request) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: info@weblogix.biz <admin@10gbps.weblogix.it>
Add a state-retention floor of
262_144blocks (=2**18; 8192 epochs × 32 slots, ~36.4 days) so EL clients can locally process any reorg without triggering sync.8192 epochs covers any leak scenario where up to ~79% of stake started offline.