Bound PoS stall memory and lock signer startup - #52
Conversation
|
Context: I operate a bonded finalizer (rank 17, 91cbc5bd…) and rode through the 2026-07-26..28 stalls this PR targets. Data below is from my prod node plus a throwaway-seed heaptrack rig stood up during yesterday’s stall. Raw files available. The failure class is real and this PR bounds the right structures. This is the profile behind the Signal exchange; the follow-up patch (id + evidence only, bodies for current/locked/valid, older bodies from the PoS store) matches exactly what this data asks for. Question 1: the byte budget vs gap-scaled proposals. BftBlock.headers grows ~1.5KB per block of finality gap, so the one body a validator must still hold, the current round’s, reaches MAX_INFLIGHT_PROPOSAL_BYTES (32MB) by itself at a gap near ~21k blocks (~6 days at 25s spacing). When that happens, does reserve_proposal_storage reject its chunks, meaning a validator can no longer assemble and prevote the proposal that would end the stall? If the design is “evict everything else, always admit the current round,” a comment and a test pinning that would help; if not, deep-stall recovery may need a floor that always admits one current-round proposal. (Long-term, the gap scaling itself points at header-span compression the PoPoW? note on the field.) Question 2: window semantics under nil grinds. We saw 200+ nil rounds at one height. RETAIN_PAST_ROUND_WINDOW=64 plus the budget looks right for that regime; the soak couldn’t reach it yet (the branch exits at startup or early catch-up, findings below); I’ll confirm once the branch survives on real data. The harness behind the findings below: throwaway-seed rig built from consistent prod state snapshots, heaptrack wrapping, RSS-cap auto-stop, and 5-minute smaps telemetry on prod for A/B (yesterday’s stall is captured end to end). It stays standing; I’ll re-run each push and against the next real stall. Soak findings (head 873b2ea, real operator state, live workshop network). Rig: throwaway-seed node from a consistent prod state snapshot (PoW state version 27.1.0, accepted fine), heaptrack-wrapped, on the live network. Control for attribution: the same rig under the PR’s own base (f811d78, no PR commits). |
|
Thanks for running this against real operator state with a control build, that is exactly the harness this branch needed, and the heaptrack stacks match the paths the compaction targets ( Your three soak findings are the same three blockers disclosed in the PR description, and they share one root: the new strict rules have no authenticated boundary with the pre-rule prefix, and the disk replay and wire catch-up paths do not share a height-aware legacy verifier. Your suspicion on finding 3 is right as far as I can tell from the callsites: the same genesis-era empty-roster certificate that kills startup at byte 0 surfaces through catch-up as On question 1, your concern is right and the binding constraint is tighter than the 32 MiB math. This branch introduces both budgets: On question 2, agreed that the 64-round window plus the budget is the right shape for nil grinds; your confirmation once the branch survives on real data is exactly the evidence it needs. The startup and catch-up blockers gate that soak, so they go first. The PR stays draft until the boundary work is in, and I will take you up on the re-run offer on the next push. |
BFT stalls can retain catch-up state and push validators into high-memory restart loops.
This draft currently:
BftBlockpayloads while retaining proposal ids, vote evidence, counts, and roster datas1_devnew-network queue ownershipChecked on head
873b2eab7da63756b436aaf21b996eed4653e303:Blocking P0s:
DurableSignerstores full proposal, signable, and lock/valid transition payloads for every signed round until the height changes. Startup reads the complete WAL into memory, rebuilds the full intent map, and clones every intent for replay before round pruning. The round/cache compaction therefore does not prove bounded memory during a long stall, and restart peak memory can scale with WAL size.The operator A/B soak found both migration paths blocked:
pos.chainexits at byte 0 because its pre-roster certificate is rejected as an empty consensus rosterpos.chainremoved, fresh live-network catch-up exits after about 50 seconds withTenderlink terminated with an I/O error: invalid data, while basef811d789under the same state and network continues past cert 1,600The source audit corroborates the missing rule epoch: strict non-empty roster validation is applied during store replay and Tenderlink startup, with no anchored pre-rule boundary shared by disk and wire paths. The exact wire callsite still needs the offered logs, but the A/B regression and absent boundary are already merge blockers.
Closure needs one authenticated compatibility design, not a blanket local height bypass. Either validate the legacy prefix under exact legacy rules below a network-anchored checkpoint, or consume a sealed migration snapshot that binds the complete prefix, checkpoint identity, parent commit, roster, namespace, chain id, finality, and new-rule activation. Disk replay and wire catch-up must use the same height/epoch verifier. Strict rules must remain mandatory above the boundary.
Source-trace corrections:
BftBlockdoes not carry the full unfinalized header span. It carries exactlybc_confirmation_depth_sigma = 3headers, and the proposer limits each BFT step to at most 40 PoW heights. The 8 MiB per-proposal cap is therefore not shown to be a finality-gap-scaled liveness ceiling. A prior version of this body said otherwise; that claim is retracted.SignerStartup::Durableonly after the complete authority and structured receipt gate passes. Missing authority remains observer-only without creating the WAL or anchor. That earlier implementation concern is closed in source and focused tests, but a real migration is still unqualified.BftBlockper committed height ininternal.bft_blocks. That is not a stalled-round multiplier, but it is linear long-run history retention and must not be described as an overall constant memory bound.Additional operator compatibility gap:
validator_*key diagnostics, and a loud release note.Do not merge or deploy this draft. Closure still requires:
BftBlockchainDraft only. No live node change.