Coverage Report

Created: 2026-04-29 19:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/tmp/bitcoin/src/wallet/load.cpp
Line
Count
Source
1
// Copyright (c) 2009-2010 Satoshi Nakamoto
2
// Copyright (c) 2009-present 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
405
{
29
405
    interfaces::Chain& chain = *context.chain;
30
405
    ArgsManager& args = *Assert(context.args);
31
32
405
    if (args.IsArgSet("-walletdir")) {
33
24
        const fs::path wallet_dir{args.GetPathArg("-walletdir")};
34
24
        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
24
        fs::path canonical_wallet_dir = fs::canonical(wallet_dir, error);
39
24
        if (error || !fs::exists(canonical_wallet_dir)) {
40
5
            chain.initError(strprintf(_("Specified -walletdir \"%s\" does not exist"), fs::PathToString(wallet_dir)));
41
5
            return false;
42
19
        } else if (!fs::is_directory(canonical_wallet_dir)) {
43
5
            chain.initError(strprintf(_("Specified -walletdir \"%s\" is not a directory"), fs::PathToString(wallet_dir)));
44
5
            return false;
45
        // The canonical path transforms relative paths into absolute ones, so we check the non-canonical version
46
14
        } else if (!wallet_dir.is_absolute()) {
47
3
            chain.initError(strprintf(_("Specified -walletdir \"%s\" is a relative path"), fs::PathToString(wallet_dir)));
48
3
            return false;
49
3
        }
50
11
        args.ForceSetArg("-walletdir", fs::PathToString(canonical_wallet_dir));
51
11
    }
52
53
392
    LogInfo("Using wallet directory %s", fs::PathToString(GetWalletDir()));
54
    // Print general DB information
55
392
    LogDBInfo();
56
57
392
    chain.initMessage(_("Verifying wallet(s)…"));
58
59
    // For backwards compatibility if an unnamed top level wallet exists in the
60
    // wallets directory, include it in the default list of wallets to load.
61
392
    if (!args.IsArgSet("wallet")) {
62
279
        DatabaseOptions options;
63
279
        DatabaseStatus status;
64
279
        ReadDatabaseArgs(args, options);
65
279
        bilingual_str error_string;
66
279
        options.require_existing = true;
67
279
        options.verify = false;
68
279
        if (MakeWalletDatabase("", options, status, error_string)) {
69
1
            common::SettingsValue wallets(common::SettingsValue::VARR);
70
1
            wallets.push_back(""); // Default wallet name is ""
71
            // Pass write=false because no need to write file and probably
72
            // better not to. If unnamed wallet needs to be added next startup
73
            // and the setting is empty, this code will just run again.
74
1
            chain.overwriteRwSetting("wallet", std::move(wallets), interfaces::SettingsAction::SKIP_WRITE);
75
1
        }
76
279
    }
77
78
    // Keep track of each wallet absolute path to detect duplicates.
79
392
    std::set<fs::path> wallet_paths;
80
81
392
    for (const auto& wallet : chain.getSettingsList("wallet")) {
82
107
        if (!wallet.isStr()) {
83
8
            chain.initError(_("Invalid value detected for '-wallet' or '-nowallet'. "
84
8
                              "'-wallet' requires a string value, while '-nowallet' accepts only '1' to disable all wallets"));
85
8
            return false;
86
8
        }
87
99
        const auto& wallet_file = wallet.get_str();
88
99
        const fs::path path = fsbridge::AbsPathJoin(GetWalletDir(), fs::PathFromString(wallet_file));
89
90
99
        if (!wallet_paths.insert(path).second) {
91
2
            chain.initWarning(strprintf(_("Ignoring duplicate -wallet %s."), wallet_file));
92
2
            continue;
93
2
        }
94
95
97
        DatabaseOptions options;
96
97
        DatabaseStatus status;
97
97
        ReadDatabaseArgs(args, options);
98
97
        options.require_existing = true;
99
97
        options.verify = true;
100
97
        bilingual_str error_string;
101
97
        if (!MakeWalletDatabase(wallet_file, options, status, error_string)) {
102
6
            if (status == DatabaseStatus::FAILED_NOT_FOUND) {
103
0
                chain.initWarning(Untranslated(strprintf("Skipping -wallet path that doesn't exist. %s", error_string.original)));
104
6
            } else if (status == DatabaseStatus::FAILED_LEGACY_DISABLED) {
105
                // Skipping legacy wallets as they will not be loaded.
106
                // This will be properly communicated to the user during the loading process.
107
1
                continue;
108
5
            } else {
109
5
                chain.initError(error_string);
110
5
                return false;
111
5
            }
112
6
        }
113
97
    }
114
115
379
    return true;
116
392
}
117
118
bool LoadWallets(WalletContext& context)
119
347
{
120
347
    interfaces::Chain& chain = *context.chain;
121
347
    try {
122
347
        std::set<fs::path> wallet_paths;
123
347
        for (const auto& wallet : chain.getSettingsList("wallet")) {
124
92
            if (!wallet.isStr()) {
125
0
                chain.initError(_("Invalid value detected for '-wallet' or '-nowallet'. "
126
0
                                  "'-wallet' requires a string value, while '-nowallet' accepts only '1' to disable all wallets"));
127
0
                return false;
128
0
            }
129
92
            const auto& name = wallet.get_str();
130
92
            if (!wallet_paths.insert(fs::PathFromString(name)).second) {
131
2
                continue;
132
2
            }
133
90
            DatabaseOptions options;
134
90
            DatabaseStatus status;
135
90
            ReadDatabaseArgs(*context.args, options);
136
90
            options.require_existing = true;
137
90
            options.verify = false; // No need to verify, assuming verified earlier in VerifyWallets()
138
90
            bilingual_str error;
139
90
            std::vector<bilingual_str> warnings;
140
90
            std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(name, options, status, error);
141
90
            if (!database) {
142
1
                if (status == DatabaseStatus::FAILED_NOT_FOUND) continue;
143
1
                if (status == DatabaseStatus::FAILED_LEGACY_DISABLED) {
144
                    // Inform user that legacy wallet is not loaded and suggest upgrade options
145
1
                    chain.initWarning(error);
146
1
                    continue;
147
1
                }
148
1
            }
149
89
            chain.initMessage(_("Loading wallet…"));
150
89
            std::shared_ptr<CWallet> pwallet = database ? CWallet::LoadExisting(context, name, std::move(database), error, warnings) : nullptr;
151
89
            if (!warnings.empty()) chain.initWarning(Join(warnings, Untranslated("\n")));
152
89
            if (!pwallet) {
153
0
                chain.initError(error);
154
0
                return false;
155
0
            }
156
157
89
            NotifyWalletLoaded(context, pwallet);
158
89
            AddWallet(context, pwallet);
159
89
        }
160
347
        return true;
161
347
    } catch (const std::runtime_error& e) {
162
0
        chain.initError(Untranslated(e.what()));
163
0
        return false;
164
0
    }
165
347
}
166
167
void StartWallets(WalletContext& context)
168
345
{
169
345
    for (const std::shared_ptr<CWallet>& pwallet : GetWallets(context)) {
170
89
        pwallet->postInitProcess();
171
89
    }
172
173
345
    context.scheduler->scheduleEvery([&context] { MaybeResendWalletTxs(context); }, 1min);
174
345
}
175
176
void UnloadWallets(WalletContext& context)
177
819
{
178
819
    auto wallets = GetWallets(context);
179
1.40k
    while (!wallets.empty()) {
180
586
        auto wallet = wallets.back();
181
586
        wallets.pop_back();
182
586
        std::vector<bilingual_str> warnings;
183
586
        RemoveWallet(context, wallet, /* load_on_start= */ std::nullopt, warnings);
184
586
        WaitForDeleteWallet(std::move(wallet));
185
586
    }
186
819
}
187
} // namespace wallet