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));
|
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(
|
blockStack.emplace_back(
|
||||||
pindex,
|
pindex,
|
||||||
std::make_pair(oldSproutTree, oldSaplingTree),
|
std::make_pair(oldSproutTree, oldSaplingTree),
|
||||||
recentlyConflicted.first.at(pindex));
|
recentlyConflicted.first[pindex]);
|
||||||
|
|
||||||
pindex = pindex->pprev;
|
pindex = pindex->pprev;
|
||||||
}
|
}
|
||||||
@@ -174,7 +181,11 @@ void ThreadNotifyWallets(CBlockIndex *pindexLastTip)
|
|||||||
// network message processing thread.
|
// 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) {
|
while (pindexLastTip && pindexLastTip != pindexFork) {
|
||||||
// Read block from disk.
|
// Read block from disk.
|
||||||
CBlock block;
|
CBlock block;
|
||||||
@@ -184,6 +195,7 @@ void ThreadNotifyWallets(CBlockIndex *pindexLastTip)
|
|||||||
// Sleep briefly and retry on the next cycle instead of crashing.
|
// Sleep briefly and retry on the next cycle instead of crashing.
|
||||||
LogPrintf("%s: block at height %d not yet readable, will retry\n",
|
LogPrintf("%s: block at height %d not yet readable, will retry\n",
|
||||||
__func__, pindexLastTip->GetHeight());
|
__func__, pindexLastTip->GetHeight());
|
||||||
|
fDisconnectIncomplete = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
LogPrintf("*** %s\n", "Failed to read block while notifying wallets of block disconnects");
|
LogPrintf("*** %s\n", "Failed to read block while notifying wallets of block disconnects");
|
||||||
@@ -205,8 +217,9 @@ void ThreadNotifyWallets(CBlockIndex *pindexLastTip)
|
|||||||
pindexLastTip = pindexLastTip->pprev;
|
pindexLastTip = pindexLastTip->pprev;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify block connections
|
// Notify block connections (skipped this cycle if the disconnect loop broke to retry an
|
||||||
while (!blockStack.empty()) {
|
// unreadable block, so pindexLastTip is not advanced past the un-disconnected blocks).
|
||||||
|
while (!fDisconnectIncomplete && !blockStack.empty()) {
|
||||||
auto blockData = blockStack.back();
|
auto blockData = blockStack.back();
|
||||||
blockStack.pop_back();
|
blockStack.pop_back();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user