Bitcoin Core 32.99.0
P2P Digital Currency
scan.cpp
Go to the documentation of this file.
1// Copyright (c) 2026-present The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5#include <chain.h>
6#include <interfaces/chain.h>
7#include <logging.h>
8#include <primitives/block.h>
9#include <sync.h>
10#include <util/check.h>
11#include <wallet/scan.h>
12#include <wallet/wallet.h>
13
15
16namespace wallet {
17
18int64_t ChainScanner::ScanFromTime(int64_t startTime, const WalletRescanReserver& reserver)
19{
20 // Find starting block. May be null if nCreateTime is greater than the
21 // highest blockchain timestamp, in which case there is nothing that needs
22 // to be scanned.
23 int start_height = 0;
24 uint256 start_block;
25 bool start = m_wallet.chain().findFirstBlockWithTimeAndHeight(startTime - TIMESTAMP_WINDOW, 0, FoundBlock().hash(start_block).height(start_height));
26 m_wallet.WalletLogPrintf("%s: Rescanning last %i blocks\n", __func__, start ? WITH_LOCK(m_wallet.cs_wallet, return m_wallet.GetLastBlockHeight()) - start_height + 1 : 0);
27
28 if (start) {
29 // TODO: this should take into account failure by ScanResult::USER_ABORT
30 ScanResult result = Scan(start_block, start_height, /*max_height=*/{}, reserver, /*save_progress=*/false);
31 if (result.status == ScanResult::FAILURE) {
32 int64_t time_max;
33 CHECK_NONFATAL(m_wallet.chain().findBlock(result.last_failed_block, FoundBlock().maxTime(time_max)));
34 return time_max + TIMESTAMP_WINDOW + 1;
35 }
36 }
37 return startTime;
38}
39
40bool WalletRescanReserver::reserve(bool with_passphrase) {
42 if (!m_wallet.Scanner().TryReserve(with_passphrase)) {
43 return false;
44 }
45 m_could_reserve = true;
46 return true;
47}
48
51}
52
54 if (m_could_reserve) {
56 }
57}
58
59bool ChainScanner::TryReserve(bool with_passphrase) {
60 if (m_scanning.exchange(true)) return false;
61 // Discard any abort request left over from previous reservation, so
62 // that an abort requested while the reservation is held always applies
63 // to abort this rescan, even if it arrives before the scan loop starts.
64 m_abort = false;
65 m_scanning_with_passphrase = with_passphrase;
66 m_scanning_start = SteadyClock::now();
68 return true;
69}
70
72 m_scanning = false;
74}
75
76namespace {
77class FastWalletRescanFilter
78{
79public:
80 FastWalletRescanFilter(const CWallet& wallet) : m_wallet(wallet)
81 {
82 // create initial filter with scripts from all ScriptPubKeyMans
83 for (auto spkm : m_wallet.GetAllScriptPubKeyMans()) {
84 auto desc_spkm{dynamic_cast<DescriptorScriptPubKeyMan*>(spkm)};
85 assert(desc_spkm != nullptr);
86 AddScriptPubKeys(desc_spkm);
87 // save each range descriptor's end for possible future filter updates
88 if (desc_spkm->IsHDEnabled()) {
89 m_last_range_ends.emplace(desc_spkm->GetID(), desc_spkm->GetEndRange());
90 }
91 }
92 }
93
94 void UpdateIfNeeded()
95 {
96 // repopulate filter with new scripts if top-up has happened since last iteration
97 for (const auto& [desc_spkm_id, last_range_end] : m_last_range_ends) {
98 auto desc_spkm{dynamic_cast<DescriptorScriptPubKeyMan*>(m_wallet.GetScriptPubKeyMan(desc_spkm_id))};
99 assert(desc_spkm != nullptr);
100 int32_t current_range_end{desc_spkm->GetEndRange()};
101 if (current_range_end > last_range_end) {
102 AddScriptPubKeys(desc_spkm, last_range_end);
103 m_last_range_ends.at(desc_spkm->GetID()) = current_range_end;
104 }
105 }
106 }
107
108 std::optional<bool> MatchesBlock(const uint256& block_hash) const
109 {
110 return m_wallet.chain().blockFilterMatchesAny(BlockFilterType::BASIC, block_hash, m_filter_set);
111 }
112
113private:
114 const CWallet& m_wallet;
121 std::map<uint256, int32_t> m_last_range_ends;
123
124 void AddScriptPubKeys(const DescriptorScriptPubKeyMan* desc_spkm, int32_t last_range_end = 0)
125 {
126 for (const auto& script_pub_key : desc_spkm->GetScriptPubKeys(last_range_end)) {
127 m_filter_set.emplace(script_pub_key.begin(), script_pub_key.end());
128 }
129 }
130};
131
132static bool ShouldFetchBlock(const FastWalletRescanFilter& filter, const uint256& block_hash, int block_height) {
133 auto matches_block{filter.MatchesBlock(block_hash)};
134 if (matches_block.has_value()) {
135 if (*matches_block) {
136 LogDebug(BCLog::SCAN, "Fast rescan: inspect block %d [%s] (filter matched)\n", block_height, block_hash.ToString());
137 return true;
138 } else {
139 return false;
140 }
141 } else {
142 LogDebug(BCLog::SCAN, "Fast rescan: inspect block %d [%s] (WARNING: block filter not found!)\n", block_height, block_hash.ToString());
143 return true;
144 }
145}
146} // namespace
147
148bool ChainScanner::QueueNextBlock(const uint256& block_hash, int block_height, std::optional<std::pair<uint256, int>>& next_block, std::optional<int> max_height) {
149 bool block_still_active = false;
150 bool has_next_block = false;
151 uint256 next_block_hash;
152 m_wallet.chain().findBlock(block_hash, FoundBlock().inActiveChain(block_still_active).nextBlock(FoundBlock().inActiveChain(has_next_block).hash(next_block_hash)));
153
154 // Queue the next block if it exists and is within range. Whether the scan
155 // has caught up with the wallet's tip is checked after the current block
156 // is processed, so blocks connected while it was being processed are not
157 // missed.
158 if (has_next_block && (!max_height || block_height < *max_height)) {
159 next_block = {{next_block_hash, block_height + 1}};
160 }
161
162 return block_still_active;
163}
164
165void ChainScanner::UpdateProgress(const LoopState& state, double progress_current, int block_height) {
167 double progress_diff = state.progress_end - state.progress_begin;
168
169 // avoid divide-by-zero for single block scan range (i.e. start and stop hashes are equal)
170 if (progress_diff <= 0.0) return;
171 m_scanning_progress = (progress_current - state.progress_begin) / progress_diff;
172
173 if (block_height % 100 == 0) {
174 m_wallet.ShowProgress(strprintf("[%s] %s", m_wallet.DisplayName(), _("Rescanning…")),
175 std::max(1, std::min(99, (int)(m_scanning_progress.load() * 100))));
176 }
177}
178
180 const uint256 new_tip = WITH_LOCK(m_wallet.cs_wallet, return m_wallet.GetLastBlockHash());
181 if (new_tip != state.tip_hash) {
182 state.tip_hash = new_tip;
184 }
185}
186
187bool ChainScanner::ScanBlock(const uint256& block_hash, int block_height, bool save_progress) {
188 // Read block data and locator if needed (the locator is usually null unless we need to save progress)
189 CBlock block;
190 CBlockLocator loc;
191 // Find block
192 FoundBlock found_block{FoundBlock().data(block)};
193 if (save_progress) found_block.locator(loc);
194 m_wallet.chain().findBlock(block_hash, found_block);
195
196 if (block.IsNull()) return false;
197
198 {
199 // cs_wallet is a RecursiveMutex; ScanBlock may be called
200 // with cs_wallet already held as in AttachChain or without it.
202 for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
204 block.vtx[posInBlock], TxStateConfirmed{block_hash, block_height,
205 static_cast<int>(posInBlock)},
206 /*rescanning_old_block=*/true);
207 }
208
209 if (!loc.IsNull()) {
210 m_wallet.WalletLogPrintf("Saving scan progress %d.\n", block_height);
212 batch.WriteBestBlock(loc);
213 }
214 }
215 return true;
216}
217
218ScanResult ChainScanner::Scan(const uint256& start_block, int start_height, std::optional<int> max_height,
219 const WalletRescanReserver& reserver, bool save_progress) {
220 constexpr auto INTERVAL_TIME{60s};
221 auto current_time{reserver.now()};
222 auto start_time{reserver.now()};
223
224 assert(reserver.isReserved());
225 auto& chain = m_wallet.chain();
226
227 std::unique_ptr<FastWalletRescanFilter> fast_rescan_filter;
228 if (chain.hasBlockFilterIndex(BlockFilterType::BASIC)) fast_rescan_filter = std::make_unique<FastWalletRescanFilter>(m_wallet);
229
230 m_wallet.WalletLogPrintf("Rescan started from block %s... (%s)\n", start_block.ToString(),
231 fast_rescan_filter ? "fast variant using block filters" : "slow variant inspecting all blocks");
232
233 // show rescan progress in GUI as dialog or on splashscreen, if rescan required on startup (e.g. due to corruption)
234 m_wallet.ShowProgress(strprintf("[%s] %s", m_wallet.DisplayName(), _("Rescanning…")), 0);
235
236 ScanResult result;
237 LoopState state;
239 uint256 end_hash = state.tip_hash;
240 if (max_height) chain.findAncestorByHeight(state.tip_hash, *max_height, FoundBlock().hash(end_hash));
241 state.progress_begin = chain.guessVerificationProgress(start_block);
242 state.progress_end = chain.guessVerificationProgress(end_hash);
243 double progress_current = state.progress_begin;
244 std::optional<std::pair<uint256, int>> next_block = {{start_block, start_height}};
245 int block_height = start_height;
246 while (!m_abort && !chain.shutdownRequested()) {
247 if (!next_block) break;
248
249 const uint256 block_hash = next_block->first;
250 block_height = next_block->second;
251 next_block.reset();
252 // Look up the current block's position separately from reading its
253 // data below, because reading is slow and there might be a reorg
254 // while it is read.
255 const bool block_still_active = QueueNextBlock(block_hash, block_height, next_block, max_height);
256
257 progress_current = chain.guessVerificationProgress(block_hash);
258 UpdateProgress(state, progress_current, block_height);
259
260 bool next_interval = reserver.now() >= current_time + INTERVAL_TIME;
261 if (next_interval) {
262 current_time = reserver.now();
263 m_wallet.WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", block_height, progress_current);
264 }
265
266 bool fetch_block{true};
267 if (fast_rescan_filter) {
268 fast_rescan_filter->UpdateIfNeeded();
269 fetch_block = ShouldFetchBlock(*fast_rescan_filter, block_hash, block_height);
270 }
271
272 if (fetch_block && !block_still_active) {
273 // Abort scan if a block that needs to be inspected is no longer
274 // active, to prevent marking transactions as coming from the
275 // wrong block. A block skipped by the filter can stay skipped:
276 // it has no successor in the active chain, so the scan ends
277 // successfully at the reorg point and the replacement blocks are
278 // handled by blockConnected notifications.
279 result.last_failed_block = block_hash;
281 break;
282 }
283 if (!fetch_block || ScanBlock(block_hash, block_height, save_progress && next_interval)) {
284 // scanned the block, or skipped it via the filter: record it as
285 // the most recent successfully scanned block
286 result.last_scanned_block = block_hash;
287 result.last_scanned_height = block_height;
288 } else {
289 // could not scan block, keep scanning but record this block as the most recent failure
290 result.last_failed_block = block_hash;
292 }
293
294 // Stop scanning once the wallet's tip is reached, re-reading the height
295 // after the block was processed so a tip extension that happened
296 // meanwhile is picked up. If scanning with cs_wallet locked (AttachChain),
297 // blocks connected during rescan are handled after scanning is complete
298 // via blockConnected notifications. Without the lock, newly added blocks
299 // are re-processed here if the notifications were handled and the last
300 // block height was updated.
301 if (block_height >= WITH_LOCK(m_wallet.cs_wallet, return m_wallet.GetLastBlockHeight())) {
302 break;
303 }
304
305 if (!max_height) UpdateTipIfChanged(state);
306 }
307 if (!max_height) {
308 m_wallet.WalletLogPrintf("Scanning current mempool transactions.\n");
309 WITH_LOCK(m_wallet.cs_wallet, chain.requestMempoolTransactions(m_wallet));
310 }
311 m_wallet.ShowProgress(strprintf("[%s] %s", m_wallet.DisplayName(), _("Rescanning…")), 100); // hide progress dialog in GUI
312 if (m_abort) {
313 m_wallet.WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", block_height, progress_current);
315 } else if (chain.shutdownRequested()) {
316 m_wallet.WalletLogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", block_height, progress_current);
318 } else {
319 m_wallet.WalletLogPrintf("Rescan completed in %15dms\n", Ticks<std::chrono::milliseconds>(reserver.now() - start_time));
320 }
321 return result;
322}
323}
constexpr int64_t TIMESTAMP_WINDOW
Timestamp window used as a grace period by code that compares external timestamps (such as timestamps...
Definition: chain.h:37
#define CHECK_NONFATAL(condition)
Identity function.
Definition: check.h:112
bool IsNull() const
Definition: block.h:54
Definition: block.h:74
std::vector< CTransactionRef > vtx
Definition: block.h:77
std::unordered_set< Element, ByteVectorHash > ElementSet
Definition: blockfilter.h:33
std::string ToString() const
Definition: uint256.cpp:21
virtual bool findFirstBlockWithTimeAndHeight(int64_t min_time, int min_height, const FoundBlock &block={})=0
Find first block in the chain with timestamp >= the given time and height >= than the given height,...
virtual bool findBlock(const uint256 &hash, const FoundBlock &block={})=0
Return whether node has the block and optionally return block metadata or contents.
virtual double guessVerificationProgress(const uint256 &block_hash)=0
Estimate fraction of total transactions verified if blocks up to the specified block hash are verifie...
Helper for findBlock to selectively return pieces of block data.
Definition: chain.h:52
FoundBlock & locator(CBlockLocator &locator)
Return locator if block is in the active chain.
Definition: chain.h:62
FoundBlock & data(CBlock &data)
Read block data from disk.
Definition: chain.h:67
256-bit opaque blob.
Definition: uint256.h:196
A CWallet maintains a set of transactions and balances, and provides the ability to create new transa...
Definition: wallet.h:313
int GetLastBlockHeight() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet)
Get last block processed height.
Definition: wallet.h:964
ChainScanner & Scanner()
Definition: wallet.cpp:499
interfaces::Chain & chain() const
Interface for accessing chain state.
Definition: wallet.h:513
void WalletLogPrintf(util::ConstevalFormatString< sizeof...(Params)> wallet_fmt, const Params &... params) const
Prepends the wallet name in logging output to ease debugging in multi-wallet use cases.
Definition: wallet.h:916
btcsignals::signal< void(const std::string &title, int nProgress)> ShowProgress
Show progress e.g.
Definition: wallet.h:823
WalletDatabase & GetDatabase() const override
Definition: wallet.h:465
std::string DisplayName() const
Return wallet name for display, like LogName() but translates "default wallet" string.
Definition: wallet.h:908
uint256 GetLastBlockHash() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet)
Definition: wallet.h:970
RecursiveMutex cs_wallet
Main wallet lock.
Definition: wallet.h:463
std::atomic< SteadyClock::time_point > m_scanning_start
Definition: scan.h:64
void UpdateProgress(const LoopState &state, double progress_current, int block_height)
Definition: scan.cpp:165
CWallet & m_wallet
Definition: scan.h:59
bool IsScanning() const
Definition: scan.h:96
std::atomic< double > m_scanning_progress
Definition: scan.h:65
void UpdateTipIfChanged(LoopState &state)
Definition: scan.cpp:179
bool QueueNextBlock(const uint256 &block_hash, int block_height, std::optional< std::pair< uint256, int > > &next_block, std::optional< int > max_height)
Locate block_hash in the chain, queueing its active-chain successor into next_block if it exists and ...
Definition: scan.cpp:148
bool ScanBlock(const uint256 &block_hash, int block_height, bool save_progress)
Definition: scan.cpp:187
bool TryReserve(bool with_passphrase=false)
Definition: scan.cpp:59
int64_t ScanFromTime(int64_t startTime, const WalletRescanReserver &reserver)
Scan active chain for relevant transactions after importing keys.
Definition: scan.cpp:18
ScanResult Scan(const uint256 &start_block, int start_height, std::optional< int > max_height, const WalletRescanReserver &reserver, bool save_progress)
Scan the block chain (starting in start_block) for transactions from or to us.
Definition: scan.cpp:218
std::atomic< bool > m_scanning_with_passphrase
Definition: scan.h:63
std::atomic< bool > m_scanning
Definition: scan.h:62
std::atomic< bool > m_abort
Definition: scan.h:61
Access to the wallet database.
Definition: walletdb.h:197
bool WriteBestBlock(const CBlockLocator &locator)
Definition: walletdb.cpp:187
RAII object to check and reserve a wallet rescan.
Definition: scan.h:37
bool isReserved() const
Definition: scan.cpp:49
Clock::time_point now() const
Definition: scan.h:50
bool reserve(bool with_passphrase=false)
Definition: scan.cpp:40
bool SyncTransaction(const CTransactionRef &tx, const SyncTxState &state, bool rescanning_old_block=false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet)
Definition: wallet.cpp:1410
#define LogDebug(category,...)
Definition: log.h:143
@ SCAN
Definition: categories.h:44
GCSFilter::ElementSet m_filter_set
Definition: scan.cpp:122
std::map< uint256, int32_t > m_last_range_ends
Map for keeping track of each range descriptor's last seen end range.
Definition: scan.cpp:121
const CWallet & m_wallet
Definition: scan.cpp:114
Describes a place in the block chain to another node such that if the other node doesn't have the sam...
Definition: block.h:117
bool IsNull() const
Definition: block.h:145
Progress window and tip tracked across Scan loop iterations.
Definition: scan.h:71
Result of a wallet scan.
Definition: scan.h:19
uint256 last_scanned_block
Hash and height of most recent block that was successfully scanned.
Definition: scan.h:25
enum wallet::ScanResult::@19 status
std::optional< int > last_scanned_height
Definition: scan.h:26
uint256 last_failed_block
Height of the most recent block that could not be scanned due to read errors or pruning.
Definition: scan.h:32
State of transaction confirmed in a block.
Definition: transaction.h:34
#define LOCK(cs)
Definition: sync.h:268
#define WITH_LOCK(cs, code)
Run code while locking a mutex.
Definition: sync.h:299
#define strprintf
Format arguments and return the string or write to given std::ostream (see tinyformat::format doc for...
Definition: tinyformat.h:1172
consteval auto _(util::TranslatedLiteral str)
Definition: translation.h:79
assert(!tx.IsCoinBase())