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.
This commit is contained in:
Krystie
2026-04-24 11:21:39 -07:00
parent db65324b7a
commit 7faf13dc31
2 changed files with 69 additions and 12 deletions
+22
View File
@@ -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();
}
}
+47 -12
View File
@@ -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<CBlockIndex*>& 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<CBlockIndex*>& 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<CBlockIndex*>::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<CTransaction>::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<CBlockIndex*> 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<CBlockIndex*>::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<CTransaction>::const_iterator txIt = block.vtx.begin(); txIt != block.vtx.end(); ++txIt)
{
const CTransaction& tx = *txIt;