I'll provide the validation in the chat. ## Validation Result — Proposed GP5 V2 Snapshot Fix **Verdict: Change #2 is the right fix; Change #1 needs guardrails; Change #3 already in place.** ### Architecture (Q1, Q2) The fix is sound. Reading candidates from `state.GetCandidates(statedb)` at the gap block is the same authority `UpdateMasternodesFromHeader` uses on the live import path (engine.go:1135-1171). That's also what Nethermind ultimately seeds from. A separate-consensus-DB rewrite (Nethermind-style) is the correct long-term architecture but a multi-week refactor — wrong tool for an emergency. ### Risks (Q3) 1. **State pruning** — `StateAt(gapHeader.Root)` fails on a pruned full-sync node. Current code at engine.go:841 silently falls through to the recursive path, which is precisely what produces the 13-candidate poisoning at the V1→V2 boundary. **This fall-through must be removed for V2 checkpoints.** 2. **13-candidate regression** — generalizing `repairSnapshot` without disabling the recursive branch re-introduces the bug the comment at engine.go:751-757 was written to prevent. 3. **Tie-break determinism** — equal-stake candidates need a secondary sort by address bytes; current sort (805-823) only orders by `stake.Cmp`. 4. **Empty contract** — treat `len(candidates)==0` at a V2 checkpoint as a hard error, not a fallback to `calcMasternodes`. 5. **Version gate** — already removed (lines 738-743, 862-870 only reject the exact V1 count of 13). Confirm no remaining `Version != X` checks elsewhere. ### Safest Fix (Q4) — minimal patch - Extract lines 800-833 into `repairFromContractState(chain, gapHeader)` — keyed on gap header, not checkpoint number; never recurses. - Replace engine.go:758-760 with a call to that helper; on success persist + cache; on err return a *clear* error naming the cause (pruned state / empty contract), not the recursive path. - Leave the recursive `repairSnapshot` body (845-927) alone — it still serves the V1 switch checkpoint branch. - For Apothem 56,830,293: first verify the node still has state at gap 56,829,150 (`debug_storageRangeAt` on the validator contract). If pruned, no patch recovers without resync. ### Pre-merge checks Add unit tests for (a) state present → contract-derived snapshot, (b) state pruned → explicit error (NOT a 13-candidate snapshot), (c) idempotent second call. Diff the recovered validator set against the live header at 56,830,293 before declaring un-stuck. I attempted to write this to `validation-result.txt` but lacked write permission — let me know if you'd like me to retry.