Branch data Line data Source code
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 : : #ifndef BITCOIN_WALLET_SCAN_H
6 : : #define BITCOIN_WALLET_SCAN_H
7 : :
8 : : #include <uint256.h>
9 : : #include <util/time.h>
10 : :
11 : : #include <atomic>
12 : : #include <functional>
13 : : #include <optional>
14 : :
15 : : namespace wallet {
16 : : class CWallet;
17 : :
18 : : /** Result of a wallet scan */
19 : : struct ScanResult {
20 : : enum { SUCCESS, FAILURE, USER_ABORT } status = SUCCESS;
21 : :
22 : : //! Hash and height of most recent block that was successfully scanned.
23 : : //! Unset if no blocks were scanned due to read errors or the chain
24 : : //! being empty.
25 : : uint256 last_scanned_block;
26 : : std::optional<int> last_scanned_height;
27 : :
28 : : //! Height of the most recent block that could not be scanned due to
29 : : //! read errors or pruning. Will be set if status is FAILURE, unset if
30 : : //! status is SUCCESS, and may or may not be set if status is
31 : : //! USER_ABORT.
32 : : uint256 last_failed_block;
33 : : };
34 : :
35 : : /** RAII object to check and reserve a wallet rescan */
36 : : class WalletRescanReserver
37 : : {
38 : : private:
39 : : using Clock = std::chrono::steady_clock;
40 : : using NowFn = std::function<Clock::time_point()>;
41 : : CWallet& m_wallet;
42 : : bool m_could_reserve{false};
43 : : NowFn m_now;
44 : : public:
45 [ # # ]: 0 : explicit WalletRescanReserver(CWallet& w) : m_wallet(w) {}
46 : :
47 : : bool reserve(bool with_passphrase = false);
48 : : bool isReserved() const;
49 : :
50 [ # # ]: 0 : Clock::time_point now() const { return m_now ? m_now() : Clock::now(); };
51 : :
52 : : void setNow(NowFn now) { m_now = std::move(now); }
53 : :
54 : : ~WalletRescanReserver();
55 : : };
56 : :
57 : : class ChainScanner {
58 : : private:
59 : : CWallet& m_wallet;
60 : :
61 : : std::atomic<bool> m_abort{false};
62 : : std::atomic<bool> m_scanning{false};
63 : : std::atomic<bool> m_scanning_with_passphrase{false};
64 : : std::atomic<SteadyClock::time_point> m_scanning_start{SteadyClock::time_point{}};
65 : : std::atomic<double> m_scanning_progress{0};
66 : :
67 : : //! Progress window and tip tracked across Scan loop iterations. The
68 : : //! current block's progress is a plain local in Scan; only the window
69 : : //! bounds are shared with the helpers, and UpdateTipIfChanged is the
70 : : //! sole mutator.
71 : : struct LoopState {
72 : : double progress_begin{0};
73 : : double progress_end{0};
74 : : uint256 tip_hash;
75 : : };
76 : :
77 : : //! Locate block_hash in the chain, queueing its active-chain successor
78 : : //! into next_block if it exists and is within the scan range. Returns
79 : : //! whether the block itself is still in the active chain.
80 : : bool QueueNextBlock(const uint256& block_hash, int block_height, std::optional<std::pair<uint256, int>>& next_block, std::optional<int> max_height);
81 : : bool ScanBlock(const uint256& block_hash, int block_height, bool save_progress);
82 : : void UpdateProgress(const LoopState& state, double progress_current, int block_height);
83 : : void UpdateTipIfChanged(LoopState& state);
84 : :
85 : : //! Only WalletRescanReserver may reserve and release scans, so that
86 : : //! reservations are always managed RAII-style.
87 : : friend class WalletRescanReserver;
88 : : bool TryReserve(bool with_passphrase = false);
89 : : void Release();
90 : :
91 : : public:
92 : 9683 : explicit ChainScanner(CWallet& wallet) : m_wallet(wallet) {}
93 : :
94 [ # # ]: 0 : void Abort() { m_abort = true; }
95 [ # # ]: 0 : bool IsAborting() const { return m_abort; }
96 [ # # ]: 0 : bool IsScanning() const { return m_scanning; }
97 [ # # # # : 0 : bool IsScanningWithPassphrase() const { return m_scanning_with_passphrase; }
# # ]
98 [ # # ]: 0 : SteadyClock::duration ScanningDuration() const { return m_scanning ? SteadyClock::now() - m_scanning_start.load() : SteadyClock::duration{}; }
99 [ # # ]: 0 : double ScanningProgress() const { return m_scanning ? m_scanning_progress.load() : 0; }
100 : :
101 : : /** Scan active chain for relevant transactions after importing keys. Should
102 : : * be called whenever new keys are added to the wallet, with the oldest key
103 : : * creation time.
104 : : * @return Earliest timestamp that could be successfully scanned from. Timestamp
105 : : * returned will be higher than startTime if relevant blocks could not be read. */
106 : : int64_t ScanFromTime(int64_t startTime, const WalletRescanReserver& reserver);
107 : :
108 : : /**
109 : : * Scan the block chain (starting in start_block) for transactions
110 : : * from or to us. If max_height is not set, the
111 : : * mempool will be scanned as well.
112 : : *
113 : : * @param[in] start_block Scan starting block. If block is not on the active
114 : : * chain, the scan will return SUCCESS immediately.
115 : : * @param[in] start_height Height of start_block
116 : : * @param[in] max_height Optional max scanning height. If unset there is
117 : : * no maximum and scanning can continue to the tip
118 : : *
119 : : * @return ScanResult returning scan information and indicating success or
120 : : * failure. Return status will be set to SUCCESS if scan was
121 : : * successful. FAILURE if a complete rescan was not possible (due to
122 : : * pruning or corruption). USER_ABORT if the rescan was aborted before
123 : : * it could complete.
124 : : *
125 : : * @pre Caller needs to make sure start_block (and the optional stop_block) are on
126 : : * the main chain after the addition of any new keys you want to detect
127 : : * transactions for.
128 : : */
129 : : ScanResult Scan(const uint256& start_block, int start_height, std::optional<int> max_height,
130 : : const WalletRescanReserver& reserver, bool save_progress);
131 : :
132 : : };
133 : :
134 : : } // namespace wallet
135 : :
136 : : #endif // BITCOIN_WALLET_SCAN_H
|