diff --git a/src/init.cpp b/src/init.cpp index 2efc495..83077f5 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1081,6 +1081,37 @@ bool AppInit2() if (!LoadBlockIndex()) return InitError(_("Error loading blkindex.dat")); + // triangles fix (pitfall #61): initialize pindexFinalized from the + // hardcoded checkpoint on startup, BEFORE the daemon opens any peer + // connections or processes any block messages. + // + // Without this, pindexFinalized stays NULL on a fresh restart even when + // we have 2.2M blocks on disk, because the auto-checkpoint code in + // ActivateBestChain() at main.cpp:2459 only sets it when + // !IsInitialBlockDownload(). If the chain tip is more than 24h stale + // (which happens on every restart with a synced chain), IsInitialBlockDownload() + // returns true and pindexFinalized never gets set. + // + // The downstream reorg guard at main.cpp:2198 short-circuits when + // pindexFinalized is NULL, which allowed a 3,755-block minority fork + // to overwrite a healthy 2,206,004-block chain on 2026-06-16. Loading + // the hardcoded checkpoint from checkpoints.cpp (block 2,205,000) on + // startup means the reorg guard is always active whenever the + // checkpointed block is in our local mapBlockIndex. + { + CBlockIndex* pCheckpoint = Checkpoints::GetLastCheckpoint(mapBlockIndex); + if (pCheckpoint && pCheckpoint != pindexFinalized) + { + pindexFinalized = pCheckpoint; + printf("STARTUP-CHECKPOINT: pindexFinalized set to block %d (%s) from hardcoded checkpoint\n", + pindexFinalized->nHeight, pindexFinalized->GetBlockHash().ToString().substr(0,20).c_str()); + } + else if (!pCheckpoint) + { + printf("STARTUP-CHECKPOINT: WARNING — hardcoded checkpoint not in local block index, pindexFinalized remains NULL\n"); + } + } + // If the block index is empty but blk0001.dat exists (bootstrap download), // fast-import: build the index directly from the block file without re-writing // data. Batches LevelDB commits every 200K blocks for speed. diff --git a/src/main.cpp b/src/main.cpp index e7d79ef..b4e8078 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4471,11 +4471,28 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) // backwards walk if pnext is null for any other reason (this // happens when LoadBlockIndex() didn't fully heal pnext links, // or the chain was bootstrapped from a snapshot). + // + // pitfall #61 guard: if pindexFinalized is set (from the startup + // hardcoded-checkpoint init in init.cpp), serve from there instead + // of genesis. This prevents a fork peer from feeding us their + // short chain back via getheaders — the peer only learns our + // canonical chain from the finalized point forward, and their + // conflicting fork gets rejected at the reorg check in + // Reorganize() because the fork point is below pindexFinalized. if (!locator.IsNull() && pindex == pindexGenesisBlock && pindexGenesisBlock && locator.GetTipHash() != pindexGenesisBlock->GetBlockHash()) { - printf("WARNING: peer getheaders locator has no common blocks — serving headers from genesis (peer may be on a fork)\n"); - pindex = pindexGenesisBlock; + if (pindexFinalized && pindexFinalized->pnext) + { + printf("getheaders: fork detected from peer %s, serving headers from finalized block %d (not genesis) — pitfall #61 guard\n", + pfrom->addr.ToString().c_str(), pindexFinalized->nHeight); + pindex = pindexFinalized; + } + else + { + printf("WARNING: peer getheaders locator has no common blocks — serving headers from genesis (peer may be on a fork)\n"); + pindex = pindexGenesisBlock; + } } if (pindex)