# Consensus Parity Audit v3 — XDC 2.6.8 vs GP5 (Sync + Mining)

**Date:** 2026-04-25
**Patient HEAD:** `ace90b251` (HOTFIX: Re-apply C1.1 walk-back guard and variable shadowing fixes) on `origin/xdc-network`
**Reference:** `XinFinOrg/XDPoSChain` (XDC 2.6.8) at `/Users/anilchinchawale/github/XDCNetwork/XDPoSChain`
**Audience:** XDC core team, future maintainers, network operators
**Supersedes:** `CONSENSUS_PARITY_AUDIT_v2.md`, `GP5_V268_PHASE_AUDIT.md`, `V2_SWITCH_PARITY_REPORT.md`, `gp5_consensus_review.md`

**Detail appendices:**
- Read-side / Phases 1-8: `.audit-v3-drafts/part1_phases_1-8.md` (47 KB)
- Mining-side / Phases 9-12: `.audit-v3-drafts/part2_phases_9-12.md`
- Validation Stages 5-6: `.audit-v3-drafts/playbook_extensions.md`
- Master prompt: `DEEP_RESEARCH_PROMPT_V2_SWITCH_AND_MINTING.md`

---

## Executive summary

**Two lead questions, two different answers.**

> **Q1: If we ran GP5 on Apothem from `V2.SwitchBlock - Gap` today as a sync peer, would it agree with v2.6.8 on every block?**

**Yes for 99% of blocks, with three known operational footguns.** The read-side hash equivalence is in place: sigHash, ecrecover, RLP layout, gas-limit bound, snapshot extraction at the V1→V2 switch, HookPenalty, HookReward, and epoch-switch detection all match v2.6.8 byte-for-byte under steady-state sync. Of the eight major findings raised in the previous audit (NF-0, NF-1, NF-2, NF-3, C1, C2, C5, C-comeback / C13, C14), **all but one (NF-1 — partial)** are now verified fixed at HEAD. Three operational issues remain: a regressed walk-back guard from a botched merge (C1.1), a regressed concurrency dedup that PR #423 was meant to land (C12), and a newly-discovered soundness gap in the bulk-sync masternode-mismatch fallback. None of the three break consensus equivalence at steady-state; they cause stalls or accept malformed-during-bulk-sync blocks that are still self-consistent on signatures.

> **Q2: If we ran GP5 on Apothem as a registered miner today, would it produce blocks v2.6.8 peers accept?**

**No, decisively, on three independent grounds. Mining-side parity is not partially broken — it is structurally absent.** The mining-side audit (Part 2, Phases 9-12) finds that:

1. **Wrapper dispatch is missing for every mining-side method.** Of the seven wrapper methods (`Prepare`, `Finalize`, `FinalizeAndAssemble`, `Seal`, `CalcDifficulty`, `SealHash`, `Authorize`), only `Finalize` has a partial dispatch (reward branch only); the other six route V2 blocks through V1 logic. The V2 engine ships complete implementations of these methods (`engine_v2/engine.go:476-650`) that are unreachable from the wrapper. A V2 block GP5 prepares will have V1 `Extra` layout, V1 difficulty (Hop formula instead of constant 1), and signatures stuffed into trailing `Extra` bytes instead of `header.Validator`. v2.6.8 peers reject before signature verification.

2. **BFT message wire is severed in both directions.** Inbound `VotePacket` / `TimeoutPacket` / `SyncInfoPacket` handlers in `eth/handler_eth.go:171-182` are stubs (`return nil`); outbound broadcasts in `eth/handler_xdc.go:15-62` are stubs (`_ = p // TODO`). GP5 cannot collect votes from peers (so it cannot form QCs); it cannot send votes to peers (so v2.6.8 nodes cannot aggregate GP5's signatures into QCs for blocks GP5 produces). GP5 is invisible on the BFT channel.

3. **Worker pipeline does not call `engine.Seal`.** Patient inherited geth 1.17's miner package, which has no `engine.Seal` invocation anywhere in `miner/*.go` (verified by grep). v2.6.8 has `miner/agent.go:103` `ca.engine.Seal(...)` directly. Geth 1.17 delegates sealing to an external block-producer driver (engine API for PoS); for XDPoS, that driver doesn't exist. So even if dispatches were correct, GP5 cannot drive a V2 block to seal.

The deployment implication is clean: **GP5 today is safe ONLY as a non-mining sync peer.** It cannot be registered as an Apothem masternode; if it were, the network would stall every time GP5 was the round leader (3-chain commit fails because GP5's blocks are malformed and its votes are never broadcast).

### The minimal change set

Restoring mining requires four independent workstreams:

1. **Phase 9 — wrapper dispatch (M-1 through M-7)**: dispatch `Prepare`, `FinalizeAndAssemble`, `Seal`, `CalcDifficulty`, `SealHash`, `Authorize` to the V2 engine. Widen `EngineV2Iface` to expose these methods. Single-PR scope; ~1-2 days of careful work.
2. **Phase 11 — sealing trigger pathway (F-11-1)**: introduce a v2.6.8-style miner agent (`miner/xdc_agent.go`) that watches for "I am the round leader" events from `engine_v2.OnNewRoundEvent` and drives `Prepare → FinalizeAndAssemble → Seal`. Structural port; multi-PR scope.
3. **Phase 12 — BFT message handlers (W-1 through W-6)**: wire inbound and outbound BFT messages through a `Bfter` analogue (port from `XDPoSChain/eth/bft/bft_handler.go`). Connect `EngineV2.VoteHandler / TimeoutHandler / SyncInfoHandler` to `eth/handler_eth.go`; implement `BroadcastVote / BroadcastTimeout / BroadcastSyncInfo` to call `p2p.Send`. Multi-PR scope.
4. **Pre-flight (CC-1)**: replace the placeholder Apothem genesis hash at `params/config.go:40` with the real value `0xbdea512b4f12ff1135ec92c00dc047ffb93890c2ea1aa0eefe9b013d80640075`. Single-line fix but blocks all peering with Apothem.

Plus the three sync-side operational fixes:
- **C1.1 regression**: re-apply `>` → `>=` at `engine.go:366` (the HOTFIX `ace90b251` flipped it back).
- **C12 regression**: re-apply PR #423's `singleflight.Group` deduplication for `getEpochSwitchInfo`.
- **Bulk-sync soundness gap**: remove the `if len(parents) > 0` fallback in `engine_v2/verifyHeader.go:152-162` that skips `CompareSignersLists`.

After all of the above lands, the validation plan is to run Stage 5 (mining shadow on a 4-validator private testnet) and Stage 6 (mixed-peer canary on Apothem) per `VALIDATION_PLAYBOOK_v2.md`.

### Estimated total effort

- **Sync-side operational fixes (C1.1, C12, bulk-sync gap):** half a day.
- **Mining wrapper dispatch (M-1 through M-7 + interface widening):** 2-3 days.
- **Sealing trigger pathway:** 3-5 days (structural, requires miner-package work).
- **BFT message wiring (Bfter port + 6 handler/broadcast functions):** 2-3 days.
- **Validation Stage 5 setup + harness:** 2-3 days (includes the `debug_minePinned` test RPC).
- **Validation Stage 6 canary on Apothem:** 1-2 weeks of observation under multiple deployment phases.

Total: ~3-4 weeks of focused engineering plus 2 weeks of canary observation before GP5 can be safely deployed as a registered Apothem miner.

---

## Section A — Status of all known findings

### Verified fixed at HEAD `ace90b251`

| Finding | Closing commit / origin | Patient cite | Reference cite |
|---|---|---|---|
| NF-0 / C10 — Author dispatch + V2 17-field sigHash | PR #420 (`8d63bed27`) | `xdpos.go:394-401` + `engine_v2/engine.go:225-256` | `XDPoS.go:193-200` + `utils.go:20-49` |
| NF-2 / C7 — HookPenalty epoch boundary via `IsEpochSwitch` | live path | `eth/backend.go:329-336` | `eth/hooks/engine_v2_hooks.go:54-60` |
| NF-3 / C8 — preMasternodes via `GetMasternodesByHash` no fallback | live path | `eth/backend.go:348` | `eth/hooks/engine_v2_hooks.go:75` |
| C1 — cold-restore walk-back present | PR #411 | `engine_v2/engine.go:362-385` | n/a (patient-only) |
| C2 — `currentSnap` LRU race | commit `7277ad8c9` | `engine.go:774-870` (no seed) | n/a |
| C5 — gas-limit 1/1024 | commit `c45ecd863` | `verifyHeader.go:64-71` | `consensus/misc/gaslimit.go:33` |
| C13 — comeback iteration order matches v2.6.8 | live path | `eth/backend.go:366-374` | `eth/hooks/engine_v2_hooks.go:95-104` |
| C14 — V1 pre-switch header guard | landed | `engine_v2/engine.go:886-894` | n/a (defensive, no upstream) |
| #391 nil checks in IsEpochSwitch / getExtraFieldsNoChain | commit `1d6f5bbfd` | `engine.go:951-958`, `engine.go:1088-1090` | n/a (defensive) |
| #395 Validators-vs-Extra at switch block | landed | `engine_v2/engine.go:875-906`, `engine.go:1149-1156` | `utils.go:181` |
| #418 / #421 forward-compat snapshot | landed | `IsValidForV2Switch` has no callsite — dead code is harmless | n/a |
| #385 Version gate accepts v2.6.8 snapshots | commit `576871c11` | `getSnapshot` accepts version 0/1/2/3 | n/a |
| C11 — Finalize wiring exposed in interface | commit `00244609d` (PR #423) | `EngineV2Iface` exposes `Finalize`; **no caller uses it through the interface — dead code** | n/a |

### Open at HEAD (regressions or partial fixes)

| Finding | Status | Patient cite | Severity |
|---|---|---|---|
| **C1.1 — walk-back guard `>=`** | **REGRESSED** by HOTFIX `ace90b251` (flipped back to `>`) | `engine_v2/engine.go:366` | Sync stall on cold-restore at SwitchBlock |
| **C12 — singleflight dedup for getEpochSwitchInfo** | **REGRESSED** in conflict resolution at HOTFIX | `engine_v2/engine.go:1217-1229` (old error-on-recursion guard) | Transient verification errors under concurrent sync |
| NF-1 / C6 — Finalize dispatch (full delegation) | PARTIALLY FIXED | wrapper still runs body in-place; only reward branch dispatches | Architectural; no consensus impact in steady state |
| **Bulk-sync masternode-mismatch fallback (NEW)** | **NEWLY DISCOVERED** | `verifyHeader.go:152-162` skips `CompareSignersLists` when `len(parents) > 0` | Soundness gap during bulk sync |

### Mining-side findings (all P0 if mining is wanted)

| ID | Finding | Patient cite | Class |
|---|---|---|---|
| M-1 | `Prepare` doesn't dispatch — V2 blocks get V1 Extra layout, V1 difficulty | `xdpos.go:918-1021` | bug |
| M-2 | `Finalize` partial — only reward branch dispatches | `xdpos.go:1024-1109` | bug (matches NF-1) |
| M-3 | `FinalizeAndAssemble` V1 reward gate — uses `number%rCheckpoint` not `IsEpochSwitch` | `xdpos.go:1112-1141` | bug |
| M-4 | `Seal` signs V1 15-field sigHash, writes signature to wrong field | `xdpos.go:1144-1228` | bug |
| M-5 | `CalcDifficulty` returns V1 hop formula for V2 blocks | `xdpos.go:1231-1241` | bug |
| M-6 | `SealHash` returns V1 layout; includes geth-1.17 fields v2.6.8 doesn't | `xdpos.go:1244-1246` | bug |
| M-7 | `Authorize` doesn't propagate signer to V2 engine | `xdpos.go:1263-1268` | bug |
| W-1, W-2, W-3 | Inbound BFT handlers are `return nil` stubs | `eth/handler_eth.go:171-182` | bug |
| W-4, W-5, W-6 | Outbound BFT broadcasts are `_ = p // TODO` stubs | `eth/handler_xdc.go:15-62` | bug |
| W-9 | Apothem genesis hash placeholder | `params/config.go:40` | bug |
| F-11-1 | No sealing trigger pathway — `engine.Seal` never called | `miner/*.go` (none call Seal) | structural bug |

### Cleanup / cross-cutting

| ID | Issue | Patient cite | Action |
|---|---|---|---|
| CC-1 | Apothem genesis hash placeholder | `params/config.go:40` | replace with real hash |
| CC-2 | `EngineV2Iface` too narrow for mining | `xdpos.go:323-333` | widen interface |
| CC-3 | `parent.Number == V2.SwitchBlock` boundary trap in CalcDifficulty dispatch | dispatch sites | use `parent.Number >= V2.SwitchBlock` |
| CC-4 | Dead duplicate HookPenalty in `eth/hooks/engine_v2_hooks.go` | lines 31-238 | delete to remove confusion |
| CC-5 | `getExtraFields` switch-block branch comment vs code mismatch | `engine.go:1149-1156` | match reference exactly (use `decodeMasternodesFromHeaderExtra` directly) |
| CC-6 | `GetSigningTxCount` range-based vs IsEpochSwitch-based iteration | `eth/hooks/engine_v2_hooks.go:333-414` | port reference's IsEpochSwitch walk-back |

---

## Section B — Critical findings (one-paragraph dossier)

Read the appendices for the full per-finding evidence with `path:line` citations in both repos. These are the headline items.

### CRITICAL: Mining is structurally absent (M-1 through M-7, W-1 through W-6, F-11-1)

GP5 inherited geth 1.17's payload-builder miner pipeline, which delegates sealing to an external driver (engine API for PoS). For XDPoS, that driver was never implemented. The wrapper consensus engine has seven mining-side methods that don't dispatch to the V2 engine; the BFT message wire has six handlers/broadcasts that are `return nil` / `// TODO` stubs. Combined effect: GP5 cannot mine a V2 block, cannot vote on a peer's block, cannot timeout when a leader is silent, cannot send sync-info catches up. Registering GP5 as a masternode on Apothem today would stall the chain every time GP5 was the round leader.

### HIGH: C1.1 walk-back guard regressed by HOTFIX (`ace90b251`)

PR #411 correctly fixed `engine.go:366` from `>` to `>=`. The HOTFIX `ace90b251` (despite its commit message claiming "uses > not >=") flipped it back to `>`. Cold-restore that lands exactly at `SwitchBlock - 1` and receives `SwitchBlock` as the next sync block will stall — `chain.GetHeaderByNumber(SwitchBlock)` returns nil during the brief window before the block commits, the `>` guard skips the walk-back, `checkpointHeader` stays nil, and `initial()` returns `checkpoint header N not found`. The fix is one character: re-apply `>=`.

### HIGH: C12 singleflight dedup regressed in same merge

PR #423 (`00244609d`) introduced `singleflight.Group`-based deduplication for `getEpochSwitchInfo` to fix concurrent-verify errors. The merge `64e064b64` lost the singleflight code in conflict resolution; the HOTFIX re-applied other PR #423 fixes but did not re-apply singleflight. Current HEAD has the older error-on-recursion guard at `engine.go:1217-1229` that returns `recursive getEpochSwitchInfo call detected` when two goroutines hit the same hash. Fix: re-apply the singleflight pattern.

### HIGH: Bulk-sync soundness gap (NEW)

`engine_v2/verifyHeader.go:152-162`: when `calcMasternodes` fails AND we have parents (bulk sync), the patient falls back to `GetMasternodesWithParents` and **skips** the `CompareSignersLists` integrity check on `Validators` and `Penalties`. v2.6.8 returns the error unconditionally (`verifyHeader.go:135-140`). A malicious peer could feed bogus `Validators` lists during bulk sync; the signature check still runs (so the immediate proposer is verified) but the masternode-list integrity is not. Once persisted, downstream reward calculations would use the bogus list. Fix: remove the `if len(parents) > 0` fallback or move `CompareSignersLists` outside the `else` so it runs unconditionally.

### MEDIUM: NF-1 / C6 partial dispatch — interface extension is dead code

PR #423 added `Finalize` to `EngineV2Iface` but no caller invokes it through the interface. The wrapper's `Finalize` (`xdpos.go:1024-1109`) still runs reward+masternode logic in-place. Net effect: dead interface entry, no behavioral change vs prior. The reward branch dispatch via `IsV2Block` + `IsEpochSwitch` is correct for the V2 reward path, but full delegation through the interface remains deferred. Severity is medium because state root is structurally identical to reference (rewards apply via the dispatched branch) on healthy chains; but `FinalizeAndAssemble` (M-3) has a related V1 reward gate bug that DOES diverge state roots.

### Cross-cutting: Apothem genesis hash placeholder (CC-1)

`params/config.go:40` defines `XDCApothemGenesisHash` as a repeating-pattern placeholder `0x8b8d5d60e7b9a4b7b5e0b0d5e0b5c0e0b5a0b5e0b5e0b5e0b5e0b5e0b5e0b5e0` instead of the real Apothem genesis `0xbdea512b4f12ff1135ec92c00dc047ffb93890c2ea1aa0eefe9b013d80640075`. This blocks every Apothem peer at handshake (`errGenesisMismatch`). Single-line fix; pre-flight blocker for both Stage 5 (testnet) and Stage 6 (mainnet canary).

---

## Section C — Wire format and byte-layout audit

### V2 sigHash (Phase 2, mining and sync)

**Verified byte-identical** when invoked through the V2 engine. Both encode 17 fields (ParentHash, UncleHash, Coinbase, Root, TxHash, ReceiptHash, Bloom, Difficulty, Number, GasLimit, GasUsed, Time, Extra, MixDigest, Nonce, Validators, Penalties) plus optional BaseFee. Patient `engine_v2/engine.go:225-256`, reference `engine_v2/utils.go:20-49`. `header.Time` type difference (uint64 vs *big.Int) RLP-encodes identically.

**Wrapper-level `sigHash` (`xdpos.go:185-245`) is V1 15-field** and **includes geth-1.17-introduced fields** (`BaseFee`, `WithdrawalsHash`, `BlobGasUsed`, `ExcessBlobGas`, `ParentBeaconRoot`) that v2.6.8 doesn't have. This is the hash returned by wrapper-level `SealHash` and used in V1 ecrecover. For V2 blocks, `Author` correctly dispatches around the wrapper's hash; for V2 mining, `Seal` does NOT dispatch and uses this wrong hash to sign. M-4 + M-6 detail.

### Snapshot serialization (Phase 4)

**Forward/backward compatible.** Reference `SnapshotV2` has 3 fields {Number, Hash, NextEpochCandidates `json:"masterNodes"`}. Patient adds `Version uint64 json:"version,omitempty"`. v2.6.8 reading patient snapshots: ignores unknown `version` field. Patient reading v2.6.8 snapshots: gets `Version=0` which the load path accepts. No interop break.

DB key prefix: both `[]byte("XDPoS-V2-") + hash[:]`. Identical.

### BFT messages (Phase 12)

Protocol codes aligned: `VoteMsg=0xe0`, `TimeoutMsg=0xe1`, `SyncInfoMsg=0xe2`, version `xdpos2/100`. Type structs (`Vote`, `Timeout`, `SyncInfo`, `QuorumCert`, `TimeoutCert`, `BlockInfo`, `ExtraFields_v2`) — **byte-by-byte RLP equivalence is INVESTIGATE**. The most likely place for silent divergence is field-order changes between geth-1.17 patient `core/types/` and v2.6.8 `core/types/`. Resolution requires fixture-based RLP encode + diff (Open Question 6).

`OrderTxMsg = 0x08` and `LendingTxMsg = 0x09` defined in v2.6.8 (XDCx) but slot-collide with geth-1.17 `NewPooledTransactionHashesMsg` / `GetPooledTransactionsMsg`. Out-of-scope for V2-only deployments; flag for future XDCx integration.

---

## Section D — BFT loop trace comparison

Per the master prompt's Section D requirement. The intent is to drive a single round through both clients with synthetic inputs and assert each step matches.

**Status:** structurally complete in patient — round handling, vote handling, timeout handling, QC formation, TC formation, sync-info handling, 3-chain commit logic all present in `vote.go`, `timeout.go`, `utils.go`, `engine.go:1467` (`setNewRound`). Function bodies are line-for-line equivalent to v2.6.8 in the parts checked (Phase 10 / Part 2 details).

**However, the loop is unreachable.** Phase 12 W-1..W-6 stubs sever both the inbound (Vote/Timeout/SyncInfo packets dropped) and outbound (broadcasts not sent) BFT message paths. So although `OnVote`, `voteHandler`, `OnTimeout`, `processQC`, etc., would behave correctly given input, no input ever reaches them in production.

**Fixing the BFT trace requires:**
1. Phase 12 wire-up (W-1 through W-6) so messages flow.
2. Open Question 1 (vote-pool iteration order) — confirm QC `Signatures` slice order is deterministic; if not, two clients produce different QC bytes for identical vote sets.
3. Open Question 4 (`header.Coinbase` plumbing) — confirm `--miner.etherbase` aligns with `Authorize`-set signer.

**Forensics is dormant in BOTH** patient `forensics.go:95` and reference `forensics.go:90` (both start with `return nil`). Intentional in v2.6.8; mirror in patient. If v2.6.8 ever turns forensics on, GP5 must follow.

---

## Section E — Validation plan

Stages 1-4 (sync-side) are documented in the existing `VALIDATION_PLAYBOOK.md`. Stages 5-6 (mining-side) are documented in `VALIDATION_PLAYBOOK_v2.md` (this audit's companion deliverable, sourced from `.audit-v3-drafts/playbook_extensions.md`).

| Stage | Goal | Location | Required gate |
|---|---|---|---|
| 1 — Static replay | Sync hash equivalence over `[switchBlock-Gap, switchBlock+3*Epoch]` | `VALIDATION_PLAYBOOK.md` | C1.1 + C12 + bulk-sync gap fixed |
| 2 — Cold-restore | Cold-snapshot bootstrap doesn't stall at switch | `VALIDATION_PLAYBOOK.md` | C1.1 fixed |
| 3 — Live shadow | Mainnet read-only canary, ≥10K blocks zero diffs | `VALIDATION_PLAYBOOK.md` | CC-1 (genesis hash) fixed |
| 4 — Telemetry | Counter-based regression detection | `VALIDATION_PLAYBOOK.md` | All |
| **5 — Mining shadow** | **Two miners with same key produce byte-identical blocks** | `VALIDATION_PLAYBOOK_v2.md` | M-1..M-7 + F-11-1 + W-1..W-6 fixed |
| **6 — Mixed-peer canary** | **Apothem mainnet canary, GP5 mining accepted by v2.6.8 peers** | `VALIDATION_PLAYBOOK_v2.md` | All of the above + Stage 5 PASS |

Stage 5's deterministic-mining harness requires a new `debug_minePinned` test RPC added to both binaries under a build tag; see `.audit-v3-drafts/playbook_extensions.md` for the implementation sketch.

---

## Section F — Open questions (require runtime evidence)

These are claims the audit could not resolve from static code; they need controlled experiments to confirm or refute.

1. **Vote-pool iteration order** — `vote.go:241-286` iterates the vote pool (a Go map) to build QC `Signatures`. Map iteration is non-deterministic. Without sorting, two clients produce QCs with different signature orderings even for identical vote sets. **Test:** fixture with 5+ valid votes, encode QC on both binaries, compare bytes.

2. **Handshake forkID against Apothem peer** — geth 1.17's handshake exchanges `Status` + `ForkID`. Verify GP5's ForkID computation aligns with v2.6.8 across XDC's fork blocks. **Test:** boot GP5 against a known v2.6.8 Apothem peer; observe handshake outcome.

3. **Sealing-driver pathway** — geth 1.17's miner doesn't call `engine.Seal`. **Test:** implement `miner/xdc_agent.go` per F-11-1; verify it produces a block via the V2 engine.

4. **`header.Coinbase` plumbing** — does `--miner.etherbase` route to both `genParams.coinbase` (worker) and `Authorize`-set signer? **Test:** log both at boot; verify equality.

5. **`misc.VerifyGaslimit` enforcement on producer** — reference V2 engine calls in `Prepare` and `Finalize`. Patient does not. **Test:** log `header.GasLimit` over 100 produced blocks; check the 1/1024 bound is held.

6. **`types.Vote / Timeout / SyncInfo / QuorumCert / TimeoutCert / BlockInfo / ExtraFields_v2` RLP byte equivalence** — encode a fixture struct with both repos' types; compare bytes. Most likely aligned, but a single field-order regression breaks BFT.

7. **HookReward formula byte-equivalence** — patient's `XDPoS.CalculateRewardForSigner` and reference's `contracts.CalculateRewardForSigner` claim alignment. **Test:** fixture-based reward calculation comparison at a known checkpoint block.

8. **Tx-pool ordering** — geth 1.17's tx-pool implementation is different from v2.6.8's. Same input set may produce different tx orderings. **Test:** Stage 5 with body-byte comparison.

---

## Section G — Recommended action plan

### Week 1 — Stop-the-bleeding fixes (sync-side)

- C1.1 — re-apply `>=` at `engine.go:366`.
- C12 — re-apply singleflight dedup for `getEpochSwitchInfo`.
- Bulk-sync soundness — remove the `if len(parents) > 0` fallback or move `CompareSignersLists` outside.
- CC-1 — replace Apothem genesis hash placeholder.
- CC-4, CC-5, CC-6 — cleanup (delete dead HookPenalty copy, fix getExtraFields comment, port IsEpochSwitch-based GetSigningTxCount).

### Weeks 2-3 — Mining wrapper dispatch (Phase 9)

- Widen `EngineV2Iface` per CC-2.
- Implement M-1 (Prepare dispatch).
- Implement M-3 (FinalizeAndAssemble reward gate).
- Implement M-4 (Seal dispatch).
- Implement M-5 (CalcDifficulty dispatch with `>= V2.SwitchBlock` boundary per CC-3).
- Implement M-6 (SealHash dispatch + drop geth-1.17 conditional fields).
- Implement M-7 (Authorize propagation).
- Resolve M-2 by deciding whether to do the full Finalize delegation refactor or accept the partial dispatch.

### Weeks 3-4 — BFT message wiring (Phase 12)

- Port `XDPoSChain/eth/bft/bft_handler.go` to patient as `eth/bft/handler.go`.
- Wire W-1, W-2, W-3 — inbound packet handlers to `Bfter.Vote/Timeout/SyncInfo`.
- Wire W-4, W-5, W-6 — outbound broadcasts using `p2p.Send(peer.rw, msgCode, encodedBytes)`.
- Confirm `peer.SendVote/SendTimeout/SendSyncInfo` exists or add to peer type.

### Weeks 4-5 — Sealing trigger pathway (Phase 11)

- Implement `miner/xdc_agent.go` watching `engine_v2.OnNewRoundEvent`.
- Drive `Prepare → FinalizeAndAssemble → Seal` when GP5 is round leader.
- Verify `--miner.etherbase` ↔ `Authorize`-set signer alignment (Open Q 4).

### Week 5 — Validation Stage 5

- Build both binaries; deploy 4-validator private testnet per playbook §5.1-§5.3.
- Add `debug_minePinned` test RPC to both binaries.
- Run deterministic mining harness; require PASS=5/5.
- Run multi-round mining for ≥1 epoch; require zero rejected blocks.

### Weeks 6-7 — Validation Stage 6 (canary)

- Phase 6A read-only canary on Apothem for ≥24 hours; require zero divergences and zero peer drops.
- Phase 6B mining canary; require every GP5-produced block accepted by v2.6.8 peers.
- Document in canary report; sign off only if both phases pass.

---

## Section H — What changed since the previous audit

The earlier `CONSENSUS_PARITY_AUDIT_v2.md` and `GP5_V268_PHASE_AUDIT.md` claimed read-side parity was ~80% complete. **The verified-fixed list is now ~95% on the read-side** thanks to PRs #411 (C1.1 — but regressed by HOTFIX), #420 (NF-0/C10), #422 (C13/C14), and #423 (C11/C12 — but C12 regressed). The remaining sync-side issues are operational (C1.1, C12) or a soundness gap (bulk-sync fallback) — not consensus divergences in steady state.

The previous audits **did not cover mining-side**. The mining-side audit in this v3 reveals that mining was never wired up after the geth 1.17 port. This is a discovery, not a regression — the gap has existed since the start of the GP5 project. It is, however, the largest outstanding block on operational deployment of GP5 as a masternode.

The previous audits flagged NF-1 (Finalize dispatch) as partially fixed and architecturally incomplete. PR #423 was supposed to land the full delegation; the merge resolution lost C12 entirely and left C11 as a dead interface entry. The architectural cleanup remains deferred but is no longer the highest-priority issue — wrapper dispatch for mining methods (M-1..M-7) is.

---

## Sources

- Patient: `XDC-Geth` `xdc-network` HEAD `ace90b251`
- Reference: `XDPoSChain` master @ `146252a30` (XDC v2.6.8 lineage)
- Detail appendices: `.audit-v3-drafts/{part1_phases_1-8.md, part2_phases_9-12.md, playbook_extensions.md}`
- Prior audits cross-referenced (now superseded): `CONSENSUS_PARITY_AUDIT_v2.md`, `GP5_V268_PHASE_AUDIT.md`, `V2_SWITCH_PARITY_REPORT.md`, `gp5_consensus_review.md`, `OPUS_4.7_ANALYSIS_ISSUE_391.md`
- PR review batch: `.pr-reviews/batch_summary_2026-04-25.md`
- Master prompt: `DEEP_RESEARCH_PROMPT_V2_SWITCH_AND_MINTING.md`
- Validation playbook companion: `VALIDATION_PLAYBOOK_v2.md`
