From 68c4e38411b5dd4eb7ca882ddbe5c79e213432ed Mon Sep 17 00:00:00 2001 From: Sami Ahmed Date: Mon, 15 Jun 2026 15:43:03 -0700 Subject: [PATCH] Fix getheaders pnext null bug: serve main chain from genesis, tip-backwards fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related bugs in the getheaders handler at main.cpp:4441: 1. Locator-mismatch returns 0 headers: GetBlockIndex() falls through to pindexGenesisBlock when no locator hash matches the main chain. pindexGenesisBlock->pnext is null, so the for-loop exits immediately and the peer gets an empty headers response. This was the DNS3 headers-first sync stall (pitfall #36): 'getheaders -1 to 0000...' logged repeatedly. Mirror the getblocks handler (line 4387): detect the mismatch, log a warning, and serve headers from genesis so the peer can discover the canonical chain. 2. Broken pnext chain at any height: Even when GetBlockIndex() returns a valid (non-genesis) block, its pnext can be null — this happens when LoadBlockIndex() didn't fully heal pnext links (e.g., the node was bootstrapped from a snapshot, or the chain was interrupted by a crash). On tridock every pnext link was null despite 2.2M blocks (the June 11 'Heal pnext links' commit addressed a similar symptom for GetKernelStakeModifier() but doesn't reach into the getheaders handler). Fall back to a tip-backwards walk from pindexBest when pnext is null: O(N) but correct, and only triggers on the broken path. (References the design in references/getheaders-pnext-fix.md from the v5.9.10 deploy notes — that fix was never actually committed, only prototyped and dropped during the June 4 git cleanup.) Bump to v5.9.14 (also embeds the embedded-Tor 0700 fix from ed996c8). Refs: SKILL.md pitfall #36 --- src/clientversion.h | 2 +- src/main.cpp | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/clientversion.h b/src/clientversion.h index e8adaaa..1ed7624 100644 --- a/src/clientversion.h +++ b/src/clientversion.h @@ -8,7 +8,7 @@ // These need to be macros, as version.cpp's and triangles-qt.rc's voodoo requires it #define CLIENT_VERSION_MAJOR 5 #define CLIENT_VERSION_MINOR 9 -#define CLIENT_VERSION_REVISION 13 +#define CLIENT_VERSION_REVISION 14 #define CLIENT_VERSION_BUILD 0 // Converts the parameter X to a string after macro replacement on X has been performed. diff --git a/src/main.cpp b/src/main.cpp index cc4b946..e7d79ef 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4457,8 +4457,43 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) { // Find the last block the caller has in the main chain pindex = locator.GetBlockIndex(); + + // triangles fix: handle broken pnext chain. + // GetBlockIndex() returns pindexGenesisBlock when no locator + // hash matches our main chain (peer is on a different fork or + // a stale local state). pindexGenesisBlock->pnext is always + // null, which would cause the for-loop below to send ZERO + // headers, leaving the peer stuck (logged as "getheaders -1"). + // + // Mirror the getblocks handler: if the locator matches nothing + // on our main chain, serve our headers from genesis so the peer + // can discover the canonical chain. Then fall back to a tip- + // 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). + 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 (pindex) - pindex = pindex->pnext; + { + if (pindex->pnext) + { + pindex = pindex->pnext; + } + else + { + // pnext is null — fall back to walking from pindexBest + // backwards to find the block immediately after pindex + CBlockIndex* pWalk = pindexBest; + while (pWalk && pWalk->pprev != pindex) + pWalk = pWalk->pprev; + pindex = pWalk; // null if pindex is already the tip + } + } } vector vHeaders;