From 1f0b83f893bf5a0b30b0682eb0f9d324853f880e Mon Sep 17 00:00:00 2001 From: Sami Ahmed Date: Wed, 1 Apr 2026 18:03:31 -0700 Subject: [PATCH] Optimize wallet rescan and address indexing during IBD Move wallet rescan to a background thread after IBD completes instead of blocking on the main thread. Address index is now built during IBD rather than skipped and rebuilt later. Wallet scan releases cs_wallet lock while reading blocks from disk to improve concurrency. Co-Authored-By: Claude Opus 4.6 --- src/init.cpp | 7 +++- src/main.cpp | 99 ++++++++++++++++++++++++++++++++------------------ src/wallet.cpp | 68 +++++++++++++++++++--------------- 3 files changed, 108 insertions(+), 66 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index ddd8a47..3ed2b72 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -420,6 +420,7 @@ std::string HelpMessage() " -upgradewallet " + _("Upgrade wallet to latest format") + "\n" + " -keypool= " + _("Set key pool size to (default: 100)") + "\n" + " -rescan " + _("Rescan the block chain for missing wallet transactions") + "\n" + + " -postibdrescan " + _("Run the wallet rescan after initial sync in a background thread (default: 1)") + "\n" + " -salvagewallet " + _("Attempt to recover private keys from a corrupt wallet.dat") + "\n" + " -checkblocks= " + _("How many blocks to check at startup (default: 2500, 0 = all)") + "\n" + " -checklevel= " + _("How thorough the block verification is (0-6, default: 1)") + "\n" + @@ -629,6 +630,10 @@ bool AppInit2() fConfChange = GetBoolArg("-confchange", false); fEnforceCanonical = GetBoolArg("-enforcecanonical", true); + fAddressIndex = GetBoolArg("-addressindex", false); + if (fAddressIndex) + printf("Address index enabled\n"); + if (mapArgs.count("-mininput")) { if (!ParseMoney(mapArgs["-mininput"], nMinimumInputValue)) @@ -1045,7 +1050,7 @@ bool AppInit2() printf("Rescanning last %i blocks (from block %i)...\n", pindexBest->nHeight - pindexRescan->nHeight, pindexRescan->nHeight); nStart = GetTimeMillis(); bool fScannedWithIndex = false; - if (GetBoolArg("-addressindex", false) && !GetBoolArg("-rescan")) + if (fAddressIndex && !GetBoolArg("-rescan")) { CTxDB txdb("r"); int nAddressIndexStartHeight = 0; diff --git a/src/main.cpp b/src/main.cpp index 856a637..073b2ac 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -110,11 +110,54 @@ struct CHeaderSyncNode static std::map mapHeaderSync; static uint256 hashBestHeaderSync = 0; +static CCriticalSection cs_PostIbdWork; +static bool fPostIbdWorkStarted = false; static const unsigned int MAX_HEADER_SYNC_CACHE = 50000; static const unsigned int HEADER_DOWNLOAD_WINDOW = 128; static const int64_t HEADER_REQUEST_TIMEOUT_MICROS = 30 * 1000000; +static void ThreadPostIbdWork(void* parg) +{ + RenameThread("Triangles-postibd"); + + try + { + if (!fShutdown && pwalletMain && GetBoolArg("-postibdrescan", true)) + { + printf("Starting post-IBD wallet rescan from genesis in background...\n"); + uiInterface.InitMessage(_("Rescanning wallet in background...")); + int nFound = 0; + bool fUsedIndex = false; + if (fAddressIndex) + { + fUsedIndex = pwalletMain->ScanForWalletTransactionsFromIndex(pindexGenesisBlock, true, &nFound); + if (!fUsedIndex) + printf("Indexed wallet rescan failed, falling back to full rescan.\n"); + } + if (!fUsedIndex) + nFound = pwalletMain->ScanForWalletTransactions(pindexGenesisBlock, true); + printf("Post-IBD wallet rescan complete: %d transactions found (indexed=%d)\n", nFound, fUsedIndex); + } + + if (!fShutdown && fSecMsgEnabled) + { + printf("Starting post-IBD secure message chain scan in background...\n"); + uiInterface.InitMessage(_("Scanning for secure messages in background...")); + SecureMsgScanBlockChain(); + printf("Post-IBD secure message chain scan complete\n"); + } + } + catch (std::exception& e) + { + PrintExceptionContinue(&e, "ThreadPostIbdWork()"); + } + catch (...) + { + PrintExceptionContinue(NULL, "ThreadPostIbdWork()"); + } +} + static uint256 GetHeaderSyncTrust(unsigned int nBits) { CBigNum bnTarget; @@ -2043,8 +2086,8 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck) return error("ConnectBlock() : UpdateTxIndex failed"); } - // Update address index (skip during IBD - will be rebuilt on next start with -reindex) - if (fAddressIndex && !fIsInitialDownload) + // Update address index + if (fAddressIndex) { for (unsigned int i = 0; i < vtx.size(); i++) { @@ -2406,23 +2449,23 @@ bool CBlock::SetBestChain(CTxDB& txdb, CBlockIndex* pindexNew) const CBlockLocator locator(pindexBest); ::SetBestChain(locator); - // Wallet rescan: SyncWithWallets was skipped during IBD, so scan - // the entire chain to pick up all wallet transactions. - if (pwalletMain) + // Run expensive post-IBD scans in the background so reaching tip + // is not blocked by wallet/message index rebuild work. + bool fStartPostIbdWork = false; { - printf("Starting post-IBD wallet rescan from genesis...\n"); - uiInterface.InitMessage(_("Rescanning wallet...")); - int nFound = pwalletMain->ScanForWalletTransactions(pindexGenesisBlock, true); - printf("Post-IBD wallet rescan complete: %d transactions found\n", nFound); + LOCK(cs_PostIbdWork); + if (!fPostIbdWorkStarted) + { + fPostIbdWorkStarted = true; + fStartPostIbdWork = true; + } } - // Secure messaging: scan chain for public keys needed to decrypt messages - if (fSecMsgEnabled) + if (fStartPostIbdWork && !NewThread(ThreadPostIbdWork, NULL)) { - printf("Starting post-IBD secure message chain scan...\n"); - uiInterface.InitMessage(_("Scanning for secure messages...")); - SecureMsgScanBlockChain(); - printf("Post-IBD secure message chain scan complete\n"); + LOCK(cs_PostIbdWork); + fPostIbdWorkStarted = false; + printf("Warning: post-IBD background work thread could not be started; scans skipped.\n"); } } fWasInitialDownload = fIsInitialDownload; @@ -2788,9 +2831,6 @@ bool CBlock::AcceptBlock() pnode->PushInventory(CInv(MSG_BLOCK, hash)); } - // triangles: check pending sync-checkpoint - Checkpoints::AcceptPendingSyncCheckpoint(); - return true; } @@ -2829,7 +2869,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) // triangles: check proof-of-stake // Limited duplicity on stake: prevents block flood attack // Duplicate stake allowed only when there is orphan child block - if (pblock->IsProofOfStake() && setStakeSeen.count(pblock->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash) && !Checkpoints::WantedByPendingSyncCheckpoint(hash)) + if (pblock->IsProofOfStake() && setStakeSeen.count(pblock->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash)) return error("ProcessBlock() : duplicate proof-of-stake (%s, %d) for block %s", pblock->GetProofOfStake().first.ToString().c_str(), pblock->GetProofOfStake().second, hash.ToString().c_str()); // Preliminary checks @@ -2843,12 +2883,12 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) } // Anti-spam: reject blocks with insufficient difficulty to prevent memory flooding. - // Use sync checkpoint as reference; fall back to chain tip if checkpoint is genesis. - CBlockIndex* pcheckpoint = Checkpoints::GetLastSyncCheckpoint(); - if (!pcheckpoint || pcheckpoint->nHeight == 0) + // Use the most recent hardened checkpoint we know about; fall back to the chain tip. + CBlockIndex* pcheckpoint = Checkpoints::GetLastCheckpoint(mapBlockIndex); + if (!pcheckpoint) pcheckpoint = pindexBest; - if (pcheckpoint && pblock->hashPrevBlock != hashBestChain && !Checkpoints::WantedByPendingSyncCheckpoint(hash)) + if (pcheckpoint && pblock->hashPrevBlock != hashBestChain) { int64_t deltaTime = pblock->GetBlockTime() - pcheckpoint->nTime; CBigNum bnNewBlock; @@ -2877,10 +2917,6 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) } } - // triangles: ask for pending sync-checkpoint if any - if (!IsInitialBlockDownload()) - Checkpoints::AskForPendingSyncCheckpoint(pfrom); - // If don't already have its previous block, shunt it off to holding area until we get it if (!mapBlockIndex.count(pblock->hashPrevBlock)) { @@ -2891,7 +2927,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) { // Limited duplicity on stake: prevents block flood attack // Duplicate stake allowed only when there is orphan child block - if (setStakeSeenOrphan.count(pblock2->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash) && !Checkpoints::WantedByPendingSyncCheckpoint(hash)) + if (setStakeSeenOrphan.count(pblock2->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash)) return error("ProcessBlock() : duplicate proof-of-stake (%s, %d) for orphan block %s", pblock2->GetProofOfStake().first.ToString().c_str(), pblock2->GetProofOfStake().second, hash.ToString().c_str()); else setStakeSeenOrphan.insert(pblock2->GetProofOfStake()); @@ -2979,10 +3015,6 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) nQueued, hash.ToString().substr(0,20).c_str()); } - // triangles: if responsible for sync-checkpoint send it - if (pfrom && !CSyncCheckpoint::strMasterPrivKey.empty()) - Checkpoints::SendSyncCheckpoint(Checkpoints::AutoSelectSyncCheckpoint()); - return true; } @@ -3859,9 +3891,6 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) cPeerBlockCounts.input(pfrom->nStartingHeight); - // triangles: ask for pending sync-checkpoint if any - if (!IsInitialBlockDownload()) - Checkpoints::AskForPendingSyncCheckpoint(pfrom); } diff --git a/src/wallet.cpp b/src/wallet.cpp index cbf9123..1249977 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -959,39 +959,47 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate) int ret = 0; CBlockIndex* pindex = pindexStart; + int nScanned = 0; + int nTotal = nBestHeight - (pindexStart ? pindexStart->nHeight : 0); + if (nTotal < 1) nTotal = 1; + int64_t nLastProgressTime = GetTimeMillis(); + + // Cache wallet birthday outside the loop (only written during key import) + int64_t nBirthTime = nTimeFirstKey; + + while (pindex) { - LOCK(cs_wallet); - int nScanned = 0; - int nTotal = nBestHeight - (pindexStart ? pindexStart->nHeight : 0); - if (nTotal < 1) nTotal = 1; - while (pindex) + if (fShutdown) + break; + + ++nScanned; + // Report progress every 500ms to keep UI responsive + int64_t nNow = GetTimeMillis(); + if (nNow - nLastProgressTime > 500) { - if (fShutdown) - break; - - // Report progress every 10000 blocks to keep UI responsive - if (++nScanned % 10000 == 0) - { - int nPercent = (nScanned * 100) / nTotal; - uiInterface.InitMessage(strprintf(_("Rescanning... %d%%"), nPercent)); - } - - // no need to read and scan block, if block was created before - // our wallet birthday (as adjusted for block time variability) - if (nTimeFirstKey && (pindex->nTime < (nTimeFirstKey - 7200))) { - pindex = pindex->pnext; - continue; - } - - CBlock block; - block.ReadFromDisk(pindex, true); - for (CTransaction& tx : block.vtx) - { - if (AddToWalletIfInvolvingMe(tx, &block, fUpdate)) - ret++; - } - pindex = pindex->pnext; + nLastProgressTime = nNow; + int nPercent = (nScanned * 100) / nTotal; + uiInterface.InitMessage(strprintf(_("Rescanning... %d%%"), nPercent)); } + + // no need to read and scan block, if block was created before + // our wallet birthday (as adjusted for block time variability) + if (nBirthTime && (pindex->nTime < (nBirthTime - 7200))) { + pindex = pindex->pnext; + continue; + } + + // Read block from disk WITHOUT holding wallet lock + CBlock block; + block.ReadFromDisk(pindex, true); + + // AddToWalletIfInvolvingMe acquires cs_wallet internally + for (CTransaction& tx : block.vtx) + { + if (AddToWalletIfInvolvingMe(tx, &block, fUpdate)) + ret++; + } + pindex = pindex->pnext; } return ret; }