diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 20df4ea69..443498cba 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3463,6 +3463,51 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate) pwalletMain->rescanHeight = pindex ? pindex->GetHeight() : 0; } + // --- interrupt checkpoint setup ------------------------------------------------------- + // Where does the wallet currently believe it has scanned to? A checkpoint may only ever + // ADVANCE that point, and only if this scan is contiguous with it. The RPC entry points + // (rescan / importprivkey / z_importkey / z_importviewingkey) take a caller-supplied start + // height validated only against chainActive.Height(), so a scan can legitimately begin far + // ABOVE the persisted locator -- writing a checkpoint from such a scan would mark the + // skipped range as scanned and hide any funds in it. + CBlockIndex* pindexPersisted = NULL; + { + CWalletDB walletdb(strWalletFile, "r+", false); + CBlockLocator locPersisted; + if (walletdb.ReadBestBlock(locPersisted)) + pindexPersisted = FindForkInGlobalIndex(chainActive, locPersisted); + } + const bool fMayCheckpoint = pindexPersisted != NULL && + pindexStart->GetHeight() <= pindexPersisted->GetHeight() + 1; + if (!fMayCheckpoint) { + LogPrintf("%s: scan starts at %d but the wallet is persisted at %d; progress will NOT be " + "checkpointed on interrupt (a non-contiguous scan cannot safely advance the locator)\n", + __func__, pindexStart->GetHeight(), + pindexPersisted ? pindexPersisted->GetHeight() : -1); + } + + // Persist progress when the scan is cut short. `pindexStopped` is the block we were ABOUT to + // scan, so the last fully-processed block is its parent. Resume restarts AT the locator's own + // block (CChain::GetLocator pushes it first; FindForkInGlobalIndex returns it), giving one + // block of deliberate overlap -- idempotent, because AddToWallet only merges when the tx is + // already present. No witness work is needed: witnesses are re-derived from each note's own + // witnessHeight, and witnessRootValidated is in-memory-only so every note is revalidated + // against hashFinalSaplingRoot on the next start. + auto checkpointProgress = [&](const CBlockIndex* pindexStopped) { + if (!fMayCheckpoint || !pindexStopped || !pindexStopped->pprev) + return; + const CBlockIndex* pindexDone = pindexStopped->pprev; + if (pindexDone->GetHeight() <= pindexPersisted->GetHeight()) + return; // never move the locator backwards + if (SetBestChainNoFlush(chainActive.GetLocator(pindexDone))) { + LogPrintf("%s: checkpointed scan progress at height %d\n", __func__, pindexDone->GetHeight()); + } else { + LogPrintf("%s: FAILED to checkpoint scan progress at height %d; the scan will replay " + "from height %d on the next start\n", __func__, pindexDone->GetHeight(), + pindexPersisted->GetHeight()); + } + }; + ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup double dProgressStart = Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), pindex, false); double dProgressTip = Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), chainActive.LastTip(), false); @@ -3478,11 +3523,13 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate) pwalletMain->fRescanning = false; pwalletMain->fAbortRescan = false; // consume it; see the note at scan entry LogPrintf("%s: Rescan aborted at block %d\n", __func__, pwalletMain->rescanHeight); + checkpointProgress(pindex); return ret; } if (ShutdownRequested()) { pwalletMain->fRescanning = false; LogPrintf("%s: Rescan interrupted by shutdown request at block %d\n", __func__, pwalletMain->rescanHeight); + checkpointProgress(pindex); return ret; }