Coverage Report

Created: 2026-09-14 20:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/tmp/bitcoin/src/wallet/scan.h
Line
Count
Source
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
1.11k
    explicit WalletRescanReserver(CWallet& w) : m_wallet(w) {}
46
47
    bool reserve(bool with_passphrase = false);
48
    bool isReserved() const;
49
50
84.0k
    Clock::time_point now() const { return m_now ? m_now() : Clock::now(); };
51
52
2
    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
1.17k
    explicit ChainScanner(CWallet& wallet) : m_wallet(wallet) {}
93
94
3
    void Abort() { m_abort = true; }
95
1.07k
    bool IsAborting() const { return m_abort; }
96
1.61k
    bool IsScanning() const { return m_scanning; }
97
55
    bool IsScanningWithPassphrase() const { return m_scanning_with_passphrase; }
98
1
    SteadyClock::duration ScanningDuration() const { return m_scanning ? SteadyClock::now() - m_scanning_start.load() : SteadyClock::duration{}; }
99
2
    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