Repository navigation
[r3.8] cl: fix Gloas historical state reads and storage formats - #25256
Merged
AskAlexSharov merged 6 commits intoOct 23, 2026
Merged
Conversation
…t past the shortest table (#24667) `CaplinStateSnapshots.BlocksAvailable()` is `min(SegmentsMax(), IndicesMax())`, and its callers treat the result as the last slot the files cover (the state antiquary resumes at `BlocksAvailable()+1`, the historical reader serves slots up to it). `SegmentsMax()` is inclusive (`to - 1`, like the base snapshots), but the Caplin `IndicesMax()` returned the smallest exclusive `to` over the tables. When all tables end at the same slot the inclusive value wins the `min` and nothing shows. When they end at different slots, `BlocksAvailable()` is one past the shortest table: with `BlockRoot` up to 50,000 and `PendingDepositsDump` up to 100,000 it reports 50,000, but `BlockRoot` has no segment for slot 50,000. `IndicesMax()` now returns `minTo - 1`, the same convention as `BaseRoSnapshots` and as `SegmentsMax()`. Two existing tests asserted the exclusive value and are updated: `TestCaplinStateBlocksAvailableUsesLowestTable` (50,000 → 49,999) and the `IndicesMax()` check in `TestCaplinStateBlocksAvailableStopsAtGap`, which already expected `SegmentsMax()` and `BlocksAvailable()` to be 49,999 next to it. Testing: ``` go test ./db/snapshotsync/ -run TestCaplinState -count=1 # main: TestCaplinStateBlocksAvailableIsCoveredByEveryTable fails with # "table BlockRoot has no segment for BlocksAvailable()=50000" go test ./db/snapshotsync/... ./cl/antiquary/... ./cl/persistence/state/... -count=1 ``` (cherry picked from commit c19cee3)
Fixes #25200 ## Problem `ReadHistoricalState` reads `builders`, `builder_pending_withdrawals` and `payload_expected_withdrawals` from the dump at `slot - slot % SlotsPerDump` plus the per-slot diffs. `ReadRequiredQueueSSZ` returned `ErrMissingGloasData` whenever that dump was missing. The antiquary writes these dumps only for a Gloas state, so every state between the Gloas fork slot and the first Gloas dump slot could not be read. On Sepolia these are slots 11296768–11297279. The same happens for an aligned fork slot whose block is missed. ## Fix The change is reader-only. There is no antiquary change and no persistence-format change. - `ReadHistoricalState` passes the Gloas fork slot to `ReadRequiredQueueSSZ`. - A missing dump at or before the fork slot is read as an empty list. The antiquary does not initialize the Gloas queue writers from a pre-Gloas state, so the first Gloas diff holds the full list. - In that case the replay starts at the dump slot itself, so a diff stored there is not skipped. When the dump is present, the replay still starts at the slot after it. - A missing dump after the fork slot still returns `ErrMissingGloasData`. ## Tests - `TestReadRequiredQueueSSZ_MissedForkSlotBlock`: aligned fork slot with a missed block. There is no dump, and the full diff is at the next slot. - `TestReadRequiredQueueSSZ_MissingDumpAtForkSlotWithBlock`: the dump at an aligned fork slot is missing and the full diff is at the fork slot. - `TestReadRequiredQueueSSZ_MissingDump`: adds a dump slot right after an unaligned fork, which still returns an error. - `TestReadHistoricalState_GloasQueuesBeforeFirstDump`: end-to-end read with fork epoch 49, which is not a multiple of 48. The fork slot is 1568 and the pre-fork dump slot is 1536. - `TestReadHistoricalState_GloasQueuesMissingDumpAfterFork`: end-to-end reverse case. The dump slot is after the fork and the dumps are missing, so the read returns `ErrMissingGloasData`. **TDD:** each new test was written first and failed on the previous code for the intended reason: - `MissedForkSlotBlock`, `MissingDumpAtForkSlotWithBlock` and `GloasQueuesBeforeFirstDump` failed with `ErrMissingGloasData` on the origin/main reader. - `MissingDumpAtForkSlotWithBlock` returned `[b]` instead of `[a, b]` before the replay-start fix. - The added `MissingDump` case expects an error, so it also passes on origin/main. It guards the relaxed check against admitting a dump slot after the fork. - The two `ReadHistoricalState` tests fail under the mutations `gloasForkSlot := r.cfg.GloasForkEpoch` and `gloasForkSlot := ^uint64(0)`. Both mutations passed the suite before these tests existed. ## Validation - `go test ./cl/persistence/state/historical_states_reader/ ./cl/antiquary/ -count=1` - `make lint`, two runs, 0 issues - `make erigon integration` ## Follow-ups (not in this PR) - Builders queue encoder: in-place updates are lost, and a duplicate trailing entry breaks the truncate count (#25205). - If every slot from the fork slot through the next dump slot is missed, there is no Gloas dump there and reads still return `ErrMissingGloasData` until the following dump. This needs 33+ missed slots; on Sepolia it needs 513. - `GloasForkEpoch * SlotsPerEpoch` overflows for a far-future fork epoch. It is reachable only when the reader's config differs from the config that wrote the DB, for example in capcli. - There is no writer-to-reader test across the Gloas fork. - The empty-base path cannot tell a lost first full diff from a missed slot. This happens only with lost rows, not with data the antiquary writes. (cherry picked from commit 0ce9d76)
… epoch (#25201) Part of #25120 (item 7). ## What and why The Caplin archive stored the Gloas `ptc_window` (96 slots × 512 validator indices, 393,216 bytes raw) in `PtcWindowTable` for every slot. The window changes only at epoch boundaries (`process_ptc_window`), so 31 of every 32 rows were copies. On mainnet this would cost about 0.74–1.2 GB per day. This PR stores the window once per epoch in a new table and Caplin state snapshot type, `PtcWindowEpoch`. The estimate is about 23 MB per day at Sepolia size and about 37 MB per day at mainnet size, roughly 32× less. A new type is used instead of changing the meaning of the existing `PtcWindow` rows: old binaries would misread sparse rows in the old type, and a minor file-version bump does not stop them, because Caplin state snapshots accept minor bumps as content-only. ## Changes - New `kv.PtcWindowEpochTable` and snapshot type `PtcWindowEpoch` (`MinCaplinEnum+35`). The key is the epoch-start slot. The value is the zstd-compressed SSZ of the exact window. - The antiquary writes one row per Gloas epoch: - in `addGenesisState`, which covers a fork at genesis and the anchor state; - at each Gloas epoch boundary, which also covers epochs without blocks; - at the fork. If the first Gloas block is after the fork slot, the state is advanced to the fork slot first. - `ReadHistoricalState` reads the row at `RoundSlotToEpoch(slot)`. A missing row returns `ErrMissingGloasData`. A row that does not decode, or has the wrong SSZ length, is a hard error. - `PtcWindowTable` is no longer written or read. Its table, snapshot type and existing data stay, so old binaries keep working. - Startup: if states were already processed past the fork but the fork-epoch row is missing (in the DB and in snapshots), the antiquary re-processes from the last pre-Gloas epoch start that has a block. - Snapshot dump: a range with an empty epoch-start row at or after the fork is not dumped yet, and is retried later. - Availability: `IndicesMax` treats `PtcWindowEpoch` as covered up to the Gloas fork slot (`GloasForkEpoch × SlotsPerEpoch`, saturated), because its files are empty by definition before the fork. Without a beacon config the strict rule stays. - `seg publishable` requires `PtcWindowEpoch` files ending at the common end of the other state types only when that end is past the fork slot; otherwise present files get only the structural checks. If the beacon config cannot be resolved, the files are always required. For a known chain, it also checks that every epoch-start word at or after the fork is non-empty and that each file has the expected word count. `seg integrity --check=Publishable` runs the same check. - A no-op migration, `create_ptc_window_epoch_table`, creates the new table in existing databases, so tools that open chaindata in accede mode do not fail. `DBSchemaVersion` is unchanged. ## Rollout `PtcWindowEpoch` limits state-snapshot availability only at and after the Gloas fork slot. - Chains before the fork (mainnet and Gnosis, where it is not scheduled, and Hoodi until 2026-10-26): Caplin state snapshots work as before, without any `PtcWindowEpoch` files. Nodes that dump state snapshots write the all-empty pre-fork files themselves, so publishing them is optional. - Chains past the fork (Sepolia today, Hoodi after its fork): until `PtcWindowEpoch` files are published, state-snapshot availability ends at the last pre-fork slot, and the antiquary processes the rest locally from just before the fork. To keep full availability, publish the new files before releasing: 1. Run the snapshot generator with this binary. It re-processes from the fork and produces the post-fork range. 2. Publish the new files in the preverified lists. Keep the old `PtcWindow` files, because older releases still need them. 3. Then release the binary. Effects: - Existing archive nodes continue from their DB progress. A Sepolia node that is past the fork re-processes from just before the fork once. - Older binaries ignore the new type. On Sepolia, their state snapshots end at the last `PtcWindow` file, and they antiquate the rest locally. - Existing `PtcWindowTable` rows on upgraded nodes are not deleted (on Sepolia, about 10 GB since the fork, estimated, not measured). - Downgrading after running this binary leaves slots without old rows; the old binary then re-antiquates them through its normal backoff. ## Tests - `TestPtcWindowStoredOncePerEpoch` (`cl/antiquary/ptc_window_epoch_test.go`) checks the exact row for every epoch over six Gloas spec fixtures: a block exactly at the fork, a first block later in the fork epoch, the only block in the epoch after the fork, a fork at genesis, an anchor followed by two epochs without blocks, and a 64-block chain. It also checks that `PtcWindowTable` stays empty. The expected rows come from replaying the fixture blocks through the state transition and taking the window of each Gloas state, independently of the antiquary code. - `TestPtcWindowEpochSnapshotRead`: the row is read from a snapshot segment. - `TestPtcWindowEpochRestart`: a restart in the middle of an epoch. - `TestPtcWindowEpochUpgradeReplay`, `TestPtcWindowEpochUpgradeReplayStartsBeforeFork`, `TestPtcWindowEpochReconstructionStart`: upgrade from a DB without new rows, for a fork at genesis and a later fork. - Missing and corrupt rows: `gloas_roundtrip_test.go`. - Dump guard: `db/snapshotsync/caplin_state_ptc_window_epoch_test.go`. - Fork-aware availability: `TestCaplinStateBlocksAvailableBeforeUnscheduledGloasFork`, `TestCaplinStateBlocksAvailableAtGloasFork`, `TestCaplinStateBlocksAvailableRequiresPtcWindowEpochFromGenesis` (`db/snapshotsync/caplin_state_lifecycle_test.go`). - Publishability and content check: `cmd/utils/app/publishable_check_test.go` (including the fork-aware cases before an unscheduled or future fork, after the fork, and at the fork boundary), `db/integrity/caplin_state_integrity_test.go`. - `TestGloasHistoricalStateRoots` (`empty_epoch_transition`) checks the full state root across an epoch boundary. Multi-block and later-fork fixtures cannot be used for a full-root check yet because of #25199 and #25200, which exist on `main` already. In those fixtures, a field-by-field diff shows that the PTC window matches. The Gloas spec fixtures under `cl/antiquary/tests/test_data/gloas/` add 3.8 MB. (cherry picked from commit 4bb4524)
… participation replay (#25211) ## Summary `HistoricalStatesReader.ReadParticipations` rebuilds the Gloas participation lists by replaying block attestations. The replay did not apply the Gloas payload-match rule, and it dropped errors from the attestation callback. A rebuilt Gloas state could have wrong `TIMELY_HEAD` flags and a wrong state root, or a partial participation list with a nil error. Fixes #25199 ## Payload-match rule (consensus-specs v1.7.0-beta.4) `get_attestation_participation_flag_indices` now applies the Gloas rule in the historical path: - a same-slot attestation (`is_attestation_same_slot`) needs `data.index == 0`; - any other attestation gets `TIMELY_HEAD` only when `data.index == execution_payload_availability[parent_slot % SLOTS_PER_HISTORICAL_ROOT]`. `parent_slot` is `state.latest_block_header.slot` before `process_block_header`, which is the slot of the block at `block.ParentRoot`. The replay resolves it from the block index and falls back to the genesis state's latest block header. The availability bit is read from the stored post-state of the including block. `process_parent_execution_payload` runs before `process_operations` and nothing later in the block changes the bit, so the stored value is the one the spec uses. Blocks without attestations skip the lookup; this keeps the synthetic genesis block of a Gloas-at-genesis chain (zero `ParentRoot`) a no-op. Pre-Gloas behaviour of the rule is unchanged. Errors from the replay (randao mix, attesting indices, flag computation, parent lookup, availability) are now returned. ## Genesis-boundary fixes The fixture tests could not pass without three more fixes: - `readHistoricalBlockRoot` returned an error for the root at the genesis slot, so attestations that target epoch 0 were dropped. It now reads the DB for every slot from the genesis slot on and uses the genesis `block_roots` vector only for slots before it. - In the genesis epoch, attestations were written to the previous epoch participation. An attestation now goes to the current epoch list exactly when its target is the epoch of the requested slot (`data.target.epoch == get_current_epoch(state)`). This also changes epoch-0 output for Altair through Fulu, to the value the spec state has. - The antiquary wrote the active validator indices of the genesis epoch only at the next epoch boundary. `addGenesisState` now writes them too. ## Tests and fixtures - `TestReadHistoricalStateGloasParticipation` rebuilds two Gloas mainnet chains through the antiquary and compares current/previous participation and the block state roots with the live transition. The fixtures are copied from consensus-spec-tests **v1.7.0-beta.2** (`sanity/blocks/attestations_after_missed_slot_use_applied_parent_payload_availability` and `sanity/blocks/attestation`); the beta.4 transition accepts them with full validation. - `TestReadHistoricalStateGloasGenesisBlockWithoutAttestations`: synthetic Gloas genesis block with a zero parent root. - `TestReadParticipationsReturnsBlockRootError`: error propagation. - Unit tests: `TestReadHistoricalBlockRootAtGenesisSlot`, `TestGloasSameSlotAttestationRejectsNonzeroIndex`, `TestGloasSameSlotAttestationAfterGenesisSlot`, `TestPayloadMatchAppliesFromGloas` (Fulu keeps `TIMELY_HEAD`, Gloas drops it). TDD note: `TestGloasSameSlotAttestationAfterGenesisSlot` and `TestPayloadMatchAppliesFromGloas` were added in the second commit, after the behaviour landed in the first one, to close coverage gaps found in review. They were checked by mutation: disabling the `data.slot != 0` same-slot branch, or removing the Gloas version gate, makes them fail. The genesis-block test was red before its fix. ## Validation - `go test ./cl/persistence/state/... ./cl/antiquary/...` - the pre-Gloas reader round-trips with `ERIGON_RUN_HEAVY_TESTS=1` - `make lint` (clean, repeated) - `make erigon integration` ## Follow-ups (not in this PR) - `ReadRandaoMixBySlotAndIndex` has the same genesis off-by-one as `readHistoricalBlockRoot` had: on chains with genesis slot 0 it uses the genesis mix instead of the final epoch-0 mix for epoch-2 attestations, so states in epochs 2 and 3 are rebuilt with wrong committees. - The replay needs the `BlockRootToSlot` index and the availability rows for Gloas blocks with attestations; while the snapshot index is rebuilt, direct `ReadParticipations` callers (liveness, lighthouse) return an error, and antiquary init does not back off on that untyped error. - A missing `ActiveValidatorIndicies` row is still read as an empty list. - No test crosses the Fulu→Gloas fork or covers Heze; pre-Gloas reader tests run only with `ERIGON_RUN_HEAVY_TESTS`. - The Gloas predicate is not shared with the live path in `cl/phase1/core/state`. - Anchors at a slot other than 0 are only partly handled. - `attestingIndicies` picks the attestation format from the attestation slot's epoch, not from the including block's version. (cherry picked from commit 10c5204)
…lap (#25230) Fixes #25205 Stacked on #25212: review only the last four commits. The last commit updates one test of this PR for the `ReadRequiredQueueSSZ` signature from #25212. This PR must ship in the same release as #25231 (builders per-index diff) and #25234 (builder pending payments base plus deltas), which are stacked on it. Without them, a Gloas node writes `builders` rows that rewrite the whole list on every in-place change. ## Problem The state antiquary stores per-slot diffs of the Electra queues (`pending_deposits`, `pending_consolidations`, `pending_partial_withdrawals`) and the Gloas lists (`builders`, `builder_pending_withdrawals`, `payload_expected_withdrawals`) with `SSZQueueEncoder`. The encoder remembered only the previous size and the previous last element. It searched the new list backwards for that element and wrote "truncate N from the front" plus the elements after it. Two cases broke this: - `builders` changes in place (withdrawals, deposits, exits). When a builder that is not the last one changed, the diff was "truncate 0, append nothing", so the change was lost until the next dump. - When an appended entry equalled the previous last entry, the search picked the appended copy, the truncate count went negative and wrapped in `uint32`, and replay emptied the queue. ## Fix The diff byte format and the replay format are unchanged. - `Initialize` keeps a copy of the previous list's element pointers in a slice the encoder owns. - `WriteDiff` first tries a fast path based on pointer identity: queue elements are copy-on-write, so equal pointers hold equal values. If the previous list's tail and the new list's head hold the same pointers, it writes the diff directly. This covers unchanged queues, append-only slots and front pops plus appends. - Otherwise it finds the longest suffix of the previous list that equals a prefix of the new list with KMP, which is linear in the list sizes, and writes `truncate = prevLen - overlap` plus `new[overlap:]`. An in-place builder change becomes a full rewrite of the list for that slot. - `ApplySSZQueueDiff` returns the new `ErrCorruptQueueDiff` when the truncate count is larger than the queue length, before it changes the queue. The readers wrap it, and the antiquary resume backs off on it like it does for `ErrMissingGloasData`. ## Upgrade note There is no migration: the stored bytes of a wrong diff cannot be repaired, and each wrong window ends at the next dump (at most 1535 slots). Re-antiquation does not repair rows already frozen in Caplin state snapshot files, and nothing regenerates a frozen range automatically. On Sepolia the Gloas range written by older releases stays wrong. Electra ranges are affected only in the rare case where two equal queue entries were appended in a row. ## Tests - Collector tests: an in-place change to builder 0 is kept, and an appended duplicate builder pending withdrawal no longer empties the queue. - Encoder tests: in-place `Set` on the same list, duplicate last append, front cut plus postponed re-append, the fast path and its fallback when equal values have different pointers, `truncate == len` accepted and `truncate > len` rejected without changing the queue, an exhaustive round trip over all small lists with duplicates, and a check that the overlap search makes a linear number of equality calls. - Reader and antiquary tests: the corrupt-diff error is wrapped by both readers, and the antiquary backoff includes it. - Benchmark in `ssz_queue_bench_test.go`: at 100k `pending_deposits` entries, a diff takes about 0.24 ms. TDD: the tests were written first and failed on the old code for the intended reason, except the `truncate == len` check and the linear-call check, which also pass on the old encoder and guard the new one. The fast-path test failed before the fast path was added. ## Validation - `go test ./cl/persistence/... ./cl/antiquary/... -count=1` - `make lint`, two runs, 0 issues - `make erigon integration` ## Follow-ups (not in this PR) - `builders` gets its own per-index row format in #25231, so an in-place builder change no longer rewrites the whole list. - No end-to-end test drives the antiquary backoff through a stored corrupt row. - Copy-on-write of queue elements is a convention, not enforced by the type. (cherry picked from commit 21fc1c3)
…iff (#25231) Stacked on #25230 (which is stacked on #25212): review only the last two commits. ## Problem The state antiquary writes one `kv.Builders` row per processed Gloas block with `SSZQueueEncoder`. Builders is a registry, not a queue: deposits, withdrawals and exits change builders in place. With the overlap encoder from #25205, any change to a builder that is not the last one rewrites the whole list for that slot, about 97 bytes per builder. At 200 builders that is about 19 KB per block, roughly 46–51 GB a year. The format has to change before Gloas reaches Hoodi and mainnet, because after that old rows would need a migration. ## Fix `kv.Builders` gets a per-index diff format. Same table, same snapshot type. `kv.BuildersDump` and the other five queues are unchanged. Row layout, little-endian, not compressed: ``` 00 00 00 00 | 00 00 00 00 | 47 4c 4f 41 ("GLOA") | 01 (version) | 01 (kind) newLength u64 | count u32 | count × (index u64, 93-byte Builder SSZ) ``` - The total length must be exactly `26 + 101*count`. A block with no builder change writes a 26-byte row. With one change at 200 builders a row is 127 bytes, against 19404 with the queue encoder. - The writer, `BuildersDiffWriter`, keeps the pre-block builders as copied values. It writes every changed index plus the appended indices, in increasing order. The first Gloas row after the fork (empty baseline) carries the full list as appends. A shrink is an error. - The decoder, `ApplyBuildersDiff`, checks the whole row before it changes the list. It rejects: - a wrong preamble, version or kind; - a wrong exact length; - a length above the registry limit, or a shrink; - indices that are not strictly increasing, or not below the new length; - appends that are not contiguous; - a builder that does not decode. - `ReadBuildersSSZ` replays the rows on top of the dump, using the same dump rules as `ReadRequiredQueueSSZ` (#25212). A processed block (one with `kv.SlotData`) at or after the fork slot that has no row is an error. A missed slot is skipped. The Gloas genesis state writes a no-change row at its own slot, so it can be read when its slot is not a dump slot. - Every rejection wraps the new `ErrHistoryRebuildRequired`, and the antiquary backs off on it like it does for `ErrMissingGloasData`. The first 8 zero bytes are for old binaries. An older reader takes them as "truncate 0", then a zero-length builder, and fails decoding the empty builder before it changes the list. It does not mistake the row for a corrupt queue diff, so it does not back off and rewrite new rows in the old format. ## Upgrade note (for the release notes) Hoodi operators must upgrade before the Gloas fork on 2026-10-26. This release changes the on-disk format of Caplin historical state rows for builders (per-index diff) and builder pending payments (epoch base plus per-block deltas, #25234). - The Caplin state snapshot types keep their file version. - A node that reads an old-format row from its DB backs off and re-antiquates from before the Gloas fork. - An older release that reads a new-format row fails with a decode error and does not read wrong data. Downgrading after new rows were written is not supported. - #25205 must not ship without this format change. - Sepolia is not repaired. Rows already frozen in Caplin state snapshot files, including the published Sepolia range 11250000–11300000, stay in the old format. Snapshot files are read before the DB and frozen ranges are not regenerated, so historical state reads that replay those rows keep failing until the range is reset or republished. - The same applies to a node that passed a Gloas fork on an older release and has already frozen Gloas state snapshots. ## Tests - **Codec:** - round trip over: no change, first, middle or last change, several changes, index reuse, append, reuse plus append, empty to one; - a change to each single non-balance field; - exact byte layout; - the baseline is a value copy; - shrink is an error; - about 20 rejected rows, each with no mutation; - both legacy queue decoders, before and after #25205, fail on a new row without changing the list. - **Reader:** exact fork slot, missed fork slot, dump plus change plus append, a processed-block hole at the target and between the dump and the target, a missed slot, a pre-fork slot, and a legacy row (sentinel, with table and slot in the error). - **Antiquary:** - backoff on the sentinel; - collector → mdbx → reader round trip; - the hook across the Fulu → Gloas fork; - a Gloas genesis state at a dump slot (0) and at a slot that is not a dump slot (32): the genesis row is the 26-byte no-change row, and both the genesis state and the next block's change read back; - a seam test with real 1000-slot snapshot files, pruning, and reads that cross the file boundary and the snapshot/DB boundary, compared with the lists actually written. - **Benchmark:** `BenchmarkBuildersRowSize`. At 200 builders with one change per block, Initialize plus WriteDiff takes about 1.7 µs, 7 allocs and 127 bytes per row. TDD: the codec, reader and backoff tests were written first and failed for the intended reason, either on undefined symbols or with the backoff predicate returning false. The genesis test also failed first: at slot 32 with `historical state row needs a rebuild: missing row from table Builders at slot 32`, and at slot 0 because the genesis slot had no row (an empty value where a 26-byte row was expected). Some later tests, the append-end check, the hole after a dump, and the single-field changes, guard existing behaviour and passed on first run. Each one was checked to fail when its target branch is disabled. The snapshot file version is deliberately not bumped, so no Caplin snapshot republish is needed. The raw preamble tells old and new rows apart. ## Validation - `go test ./cl/persistence/... ./cl/antiquary/... ./db/snapshotsync/... -count=1`, and `-race` on `cl/antiquary` - `make lint`, two runs, 0 issues - `make erigon integration` - Dual review until converged: two review rounds, a fix-delta review and a fresh adversarial pass. The genesis-row commit had its own dual review, with no blocker. ## Follow-ups (not in this PR) - Gloas required-queue history is unreadable when every slot from the fork through the next dump slot is missed: no Gloas dump is written at that slot. This is pre-existing and affects all three Gloas queues. - `LatestExecutionPayloadBidTable` stores a redundant full bid per slot. It can be changed after the Hoodi fork. (cherry picked from commit 36e6501)
domiwei
requested review from
AskAlexSharov,
sudeepdino008 and
yperbasis
as code owners
October 22, 2026 21:49
AskAlexSharov
approved these changes
Oct 23, 2026
AskAlexSharov
deleted the
kewei/cherry-pick-gloas-history-to-release-3.8
branch
October 23, 2026 02:42
11 of 12 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Cherry-pick of #24667, #25212, #25201, #25211, #25230 and #25231 to release/3.8, in merge order. All six commits apply without changes.
CaplinStateSnapshots.BlocksAvailableno longer reports one slot past the shortest table.PtcWindowEpoch.kv.Buildersrows use a per-index diff format.#25201 is included because #25211, #25230 and #25231 depend on it in
cl/antiquary. #24667 is included because #25201's tests expect itsBlocksAvailablesemantics, and becausePtcWindowEpochadds a table whose files can end before the others.Release requirements
PtcWindowEpoch(cl/antiquary, cl/persistence, db: store the Gloas PTC window once per epoch #25201): a binary with this PR uses Caplin state snapshots only oncePtcWindowEpochfiles are published for the chain, becauseCaplinStateSnapshots.IndicesMaxis the minimum over all types. Generate and publish those files in the preverified lists before the release; until then, archive nodes on this release antiquate locally. The rollout order is in cl/antiquary, cl/persistence, db: store the Gloas PTC window once per epoch #25201.Checked on this branch
go testfor every package with changed Go files: pass.make lint: 0 issues.make erigon integration: builds.