Skip to content

feat(warmboot): wire g_warm_boot_mismatch — detect a provably non-canonical tip#50

Merged
Eth-Interchained merged 1 commit into
mainfrom
hyperagent/nightly-2026-06-24-warmboot-mismatch
Jun 25, 2026
Merged

feat(warmboot): wire g_warm_boot_mismatch — detect a provably non-canonical tip#50
Eth-Interchained merged 1 commit into
mainfrom
hyperagent/nightly-2026-06-24-warmboot-mismatch

Conversation

@Eth-Interchained

Copy link
Copy Markdown
Owner

Wires g_warm_boot_mismatch — a flag that was declared, externed, and reset on every TryWarmBoot but never set anywhere. Its header comment ("reserved for active fork detection") described behavior that didn't exist, so a genuinely orphaned tip was indistinguishable from a merely slow/behind seed (both fell through the same 2-minute watchdog -> nedb_warmboot_unconfirmed -> full scan next start).

What it does now

In ProcessHeadersMessage, after pindexLast is accepted (under cs_main), detect the negative of the existing Proof-of-Prefix seam: if a peer presents a strictly-more-work valid header chain (pindexLast->nChainWork > g_warm_boot_tip_chainwork) whose block at our warm-boot tip height is a different hash (GetAncestor(tip_height) != our tip), then our tip is provably not on the most-work chain. Set g_warm_boot_mismatch, log distinctly, and persist nedb_warmboot_unconfirmed immediately (new WarmBootMarkUnconfirmed()), so the next controlled startup full-scans from 0 — instead of waiting out the 2-minute watchdog.

Why this matters

Distinguishes "provably wrong tip" (act now) from "unconfirmed / seed behind" (wait) — the gap GPT-5.5 flagged. Closes a dead defensive flag whose comment lied.

Conservative by construction

  • Strictly more work -> a weaker/equal fork never demotes our tip.
  • GetAncestor() null (peer chain not linked down to our tip height) -> no conclusion (absence of evidence stays the watchdog's job).
  • No live wipe/reorg — matches the watchdog doctrine (destructive action only at the controlled startup path). Acts on positive proof, not a timer.
  • False-positive cost = one slow, non-destructive full-scan boot; forging strictly-more valid PoW work is expensive.

Engine impact

None — itcd-side only; the NEDB engine repo is untouched.

Studio impact

None.

Tests run

None — no g++/cmake in the authoring sandbox; source-reviewed only. This is the network-anchoring path (not consensus validation). Please build on CI/iMac and review before merge. Suggested validation: a node warm-booted on an orphaned tip logs WarmBoot MISMATCH on the first strictly-more-work peer headers that fork below its tip height and sets the unconfirmed flag; a node on the canonical tip never trips it.

Follow-up

  • "Seed behind" grace (the companion fix): don't let the 2-minute watchdog demote an honest, merely-lagging seed.
  • Optional: pin the seam to the seed's peer identity / assert active-best-chain (GPT Q1).

Review-only — do not auto-merge.

© Interchained LLC × Claude

…onical tip

g_warm_boot_mismatch was declared, externed, and reset on every TryWarmBoot, but
NEVER set anywhere — a dead flag whose header comment ("reserved for active fork
detection") described behavior that did not exist. The warm-boot state machine
therefore had only two outcomes: seam verified (proceed), or the 2-minute
watchdog timeout -> nedb_warmboot_unconfirmed -> full scan next start. A genuinely
orphaned/forked tip was indistinguishable from a merely slow/behind seed.

Wire it. In ProcessHeadersMessage, after the peer's headers are accepted and
pindexLast is known (under cs_main), detect the negative of the existing seam
check: if a peer presents a strictly-MORE-work valid header chain
(pindexLast->nChainWork > g_warm_boot_tip_chainwork) whose block AT our warm-boot
tip height is a different hash (pindexLast->GetAncestor(tip_height) != our tip),
then our tip is provably NOT on the most-work chain. Set g_warm_boot_mismatch,
log it distinctly, and persist nedb_warmboot_unconfirmed immediately via the new
WarmBootMarkUnconfirmed() so the next (controlled) startup full-scans from 0.

Conservative by construction:
- Requires STRICTLY more work, so a weaker/equal fork never demotes our tip.
- GetAncestor() returns null if the peer's chain is not linked down to our tip
  height -> no conclusion drawn (absence of evidence remains the watchdog's job).
- No live wipe or reorg here: matches the watchdog doctrine that destructive
  action belongs at the controlled startup path. Mismatch acts on POSITIVE proof
  immediately instead of waiting out the 2-minute watchdog.
- Worst case of a false positive is one slow full-scan boot (non-destructive;
  immutable objects preserved), and forging strictly-more valid PoW work is not
  cheap.

Adds g_warm_boot_tip_chainwork (the persisted cumulative work of the warm-boot
tip) so the seam can compare a peer's chain work without an index lookup.

Tests: NO C++ build harness in the authoring sandbox (no g++/cmake), so this is
source-reviewed only and MUST be built + reviewed before merge. This is the
network-anchoring path, not consensus validation. Suggested validation: a node
warm-booted on an orphaned tip should log "WarmBoot MISMATCH" on the first
strictly-more-work peer headers that fork below its tip height and set the
unconfirmed flag; a node on the canonical tip must never trip it (the seam closes,
or GetAncestor matches our tip). Review-only — do not auto-merge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Eth-Interchained Eth-Interchained merged commit 24d137a into main Jun 25, 2026
Eth-Interchained pushed a commit that referenced this pull request Jun 25, 2026
…coin prefetch)

main advanced past this branch's base via #52 (batch-prefetch block input coins,
867ffbd) and #50 (warm-boot seam tip-mismatch detection, 24d137a).

Conflicts were confined to the warm-boot state block:
- validation.h / validation.cpp: keep main's authoritative g_warm_boot_mismatch
  (the real ProcessHeadersMessage detection from #50) + g_warm_boot_tip_chainwork
  + WarmBootMarkUnconfirmed(); add this branch's g_warm_boot_anchor alongside.
  This branch predated #50 and carried only a 'reserved' mismatch stub -- dropped
  in favor of #50's implementation.

Semantic reconciliation (the #50 x #51 interaction):
- net_processing.cpp ProcessHeadersMessage: #50's mismatch-detection gate was
  written before -anchor existed and lacked the anchor guard. Added
  !g_warm_boot_anchor.load() to it, mirroring the seam-verify gate above. A
  declared root-of-trust seed must never let a peer's more-work header chain flag
  its own canonical tip for resync -- that would be a DoS on exactly the nodes
  -anchor protects. This is the only behavioral change the merge introduces.

init.cpp (-anchor arg beside #52's -coinprefetch, the warm_ok anchor set, and the
ThreadImport startup-ABC gate) auto-merged cleanly and was verified by hand.

Tests: no C++/cargo harness in the authoring sandbox -- source-reviewed; CI
(Codemagic) compiles.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant