fix: harden ThreadNotifyWallets IBD read-retry (crash + abandoned disconnects)
Two defects in the IBD ReadBlockFromDisk retry path of ThreadNotifyWallets: - The connect-loop rebuild indexed recentlyConflicted.first.at(pindex), which throws std::out_of_range for a block whose conflict entry was drained in an earlier retry cycle. Uncaught under cs_main in the notify boost thread, that aborts the node and crash-loops. Use operator[] (empty-list default), i.e. best-effort conflict notifications, instead of throwing. - The disconnect-loop IBD break fell through into the connect loop, which advanced pindexLastTip to the new tip and permanently abandoned the pending disconnect notifications (leaving wallet witness/anchor state desynced from the chain). Add a flag so the break skips the connect loop this cycle and truly retries the disconnect on the next one. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user