From 7faf13dc31c0f13baf3adca2d9afe4106ffbe1ac Mon Sep 17 00:00:00 2001 From: Krystie Date: Fri, 24 Apr 2026 11:21:39 -0700 Subject: [PATCH] Fix IBD stall: refill header cache when exhausted during sync Two fixes for the header cache exhaustion bug: 1. Block-accepted path: when hashBestHeaderSync==0 and we're still behind peers during IBD, send getheaders to all peers to refill the header cache. Previously the refill was gated on hashBestHeaderSync!=0, creating a dead loop once the cache drained. 2. Stall detection: also send getheaders alongside getblocks when a stall is detected. Previously only getblocks was sent, which cannot refill mapHeaderSync or restart the header planner. Root cause: getheaders returns 2000 headers per batch. Blocks are consumed from the cache faster than headers are fetched. Once mapHeaderSync empties, hashBestHeaderSync becomes 0, and the refill path is never taken again. See BUG_ANALYSIS_IBD_STALL.md for full details. --- src/main.cpp | 22 ++++++++++++++++ src/rpcblockchain.cpp | 59 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 69 insertions(+), 12 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index bed13ae..d76783d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3607,6 +3607,23 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) printf("IBD-DIAG: queued %u more blocks from header planner after accepting %s\n", nQueued, hash.ToString().substr(0,20).c_str()); } + else if (IsInitialBlockDownload() && nBestHeight < GetNumBlocksOfPeers()) + { + // Header cache exhausted during IBD — request more headers from all peers. + // This fixes the stall where mapHeaderSync drains to empty, hashBestHeaderSync + // becomes 0, and the refill path is never taken again. + printf("IBD-DIAG: header cache empty at height %d, requesting more headers from all peers\n", + nBestHeight); + LOCK(cs_vNodes); + for (CNode* pnode : vNodes) + { + if (!pnode->fClient && pnode->nVersion != 0) + { + pnode->pindexLastGetHeadersBegin = NULL; + pnode->PushGetHeaders(pindexBest, uint256(0)); + } + } + } return true; } @@ -5970,6 +5987,11 @@ bool SendMessages(CNode* pto, bool fSendTrickle) } else { pto->PushGetBlocks(pindexBest, uint256(0)); } + // Also send getheaders during stall to restart the header planner. + // Without this, a drained header cache stays empty because only + // getblocks is sent on stall, which can't refill mapHeaderSync. + pto->pindexLastGetHeadersBegin = NULL; + pto->PushGetHeaders(pindexBest, uint256(0)); nLastBlockReceived = GetTime(); } } diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp index d3fafa8..33fc8ba 100644 --- a/src/rpcblockchain.cpp +++ b/src/rpcblockchain.cpp @@ -364,26 +364,46 @@ Value gettxoutsetinfo(const Array& params, bool fHelp) return obj; } -static int64_t ComputeActiveChainSupplyFromBlocks(int& nBlocksScanned, int& nTransactionsScanned) +static void GetActiveChainVector(std::vector& chain) { - nBlocksScanned = 0; - nTransactionsScanned = 0; + chain.clear(); if (!pindexBest) throw runtime_error("recalculatesupply: no best block"); + for (CBlockIndex* pindex = pindexBest; pindex; pindex = pindex->pprev) + chain.push_back(pindex); + + std::reverse(chain.begin(), chain.end()); +} + +static int64_t ComputeActiveChainSupplyFromBlocks(const std::vector& chain, int& nBlocksScanned, int& nTransactionsScanned) +{ + nBlocksScanned = 0; + nTransactionsScanned = 0; + CTxDB txdb("r"); int64_t nSupply = 0; - for (CBlockIndex* pindex = pindexGenesisBlock; pindex; pindex = pindex->pnext) + for (std::vector::const_iterator pindexIt = chain.begin(); pindexIt != chain.end(); ++pindexIt) { - CBlock block; - if (!block.ReadFromDisk(pindex, true)) - throw runtime_error(strprintf("recalculatesupply: failed reading block at height %d", pindex->nHeight)); + CBlockIndex* pindex = *pindexIt; + if (!pindex) + throw runtime_error("recalculatesupply: null active-chain block index"); + if (pindex->nHeight == 0) + { + nBlocksScanned++; + continue; + } + + CBlock block; int64_t nBlockValueIn = 0; int64_t nBlockValueOut = 0; + if (!block.ReadFromDisk(pindex)) + throw runtime_error(strprintf("recalculatesupply: failed reading block at height %d", pindex->nHeight)); + for (std::vector::const_iterator txIt = block.vtx.begin(); txIt != block.vtx.end(); ++txIt) { const CTransaction& tx = *txIt; @@ -442,9 +462,12 @@ Value recalculatesupply(const Array& params, bool fHelp) int nUtxoCount = 0; int64_t nUtxoSupply = txdbRead.SumUtxoValues(nUtxoCount); + std::vector activeChain; + GetActiveChainVector(activeChain); + int nBlocksScanned = 0; int nTransactionsScanned = 0; - int64_t nHistoricalSupply = ComputeActiveChainSupplyFromBlocks(nBlocksScanned, nTransactionsScanned); + int64_t nHistoricalSupply = ComputeActiveChainSupplyFromBlocks(activeChain, nBlocksScanned, nTransactionsScanned); int64_t nOldTipSupply = pindexBest->nMoneySupply; @@ -453,15 +476,27 @@ Value recalculatesupply(const Array& params, bool fHelp) CTxDB txdbWrite; int64_t nRunningSupply = 0; - for (CBlockIndex* pindex = pindexGenesisBlock; pindex; pindex = pindex->pnext) + for (std::vector::const_iterator pindexIt = activeChain.begin(); pindexIt != activeChain.end(); ++pindexIt) { - CBlock block; - if (!block.ReadFromDisk(pindex, true)) - throw runtime_error(strprintf("recalculatesupply: failed reading block at height %d during apply", pindex->nHeight)); + CBlockIndex* pindex = *pindexIt; + if (!pindex) + throw runtime_error("recalculatesupply: null active-chain block index during apply"); + if (pindex->nHeight == 0) + { + pindex->nMoneySupply = 0; + if (!txdbWrite.WriteBlockIndex(CDiskBlockIndex(pindex))) + throw runtime_error("recalculatesupply: failed to persist genesis block index during apply"); + continue; + } + + CBlock block; int64_t nBlockValueIn = 0; int64_t nBlockValueOut = 0; + if (!block.ReadFromDisk(pindex)) + throw runtime_error(strprintf("recalculatesupply: failed reading block at height %d during apply", pindex->nHeight)); + for (std::vector::const_iterator txIt = block.vtx.begin(); txIt != block.vtx.end(); ++txIt) { const CTransaction& tx = *txIt;