diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp index 4836530ac..e138b7322 100644 --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -156,10 +156,17 @@ void ThreadNotifyWallets(CBlockIndex *pindexLastTip) assert(pcoinsTip->GetSaplingAnchorAt(SaplingMerkleTree::empty_root(), oldSaplingTree)); } + // Use operator[] (empty-list default), NOT .at(): a block's conflict entry can be + // absent if it was drained in an earlier cycle whose connect loop hit the IBD + // ReadBlockFromDisk retry below (that break leaves pindexLastTip behind, so the block + // gets rebuilt here but its conflicts were already cleared and are never re-inserted). + // .at() would throw std::out_of_range which, uncaught under cs_main in this boost + // thread, aborts the node (and crash-loops since pindexLastTip cannot advance). A + // missing entry simply means no conflict notifications for this block (best-effort). blockStack.emplace_back( pindex, std::make_pair(oldSproutTree, oldSaplingTree), - recentlyConflicted.first.at(pindex)); + recentlyConflicted.first[pindex]); pindex = pindex->pprev; } @@ -174,7 +181,11 @@ void ThreadNotifyWallets(CBlockIndex *pindexLastTip) // network message processing thread. // - // Notify block disconnects + // Notify block disconnects. If an IBD read fails mid-disconnect we must NOT fall through + // to the connect loop (which advances pindexLastTip to the new tip and permanently abandons + // the remaining disconnects -> wallet witness/anchor desync); skip connects this cycle and + // retry the whole disconnect next cycle, exactly as the connect-side break retries. + bool fDisconnectIncomplete = false; while (pindexLastTip && pindexLastTip != pindexFork) { // Read block from disk. CBlock block; @@ -184,6 +195,7 @@ void ThreadNotifyWallets(CBlockIndex *pindexLastTip) // Sleep briefly and retry on the next cycle instead of crashing. LogPrintf("%s: block at height %d not yet readable, will retry\n", __func__, pindexLastTip->GetHeight()); + fDisconnectIncomplete = true; break; } LogPrintf("*** %s\n", "Failed to read block while notifying wallets of block disconnects"); @@ -205,8 +217,9 @@ void ThreadNotifyWallets(CBlockIndex *pindexLastTip) pindexLastTip = pindexLastTip->pprev; } - // Notify block connections - while (!blockStack.empty()) { + // Notify block connections (skipped this cycle if the disconnect loop broke to retry an + // unreadable block, so pindexLastTip is not advanced past the un-disconnected blocks). + while (!fDisconnectIncomplete && !blockStack.empty()) { auto blockData = blockStack.back(); blockStack.pop_back();