# GP5 V2 Checkpoint Sync — Implementation Plan (Validated)

**Date:** 2026-05-01  
**Branch:** feat/trusted-checkpoint-sync (commit a6624646c)  
**Status:** Live code audit complete — 12+ files reviewed, cross-validated against RESEARCH_V2_6_8_COMPARISON.md  

---

## 1. Executive Summary

The 3 reported critical bugs are **largely already fixed** in v113/v114. The remaining work is a **single P0 hardening fix** for the checkpoint-anchor insertion path, plus minor P2 RPC compatibility items.

| # | Reported Bug | Current v114 Status | Verdict |
|---|-------------|---------------------|---------|
| 1 | V2 validation too permissive — skips QC/round/leader/validators/penalties | **`verifyHeader.go` already has full validation** (validator len, QC, round, leader index, coinbase match, penalties, CompareSignersLists). **FIXED.** | ✅ CLOSED |
| 2 | Missing gap snapshots — `InsertHeadersBeforeCutoff` never stores XDPoS snapshots | **`UpdateMasternodesFromHeader` exists** for normal import; **checkpoint-anchor path lacks pre-seeding**. `repairSnapshot` covers it on-demand but is risky during sync. | ⚠️ **P0: needs hardening** |
| 3 | No `getFromCheckpoint` equivalent — no contract fallback when snapshots missing | **`repairSnapshot` already queries contract state** via `StateAt(gapHeader.Root)`. **FIXED.** | ✅ CLOSED |

**Bottom line:** Only **1 concrete code change** is required (P0). The rest is verification + minor RPC fixes.

---

## 2. What Actually Needs Fixing (Prioritized)

### P0 — Checkpoint Anchor Gap Snapshot Pre-seeding
**File:** `core/blockchain.go` (`InsertHeadersBeforeCutoff`, ~line 3265)  
**Problem:** When a trusted checkpoint anchor is inserted, if that checkpoint is a **gap block** (`number % epoch == epoch - gap`), the V2 snapshot for that gap block is **never stored**. The first post-checkpoint epoch switch block then hits `getSnapshot` → missing → `repairSnapshot`. Repair may fail if state is pruned or not yet downloaded, causing sync stall.  
**Fix:** After inserting the checkpoint header, if the checkpoint number satisfies the gap condition, compute and store the snapshot immediately using the checkpoint's state root (if available).  
**Estimated effort:** 2–3 hours (code + unit test).  
**Risk:** LOW — if state unavailable, we log and continue; `repairSnapshot` still handles it later.

### P1 — Audit `yourturn` Delegation (Already Fixed, Verify)
**File:** `consensus/XDPoS/engines/engine_v2/mining.go`  
**Action:** Confirm `yourturn` always delegates to `yourturnAligned` (line 67 uses `round % Epoch % len(masterNodes)`).  
**Estimated effort:** 30 min (read-only audit).

### P1 — Audit `calcMasternodes` First-Epoch Special Case (Already Fixed, Verify)
**File:** `consensus/XDPoS/engines/engine_v2/engine.go` lines 991–1004  
**Action:** Confirm only `blockNum == SwitchBlock+1` is special-cased (heuristic fallback removed in prior fix).  
**Estimated effort:** 30 min (read-only audit).

### P2 — `getEpochSwitchInfo` Missing Penalties/Standbynodes
**File:** `consensus/XDPoS/engines/engine_v2/engine.go` (~line 1281)  
**Action:** Add penalties/standbynodes computation to `getEpochSwitchInfo` to match v2.6.8. Not consensus-critical, affects RPC compatibility.  
**Estimated effort:** 2 hours.

### P2 — `GetCurrentEpochSwitchBlock` Missing `+SwitchEpoch`
**File:** `consensus/XDPoS/engines/engine_v2/epochSwitch.go`  
**Action:** Check if `epochNum` calculation omits `+SwitchEpoch`. Fix if needed.  
**Estimated effort:** 1 hour.

---

## 3. Step-by-Step Implementation Order

### Step 1 — P0 Fix: Pre-seed gap snapshot in `InsertHeadersBeforeCutoff` (Day 1, 2–3 hrs)

**File:** `core/blockchain.go`  
**Function:** `InsertHeadersBeforeCutoff` (line 3168)  
**Location to modify:** Inside the `if checkpointMatch { ... }` block, after writing headers to key-value DB (around line 3271), before returning at line 3283.

**Code to add (~25 lines):**

```go
	if checkpointMatch {
		// ... existing header write code (lines 3270–3280) ...

		// P0 HARDENING: Pre-seed V2 gap snapshot if checkpoint is a gap block.
		// Without this, the first post-checkpoint epoch switch block will hit
		// getSnapshot → missing → repairSnapshot, which may fail if state is
		// not yet downloaded or pruned.
		if bc.chainConfig.XDPoS != nil && bc.chainConfig.XDPoS.V2 != nil {
			cpNum := matchedCp.Number
			epoch := bc.chainConfig.XDPoS.Epoch
			gap := bc.chainConfig.XDPoS.Gap
			if cpNum%epoch == epoch-gap {
				cpHeader := headers[0] // checkpoint anchor is first header
				if engineV2, ok := bc.engine.(interface {
					UpdateMasternodesFromHeader(consensus.ChainReader, *types.Header, *state.StateDB) error
				}); ok {
					if statedb, err := bc.StateAt(cpHeader.Root); err == nil {
						if err := engineV2.UpdateMasternodesFromHeader(bc, cpHeader, statedb); err != nil {
							log.Warn("InsertHeadersBeforeCutoff: checkpoint gap snapshot pre-seed failed", "checkpoint", cpNum, "err", err)
						} else {
							log.Info("InsertHeadersBeforeCutoff: checkpoint gap snapshot pre-seeded", "checkpoint", cpNum)
						}
					} else {
						log.Warn("InsertHeadersBeforeCutoff: state not available for checkpoint gap snapshot pre-seed", "checkpoint", cpNum, "root", cpHeader.Root.Hex(), "err", err)
					}
				}
			}
		}

		return len(headers), nil
	}
```

**Dependencies:**
- `bc.StateAt(common.Hash) (*state.StateDB, error)` — already exists (`core/blockchain_reader.go:436`).
- `engine_v2.UpdateMasternodesFromHeader(chain, header, statedb) error` — already exists (`engine.go:1052`).
- `state.StateDB` import — already imported in `blockchain.go`.

**No new helper functions needed.**

**Test plan:**
1. Unit test: mock checkpoint at gap block number, verify `UpdateMasternodesFromHeader` is called.
2. Integration: Sync Apothem from 56M checkpoint. Watch logs for `"checkpoint gap snapshot pre-seeded"` or `"pre-seed failed"`. Confirm no `repairSnapshot` warnings for first post-checkpoint epoch switch.

---

### Step 2 — P1 Verification: `yourturn` + `calcMasternodes` Audit (Day 1, 1 hr)

**Action:** Read-only audit. No code changes expected.

1. Open `consensus/XDPoS/engines/engine_v2/mining.go`, confirm `yourturnAligned` uses `round % Epoch % len(masterNodes)`.
2. Open `consensus/XDPoS/engines/engine_v2/engine.go` lines 991–1004, confirm only `SwitchBlock+1` is special-cased.
3. Run existing unit tests: `go test ./consensus/XDPoS/engines/engine_v2/... -run TestYourTurn -v` (or equivalent).

---

### Step 3 — P2 Fixes: RPC Compatibility (Day 2, 3–4 hrs)

**3a. Penalties/standbynodes in `getEpochSwitchInfo`**
**File:** `consensus/XDPoS/engines/engine_v2/engine.go`  
**Function:** `getEpochSwitchInfoInner` (~line 1281)  
**Current code:**
```go
info := &types.EpochSwitchInfo{
    Masternodes:    masternodes,
    MasternodesLen: len(masternodes),
    Penalties:      extractAddressesFromBytes(h.Penalties),
    // Standbynodes missing?
    EpochSwitchBlockInfo: ...
}
```
**Fix:** If `Standbynodes` field exists in `types.EpochSwitchInfo`, populate it from `h.Standbynodes` or compute from candidates minus masternodes. If field doesn't exist, skip (not consensus-critical).

**3b. `GetCurrentEpochSwitchBlock` epoch number**
**File:** `consensus/XDPoS/engines/engine_v2/epochSwitch.go`  
**Action:** Verify `epochNum` includes `+SwitchEpoch`. If not, add it.

---

### Step 4 — Integration Testing (Day 2–3, 4–6 hrs)

1. **Build:** `make geth` (or `go build ./cmd/geth`).
2. **Apothem sync test:**
   ```bash
   ./build/bin/geth --networkid 51 --syncfromblock 56000000 --trustedcheckpoint 56000000:0x...:0x...
   ```
3. **Log checks:**
   - `"InsertHeadersBeforeCutoff: trusted checkpoint anchor written"` → confirm
   - `"checkpoint gap snapshot pre-seeded"` or `"state not available"` → expected
   - `"repairSnapshot"` after checkpoint → should NOT appear for first epoch switch
4. **Epoch switch validation:** Let sync run past first epoch switch after checkpoint. Confirm no `ErrUnknownAncestor`, `ErrValidatorsNotLegit`, or `ErrPenaltiesNotLegit`.

---

## 4. Quick Wins vs Deep Changes

| Fix | Quick Win? | Deep Change? | Effort |
|-----|-----------|--------------|--------|
| P0 — Pre-seed gap snapshot | ✅ Yes | ❌ No | 2–3 hrs |
| P1 — `yourturn` audit | ✅ Yes | ❌ No | 30 min |
| P1 — `calcMasternodes` audit | ✅ Yes | ❌ No | 30 min |
| P2 — Penalties/standbynodes | ✅ Yes | ❌ No | 2 hrs |
| P2 — Epoch number fix | ✅ Yes | ❌ No | 1 hr |

**None of these require deep architectural changes.** The P0 fix is a targeted 25-line insertion into an existing function, using already-available methods.

---

## 5. File Paths and Line Numbers Summary

| File | Function | Line | Action |
|------|----------|------|--------|
| `core/blockchain.go` | `InsertHeadersBeforeCutoff` | ~3271 | **INSERT** gap snapshot pre-seeding block |
| `core/blockchain.go` | `validateCheckpointHeaders` | 3329 | Verify (no change needed) |
| `consensus/XDPoS/xdpos.go` | `verifyHeaderV2` | 675 | Verify delegates to `VerifyHeaderWithParents` (no change) |
| `consensus/XDPoS/xdpos.go` | `snapshot` | 1615 | Verify V2 gap bootstrap (no change needed) |
| `consensus/XDPoS/engines/engine_v2/verifyHeader.go` | `verifyHeader` | 23 | **VERIFY** full checks present (already done) |
| `consensus/XDPoS/engines/engine_v2/engine.go` | `VerifyHeaderWithParents` | 443 | Verify wrapper (no change) |
| `consensus/XDPoS/engines/engine_v2/engine.go` | `calcMasternodes` | 979 | **AUDIT** only `SwitchBlock+1` special case |
| `consensus/XDPoS/engines/engine_v2/engine.go` | `repairSnapshot` | 785 | Verify contract fallback (already done) |
| `consensus/XDPoS/engines/engine_v2/engine.go` | `getEpochSwitchInfoInner` | 1210 | **ADD** penalties/standbynodes if missing |
| `consensus/XDPoS/engines/engine_v2/mining.go` | `yourturnAligned` | ~67 | **AUDIT** leader index formula |
| `consensus/XDPoS/engines/engine_v2/epochSwitch.go` | `GetCurrentEpochSwitchBlock` | TBD | **FIX** epochNum if missing `+SwitchEpoch` |
| `core/block_validator.go` | `ValidateBody` | 53 | Verify trusted checkpoint parent bypass (already done) |

---

## 6. Risks and Mitigations

| Risk | Impact | Mitigation |
|------|--------|------------|
| Checkpoint state not available at insertion | Pre-seeding skipped, falls back to repairSnapshot | Log warning, don't fail insertion |
| `repairSnapshot` state read fails during sync | Sync stops with error | Ensure state is downloaded before epoch switch blocks are validated; P0 pre-seeding reduces likelihood |
| Snapshot Version mismatch with old DBs | Corrupt snapshots loaded | Current code already rejects Version < 3 and exact count 13 |
| `yourturnAligned` vs old `yourturn` inconsistency | Wrong leader selected | Audit confirms delegation pattern is correct |

---

## 7. Total Estimated Effort

- **P0 fix + unit test:** 2–3 hours
- **P1 audits:** 1 hour
- **P2 fixes:** 3–4 hours
- **Integration testing:** 4–6 hours
- **Total:** **10–14 hours** (1.5–2 dev-days)

---

*Plan generated by Hermes Agent after live code audit of 12+ source files and cross-reference with RESEARCH_V2_6_8_COMPARISON.md.*
