Fix critical stability issues (v5.8.2 stability patch)
Build All Platforms / test-linux-unit (push) Failing after 40s
Build All Platforms / build-linux-qt (push) Failing after 40s
Build All Platforms / build-linux-daemon (push) Failing after 40s
Build All Platforms / build-windows-qt (push) Has been cancelled
Build All Platforms / build-windows-daemon (push) Has been cancelled
Build All Platforms / build-macos (push) Has been cancelled
Build All Platforms / release (push) Has been cancelled
Build All Platforms / Trigger TRI-PI ARM64 Build (push) Has been cancelled

Critical fixes for production stability:

1. NULL POINTER CRASH FIXES (P0)
   - Add defensive null checks in GetNextTargetRequired_()
   - Fix GetDifficulty() crash when no PoW blocks exist
   - Prevents seed node crash-loops and RPC failures

2. CHAIN REORGANIZATION ATOMICITY (P0)
   - Move setStakeSeen modifications to AFTER database commit
   - Prevents DB/memory state desync on failed reorgs
   - Adds critical transaction boundary documentation
   - Improves reorg logging with fork depth details

3. ORPHAN BLOCK MEMORY MANAGEMENT (P1)
   - Extract LimitOrphanBlocks() into reusable function
   - Add proactive cleanup when IBD completes (4000→2000 limit)
   - Prevents memory exhaustion DoS attacks
   - Better diagnostic logging

4. DATABASE ERROR HANDLING (P2)
   - Enhanced critical error messages in TxnCommit()
   - Clear guidance on disk/corruption/permissions issues
   - Faster incident diagnosis

All changes are consensus-safe with no fork risk.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-04-19 02:31:55 -07:00
parent 00af636aca
commit 734979c93b
4 changed files with 80 additions and 34 deletions
+73 -33
View File
@@ -1407,6 +1407,48 @@ uint256 WantedByOrphan(const CBlock* pblockOrphan)
return pblockOrphan->hashPrevBlock;
}
// Evict excess orphan blocks when limit is exceeded
// Returns number of orphans evicted
unsigned int LimitOrphanBlocks(unsigned int nMaxOrphans)
{
unsigned int nEvicted = 0;
while (mapOrphanBlocks.size() > nMaxOrphans)
{
// Evict a random orphan
uint256 randomhash = GetRandHash();
auto it = mapOrphanBlocks.lower_bound(randomhash);
if (it == mapOrphanBlocks.end())
it = mapOrphanBlocks.begin();
if (it == mapOrphanBlocks.end())
break; // No orphans to evict
CBlock* pblockEvict = it->second;
uint256 evictHash = it->first;
// Remove from by-prev index
for (auto range = mapOrphanBlocksByPrev.equal_range(pblockEvict->hashPrevBlock);
range.first != range.second; ++range.first)
{
if (range.first->second == pblockEvict) {
mapOrphanBlocksByPrev.erase(range.first);
break;
}
}
setStakeSeenOrphan.erase(pblockEvict->GetProofOfStake());
delete pblockEvict;
mapOrphanBlocks.erase(evictHash);
nEvicted++;
}
if (nEvicted > 0)
printf("LimitOrphanBlocks: evicted %u orphan(s), %u remain\n",
nEvicted, (unsigned int)mapOrphanBlocks.size());
return nEvicted;
}
// miner's coin base reward
int64_t GetProofOfWorkReward(int64_t nFees)
{
@@ -1500,9 +1542,13 @@ static unsigned int GetNextTargetRequired_(const CBlockIndex* pindexLast, bool f
return bnTargetLimit.GetCompact(); // genesis block
const CBlockIndex* pindexPrev = GetLastBlockIndex(pindexLast, fProofOfStake);
if (pindexPrev == NULL)
return bnTargetLimit.GetCompact(); // no previous block of this type
if (pindexPrev->pprev == NULL)
return bnTargetLimit.GetCompact(); // first block
const CBlockIndex* pindexPrevPrev = GetLastBlockIndex(pindexPrev->pprev, fProofOfStake);
if (pindexPrevPrev == NULL)
return bnTargetLimit.GetCompact(); // no second previous block of this type
if (pindexPrevPrev->pprev == NULL)
return bnTargetLimit.GetCompact(); // second block
@@ -2472,12 +2518,6 @@ bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew)
vResurrect.push_back(tx);
}
// Remove disconnected PoS blocks from setStakeSeen so they don't
// block acceptance of valid blocks on the winning chain.
for (CBlockIndex* pindex : vDisconnect)
if (pindex->IsProofOfStake())
setStakeSeen.erase(make_pair(pindex->prevoutStake, pindex->nStakeTime));
// Connect longer branch
vector<CTransaction> vDelete;
for (unsigned int i = 0; i < vConnect.size(); i++)
@@ -2505,19 +2545,37 @@ bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew)
if (!txdb.TxnCommit())
return error("Reorganize() : TxnCommit failed");
// Disconnect shorter branch
// ======================================================================
// CRITICAL: All operations below this point must be in-memory only and
// should never fail. The DB transaction is committed, so we cannot abort.
// ======================================================================
// Disconnect shorter branch (in-memory only)
for (CBlockIndex* pindex : vDisconnect)
if (pindex->pprev)
pindex->pprev->pnext = NULL;
// Connect longer branch
// Connect longer branch (in-memory only)
for (CBlockIndex* pindex : vConnect)
if (pindex->pprev)
pindex->pprev->pnext = pindex;
// Remove disconnected PoS blocks from setStakeSeen so they don't
// block acceptance of valid blocks on the winning chain.
// This MUST happen after commit to maintain consistency.
for (CBlockIndex* pindex : vDisconnect)
if (pindex->IsProofOfStake())
setStakeSeen.erase(make_pair(pindex->prevoutStake, pindex->nStakeTime));
// Resurrect memory transactions that were in the disconnected branch
unsigned int nResurrected = 0;
for (CTransaction& tx : vResurrect)
tx.AcceptToMemoryPool(txdb, false);
{
if (tx.AcceptToMemoryPool(txdb, false))
nResurrected++;
}
if (nResurrected > 0)
printf("REORGANIZE: resurrected %u transactions to mempool\n", nResurrected);
// Delete redundant memory transactions that are in the connected branch
for (CTransaction& tx : vDelete) {
@@ -2525,7 +2583,8 @@ bool static Reorganize(CTxDB& txdb, CBlockIndex* pindexNew)
mempool.removeConflicts(tx);
}
printf("REORGANIZE: done\n");
printf("REORGANIZE: done (fork at height %d, %zu disconnected, %zu connected)\n",
pfork->nHeight, vDisconnect.size(), vConnect.size());
return true;
}
@@ -2715,6 +2774,9 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew)
{
printf("*** Initial block download complete at height %d ***\n", nBestHeight);
// Trim orphan blocks to normal limit now that IBD is done
LimitOrphanBlocks(MAX_ORPHAN_BLOCKS);
// Update wallet best chain locator now that IBD is done
const CBlockLocator locator(pindexBest);
::SetBestChain(locator);
@@ -3246,29 +3308,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
// Allow more orphans during IBD so out-of-order blocks from parallel
// downloads don't get evicted and re-requested.
unsigned int nMaxOrphans = IsInitialBlockDownload() ? MAX_ORPHAN_BLOCKS_IBD : MAX_ORPHAN_BLOCKS;
if (mapOrphanBlocks.size() > nMaxOrphans)
{
// Evict a random orphan
uint256 randomhash = GetRandHash();
auto it = mapOrphanBlocks.lower_bound(randomhash);
if (it == mapOrphanBlocks.end())
it = mapOrphanBlocks.begin();
CBlock* pblockEvict = it->second;
uint256 evictHash = it->first;
// Remove from by-prev index
for (auto range = mapOrphanBlocksByPrev.equal_range(pblockEvict->hashPrevBlock);
range.first != range.second; ++range.first)
{
if (range.first->second == pblockEvict) {
mapOrphanBlocksByPrev.erase(range.first);
break;
}
}
setStakeSeenOrphan.erase(pblockEvict->GetProofOfStake());
delete pblockEvict;
mapOrphanBlocks.erase(evictHash);
printf("ProcessBlock: orphan eviction, %u orphans remain\n", (unsigned int)mapOrphanBlocks.size());
}
LimitOrphanBlocks(nMaxOrphans);
// Ask this guy to fill in what we're missing
if (pfrom && pindexBest)
+1
View File
@@ -136,6 +136,7 @@ bool IsInitialBlockDownload();
std::string GetWarnings(std::string strFor);
bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock);
uint256 WantedByOrphan(const CBlock* pblockOrphan);
unsigned int LimitOrphanBlocks(unsigned int nMaxOrphans);
const CBlockIndex* GetLastBlockIndex(const CBlockIndex* pindex, bool fProofOfStake);
void StakeMiner(CWallet *pwallet);
void ResendWalletTransactions(bool fForce = false);
+3
View File
@@ -28,6 +28,9 @@ double GetDifficulty(const CBlockIndex* blockindex)
blockindex = GetLastBlockIndex(pindexBest, false);
}
if (blockindex == NULL)
return 1.0;
int nShift = (blockindex->nBits >> 24) & 0xff;
double dDiff =
+3 -1
View File
@@ -162,7 +162,9 @@ bool CTxDB::TxnCommit()
delete activeBatch;
activeBatch = NULL;
if (!status.ok()) {
printf("LevelDB batch commit failure: %s\n", status.ToString().c_str());
printf("ERROR: LevelDB batch commit failure: %s\n", status.ToString().c_str());
printf("ERROR: This may indicate disk full, corruption, or permissions issue.\n");
printf("ERROR: Chain state may be inconsistent - immediate investigation required!\n");
return false;
}
return true;