fuzz coverage

Coverage Report

Created: 2025-09-17 22:41

/Users/eugenesiegel/btc/bitcoin/src/wallet/load.cpp
Line
Count
Source (jump to first uncovered line)
1
// Copyright (c) 2009-2010 Satoshi Nakamoto
2
// Copyright (c) 2009-2022 The Bitcoin Core developers
3
// Distributed under the MIT software license, see the accompanying
4
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
5
6
#include <wallet/load.h>
7
8
#include <common/args.h>
9
#include <interfaces/chain.h>
10
#include <scheduler.h>
11
#include <util/check.h>
12
#include <util/fs.h>
13
#include <util/string.h>
14
#include <util/translation.h>
15
#include <wallet/context.h>
16
#include <wallet/spend.h>
17
#include <wallet/wallet.h>
18
#include <wallet/walletdb.h>
19
20
#include <univalue.h>
21
22
#include <system_error>
23
24
using util::Join;
25
26
namespace wallet {
27
bool VerifyWallets(WalletContext& context)
28
0
{
29
0
    interfaces::Chain& chain = *context.chain;
30
0
    ArgsManager& args = *Assert(context.args);
Line
Count
Source
106
0
#define Assert(val) inline_assertion_check<true>(val, __FILE__, __LINE__, __func__, #val)
31
32
0
    if (args.IsArgSet("-walletdir")) {
33
0
        const fs::path wallet_dir{args.GetPathArg("-walletdir")};
34
0
        std::error_code error;
35
        // The canonical path cleans the path, preventing >1 Berkeley environment instances for the same directory
36
        // It also lets the fs::exists and fs::is_directory checks below pass on windows, since they return false
37
        // if a path has trailing slashes, and it strips trailing slashes.
38
0
        fs::path canonical_wallet_dir = fs::canonical(wallet_dir, error);
39
0
        if (error || !fs::exists(canonical_wallet_dir)) {
40
0
            chain.initError(strprintf(_("Specified -walletdir \"%s\" does not exist"), fs::PathToString(wallet_dir)));
Line
Count
Source
1172
0
#define strprintf tfm::format
41
0
            return false;
42
0
        } else if (!fs::is_directory(canonical_wallet_dir)) {
43
0
            chain.initError(strprintf(_("Specified -walletdir \"%s\" is not a directory"), fs::PathToString(wallet_dir)));
Line
Count
Source
1172
0
#define strprintf tfm::format
44
0
            return false;
45
        // The canonical path transforms relative paths into absolute ones, so we check the non-canonical version
46
0
        } else if (!wallet_dir.is_absolute()) {
47
0
            chain.initError(strprintf(_("Specified -walletdir \"%s\" is a relative path"), fs::PathToString(wallet_dir)));
Line
Count
Source
1172
0
#define strprintf tfm::format
48
0
            return false;
49
0
        }
50
0
        args.ForceSetArg("-walletdir", fs::PathToString(canonical_wallet_dir));
51
0
    }
52
53
0
    LogInfo("Using wallet directory %s", fs::PathToString(GetWalletDir()));
Line
Count
Source
356
0
#define LogInfo(...) LogPrintLevel_(BCLog::LogFlags::ALL, BCLog::Level::Info, /*should_ratelimit=*/true, __VA_ARGS__)
Line
Count
Source
350
0
#define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__)
54
55
0
    chain.initMessage(_("Verifying wallet(s)…"));
56
57
    // For backwards compatibility if an unnamed top level wallet exists in the
58
    // wallets directory, include it in the default list of wallets to load.
59
0
    if (!args.IsArgSet("wallet")) {
60
0
        DatabaseOptions options;
61
0
        DatabaseStatus status;
62
0
        ReadDatabaseArgs(args, options);
63
0
        bilingual_str error_string;
64
0
        options.require_existing = true;
65
0
        options.verify = false;
66
0
        if (MakeWalletDatabase("", options, status, error_string)) {
67
0
            common::SettingsValue wallets(common::SettingsValue::VARR);
68
0
            wallets.push_back(""); // Default wallet name is ""
69
            // Pass write=false because no need to write file and probably
70
            // better not to. If unnamed wallet needs to be added next startup
71
            // and the setting is empty, this code will just run again.
72
0
            chain.overwriteRwSetting("wallet", std::move(wallets), interfaces::SettingsAction::SKIP_WRITE);
73
0
        }
74
0
    }
75
76
    // Keep track of each wallet absolute path to detect duplicates.
77
0
    std::set<fs::path> wallet_paths;
78
79
0
    for (const auto& wallet : chain.getSettingsList("wallet")) {
80
0
        if (!wallet.isStr()) {
81
0
            chain.initError(_("Invalid value detected for '-wallet' or '-nowallet'. "
82
0
                              "'-wallet' requires a string value, while '-nowallet' accepts only '1' to disable all wallets"));
83
0
            return false;
84
0
        }
85
0
        const auto& wallet_file = wallet.get_str();
86
0
        const fs::path path = fsbridge::AbsPathJoin(GetWalletDir(), fs::PathFromString(wallet_file));
87
88
0
        if (!wallet_paths.insert(path).second) {
89
0
            chain.initWarning(strprintf(_("Ignoring duplicate -wallet %s."), wallet_file));
Line
Count
Source
1172
0
#define strprintf tfm::format
90
0
            continue;
91
0
        }
92
93
0
        DatabaseOptions options;
94
0
        DatabaseStatus status;
95
0
        ReadDatabaseArgs(args, options);
96
0
        options.require_existing = true;
97
0
        options.verify = true;
98
0
        bilingual_str error_string;
99
0
        if (!MakeWalletDatabase(wallet_file, options, status, error_string)) {
100
0
            if (status == DatabaseStatus::FAILED_NOT_FOUND) {
101
0
                chain.initWarning(Untranslated(strprintf("Skipping -wallet path that doesn't exist. %s", error_string.original)));
Line
Count
Source
1172
0
#define strprintf tfm::format
102
0
            } else if (status == DatabaseStatus::FAILED_LEGACY_DISABLED) {
103
                // Skipping legacy wallets as they will not be loaded.
104
                // This will be properly communicated to the user during the loading process.
105
0
                continue;
106
0
            } else {
107
0
                chain.initError(error_string);
108
0
                return false;
109
0
            }
110
0
        }
111
0
    }
112
113
0
    return true;
114
0
}
115
116
bool LoadWallets(WalletContext& context)
117
0
{
118
0
    interfaces::Chain& chain = *context.chain;
119
0
    try {
120
0
        std::set<fs::path> wallet_paths;
121
0
        for (const auto& wallet : chain.getSettingsList("wallet")) {
122
0
            if (!wallet.isStr()) {
123
0
                chain.initError(_("Invalid value detected for '-wallet' or '-nowallet'. "
124
0
                                  "'-wallet' requires a string value, while '-nowallet' accepts only '1' to disable all wallets"));
125
0
                return false;
126
0
            }
127
0
            const auto& name = wallet.get_str();
128
0
            if (!wallet_paths.insert(fs::PathFromString(name)).second) {
129
0
                continue;
130
0
            }
131
0
            DatabaseOptions options;
132
0
            DatabaseStatus status;
133
0
            ReadDatabaseArgs(*context.args, options);
134
0
            options.require_existing = true;
135
0
            options.verify = false; // No need to verify, assuming verified earlier in VerifyWallets()
136
0
            bilingual_str error;
137
0
            std::vector<bilingual_str> warnings;
138
0
            std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(name, options, status, error);
139
0
            if (!database) {
140
0
                if (status == DatabaseStatus::FAILED_NOT_FOUND) continue;
141
0
                if (status == DatabaseStatus::FAILED_LEGACY_DISABLED) {
142
                    // Inform user that legacy wallet is not loaded and suggest upgrade options
143
0
                    chain.initWarning(error);
144
0
                    continue;
145
0
                }
146
0
            }
147
0
            chain.initMessage(_("Loading wallet…"));
148
0
            std::shared_ptr<CWallet> pwallet = database ? CWallet::Create(context, name, std::move(database), options.create_flags, error, warnings) : nullptr;
149
0
            if (!warnings.empty()) chain.initWarning(Join(warnings, Untranslated("\n")));
150
0
            if (!pwallet) {
151
0
                chain.initError(error);
152
0
                return false;
153
0
            }
154
155
0
            NotifyWalletLoaded(context, pwallet);
156
0
            AddWallet(context, pwallet);
157
0
        }
158
0
        return true;
159
0
    } catch (const std::runtime_error& e) {
160
0
        chain.initError(Untranslated(e.what()));
161
0
        return false;
162
0
    }
163
0
}
164
165
void StartWallets(WalletContext& context)
166
0
{
167
0
    for (const std::shared_ptr<CWallet>& pwallet : GetWallets(context)) {
168
0
        pwallet->postInitProcess();
169
0
    }
170
171
0
    context.scheduler->scheduleEvery([&context] { MaybeResendWalletTxs(context); }, 1min);
172
0
}
173
174
void UnloadWallets(WalletContext& context)
175
0
{
176
0
    auto wallets = GetWallets(context);
177
0
    while (!wallets.empty()) {
178
0
        auto wallet = wallets.back();
179
0
        wallets.pop_back();
180
0
        std::vector<bilingual_str> warnings;
181
0
        RemoveWallet(context, wallet, /* load_on_start= */ std::nullopt, warnings);
182
0
        WaitForDeleteWallet(std::move(wallet));
183
0
    }
184
0
}
185
} // namespace wallet