# GP5 Start-from-Specific-Block Plan

## Goal
Enable GP5 (XDC go-ethereum fork) to start from a specific block number instead of genesis, specifically to validate and test the XDPoS V2 switch block (mainnet: 80,370,000 / apothem: 56,828,700).

## Current State Analysis

### How GP5 Currently Starts
1. `NewBlockChain()` in `core/blockchain.go:372` loads genesis and calls `loadLastState()`
2. `loadLastState()` reads `HeadBlockHash` from DB → sets current block
3. If state missing → rewinds to find valid state (genesis if nothing found)
4. Sync starts from `CurrentBlock()` via `syncWithPeerXDC()` in `eth/downloader/downloader_xdc.go:224`

### Key Code Paths
- **Chain init**: `core/blockchain.go:372-671` — `NewBlockChain()`
- **State loading**: `core/blockchain.go:464-466` — `loadLastState()`
- **State repair**: `core/blockchain.go:542-616` — XDC-specific rewind logic
- **Sync origin**: `eth/downloader/downloader_xdc.go:257-281` — `localHead = CurrentBlock().Number`
- **V2 switch**: `consensus/XDPoS/network_constants.go:60` — `TIPV2SwitchBlock: 80370000`
- **V2 validation**: `consensus/XDPoS/xdpos.go:411-415` — `IsV2Block()`

## Options to Start from Specific Block

### Option A: `debug_setHead` RPC (Immediate, No Code Change)
**Approach**: Start node normally, then call `debug_setHead(targetBlock)` via RPC.

**Implementation**:
```bash
# Start node with full sync
gp5 --syncmode full --datadir /data ...

# Wait for it to reach target block, then set head to V2 switch block
curl -X POST http://localhost:8545 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc":"2.0",
    "method":"debug_setHead",
    "params":["0x4C9D2F0"],
    "id":1
  }'
# 0x4C9D2F0 = 80,370,000 (mainnet V2 switch)
```

**Pros**:
- Zero code changes
- Uses existing `debug_setHead` → `SetHead()` → `setHeadBeyondRoot()` chain
- Already tested in production (used for chain repair)

**Cons**:
- Requires state at target block to exist (must sync there first)
- Destructive — rewinds chain, drops blocks after target
- Not useful for "start fresh at V2 switch" scenario

**Verdict**: Good for testing V2 behavior on existing synced nodes. Not for fresh-start validation.

---

### Option B: `--syncmode full --startblock N` CLI Flag (Recommended)
**Approach**: Add a new CLI flag that tells the downloader to start sync from a specific block, treating blocks before that as "already known".

**Implementation Plan**:

#### 1. Add CLI Flag (`cmd/utils/flags.go`)
```go
var (
    StartBlockFlag = &cli.Uint64Flag{
        Name:     "startblock",
        Usage:    "Start syncing from this block number instead of genesis (XDC testing only)",
        Category: flags.XDCCategory,
        Value:    0,
    }
)
```

#### 2. Store Flag in Config (`eth/ethconfig/config.go`)
```go
type Config struct {
    // ... existing fields ...
    StartBlock uint64 // XDC: start sync from this block (0 = genesis)
}
```

#### 3. Modify Downloader Origin (`eth/downloader/downloader_xdc.go:257-281`)
```go
func (d *Downloader) syncWithPeerXDC(p *peerConnection, hash common.Hash, td *big.Int) (err error) {
    // ... existing code ...
    localHead := d.blockchain.CurrentBlock().Number.Uint64()
    
    // XDC: If startblock is configured and we're below it, jump to it
    if startBlock := d.blockchain.Config().StartBlock; startBlock > 0 && localHead < startBlock {
        origin = startBlock
        log.Info("XDC: startblock configured, jumping sync origin", 
            "from", localHead, "to", startBlock)
    } else {
        // existing origin logic
        origin = localHead
    }
    // ... rest of sync setup ...
}
```

#### 4. Handle State Gap at Start Block
The critical issue: if we jump to block N, we need state at block N-1 to execute block N.

**Solution**: Use `debug_setHead` pattern internally — on first startup with `--startblock`:
1. Check if state exists at `startblock-1`
2. If not, trigger a "light sync" of headers+bodies from genesis to startblock (no state execution)
3. Or: require a snapshot/snapshot-at-block to be provided alongside `--startblock`

**Better Solution**: XDC-specific — since XDC bulk sync skips tx execution for non-checkpoint blocks, we can:
1. Sync headers from genesis to startblock (fast, just validate headers)
2. At startblock, verify it's a checkpoint block (multiple of 900)
3. Execute only checkpoint blocks to build state
4. Start normal sync from startblock

#### 5. XDC-Specific Checkpoint-Aware Sync
```go
// In eth/downloader/downloader_xdc.go
func (d *Downloader) syncWithPeerXDC(...) {
    startBlock := d.config.StartBlock // from CLI
    if startBlock > 0 {
        // Ensure startBlock is at checkpoint boundary for state availability
        if startBlock % 900 != 0 {
            startBlock = (startBlock / 900) * 900
            log.Warn("XDC: startblock adjusted to checkpoint boundary", "adjusted", startBlock)
        }
        
        // Phase 1: Fast header sync from genesis to startBlock (no state)
        if localHead < startBlock {
            d.fastHeaderSyncToStartBlock(p, localHead, startBlock)
        }
        
        // Phase 2: Normal sync from startBlock
        origin = startBlock
    }
}
```

**Pros**:
- Clean CLI interface for testing
- Can target exact V2 switch block
- Reuses existing sync infrastructure
- Checkpoint alignment ensures state availability

**Cons**:
- Requires code changes across multiple packages
- State gap handling is complex
- Need to handle V1→V2 transition correctly at switch block

**Verdict**: Best long-term solution. Requires ~2-3 days implementation.

---

### Option C: Snapshot Import at Target Block (Fastest for Testing)
**Approach**: Use geth's existing snapshot mechanism. Create a snapshot at the V2 switch block from a fully synced node, then import it into a fresh node.

**Implementation**:

#### 1. On Source Node (Fully Synced)
```bash
# Export chain segment around V2 switch
gp5 export --datadir /data chain_export.rlp 80369100 80370900

# Or use snapshot commands
# gp5 snapshot --datadir /data ...
```

#### 2. Create Custom Genesis at V2 Switch Block
Create a new genesis config where:
- `genesis.number = 80370000`
- `genesis.alloc = state at block 80369999`
- `chainConfig.XDPoS.V2.SwitchBlock = 0` (already switched)

#### 3. On Target Node (Fresh)
```bash
# Init with custom genesis
gp5 init --datadir /data v2_switch_genesis.json

# Import blocks after switch
gp5 import --datadir /data chain_export.rlp
```

**Pros**:
- Uses existing `init` + `import` commands (no new code)
- Very fast — skip 80M blocks of sync
- Perfect for isolated V2 testing

**Cons**:
- Need to extract state at block 80369999 (complex)
- Custom genesis is a one-off artifact
- Doesn't test the actual V1→V2 transition path
- Not suitable for production use

**Verdict**: Good for CI/testing environments. Not for validating the actual switch.

---

### Option D: `--syncmode "verify"` — Header-Only Sync to Target, Then Full Sync (Best for Validation)
**Approach**: New sync mode that:
1. Downloads headers from genesis to target block (fast, no state)
2. Validates header chain integrity (including V1→V2 transition)
3. Starts full sync from target block with state execution

**Implementation**:

#### 1. New Sync Mode (`eth/ethconfig/config.go`)
```go
const (
    FullSync = iota
    SnapSync
    VerifySync // XDC: header-only sync to validate chain, then full sync from target
)
```

#### 2. Downloader Logic (`eth/downloader/downloader_xdc.go`)
```go
func (d *Downloader) syncWithPeerXDC(...) {
    targetBlock := d.config.VerifyTargetBlock // e.g., 80370000
    
    if targetBlock > 0 && localHead < targetBlock {
        // Phase 1: Header-only sync to target
        log.Info("XDC verify sync: phase 1 — headers to target", "target", targetBlock)
        if err := d.verifyHeaderChainToBlock(p, targetBlock); err != nil {
            return err
        }
        
        // Phase 2: Full sync from target
        log.Info("XDC verify sync: phase 2 — full sync from target")
        origin = targetBlock
    }
}
```

#### 3. Header-Only Validation
```go
func (d *Downloader) verifyHeaderChainToBlock(p *peerConnection, target uint64) error {
    // Fetch headers in batches
    // Verify each header's parent hash links
    // Verify XDPoS V1 signatures for pre-switch blocks
    // Verify XDPoS V2 QC for post-switch blocks
    // DO NOT execute transactions or build state
    
    for from := uint64(1); from < target; {
        batch := min(MaxHeaderFetch, target-from)
        headers, err := d.requestHeadersByNumberXDC(p, from, batch, 0, false)
        if err != nil {
            return err
        }
        
        for _, h := range headers {
            // Verify parent link
            parent := d.blockchain.GetHeaderByHash(h.ParentHash)
            if parent == nil && h.Number.Uint64() > 1 {
                return fmt.Errorf("missing parent for block %d", h.Number)
            }
            
            // Verify XDPoS consensus
            if err := d.engine.VerifyHeader(d.blockchain, h); err != nil {
                return fmt.Errorf("invalid header %d: %w", h.Number, err)
            }
        }
        
        from += uint64(len(headers))
    }
    return nil
}
```

**Pros**:
- Specifically designed for V2 switch validation
- Fast — no state computation for 80M blocks
- Validates actual V1→V2 transition
- Can detect consensus issues before switch block

**Cons**:
- New sync mode = significant code addition
- Header-only validation doesn't catch state-related bugs
- Still need state at target block to continue

**Verdict**: Best for the specific goal of V2 switch validation.

---

## Recommended Implementation: Hybrid Approach

Combine **Option B** (startblock flag) + **Option D** (header verify phase):

### Phase 1: Add `--startblock` Flag (1 day)
1. `cmd/utils/flags.go`: Add `StartBlockFlag`
2. `eth/ethconfig/config.go`: Add `StartBlock uint64`
3. `eth/downloader/downloader_xdc.go`: Use startblock as origin

### Phase 2: Header Pre-Validation (1-2 days)
1. Add `--verify-headers` flag (bool)
2. If set, download headers from genesis to startblock
3. Verify parent links + XDPoS signatures
4. Store verified headers in DB (no state)

### Phase 3: State Bootstrap at Checkpoint (1 day)
1. Ensure startblock aligns to checkpoint (multiple of 900)
2. At checkpoint, execute block fully to create state
3. Continue normal sync from startblock

### Phase 4: V2-Specific Testing (ongoing)
1. Test with startblock = V2 switch - 900 (pre-switch checkpoint)
2. Test with startblock = V2 switch (the switch block itself)
3. Test with startblock = V2 switch + 1 (first post-switch block)
4. Compare behavior against v2.6.8 reference node

## Files to Modify

| File | Change |
|------|--------|
| `cmd/utils/flags.go` | Add `StartBlockFlag`, `VerifyHeadersFlag` |
| `eth/ethconfig/config.go` | Add `StartBlock`, `VerifyTargetBlock` to Config |
| `eth/backend.go` | Pass startblock config to downloader |
| `eth/downloader/downloader_xdc.go` | Use startblock as origin; add header verify phase |
| `eth/downloader/queue.go` | Handle header-only mode (no bodies/receipts) |
| `core/blockchain.go` | Allow inserting headers without state for verify mode |
| `consensus/XDPoS/xdpos.go` | Ensure V1/V2 routing works with partial header chain |

## Testing Plan

1. **Apothem Testnet First** (lower block count, V2 at 56,828,700)
   ```bash
   gp5 --network apothem --syncmode full --startblock 56828700 --verify-headers
   ```

2. **Validate Header Chain**
   - Check all headers from genesis to 56,828,700 are linked
   - Verify V1 signatures valid
   - Verify V2 switch block has round=0
   - Verify V2 QC present post-switch

3. **Validate State at Switch Block**
   - Compare state root with v2.6.8 reference
   - Compare masternode list
   - Verify snapshot integrity

4. **Mainnet Dry Run**
   - Same process at 80,370,000
   - Run on test server (xdc07) before production

## Risks & Mitigations

| Risk | Mitigation |
|------|-----------|
| State missing at startblock | Align to checkpoint (900 boundary); execute checkpoint block |
| V1→V2 transition bugs | Test on Apothem first; compare with v2.6.8 |
| Peer rejects sync from mid-chain | Use `findAncestorXDC` with startblock as floor |
| Header validation too slow | Parallel header fetch (already in v14) |
| DB corruption on restart | Store `startblock` flag in DB; resume from known point |

## Next Steps

1. **Immediate** (today): Create branch `feature/startblock-sync`
2. **Day 1**: Implement `--startblock` flag + config plumbing
3. **Day 2**: Modify `syncWithPeerXDC` to use startblock as origin
4. **Day 3**: Add `--verify-headers` pre-sync phase
5. **Day 4**: Test on Apothem devnet
6. **Day 5**: Test on mainnet snapshot node

## Alternative: Quick Win with Existing Tools

If you need to test V2 switch **today** without code changes:

```bash
# 1. Start GP5 with a v2.6.8 datadir snapshot at block 80,369,900
# 2. It will sync the last 100 blocks to reach switch
# 3. Use debug_setHead to rewind to 80,370,000 after sync

# Or: Use the existing import/export
# Export from v2.6.8 at block 80,369,999, import into GP5
```

This validates V2 execution without implementing new sync modes.
