load.cpp raw
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 {
29 interfaces::Chain& chain = *context.chain;
30 ArgsManager& args = *Assert(context.args);
31
32 if (args.IsArgSet("-walletdir")) {
33 const fs::path wallet_dir{args.GetPathArg("-walletdir")};
34 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 fs::path canonical_wallet_dir = fs::canonical(wallet_dir, error);
39 if (error || !fs::exists(canonical_wallet_dir)) {
40 chain.initError(strprintf(_("Specified -walletdir \"%s\" does not exist"), fs::PathToString(wallet_dir)));
41 return false;
42 } else if (!fs::is_directory(canonical_wallet_dir)) {
43 chain.initError(strprintf(_("Specified -walletdir \"%s\" is not a directory"), fs::PathToString(wallet_dir)));
44 return false;
45 // The canonical path transforms relative paths into absolute ones, so we check the non-canonical version
46 } else if (!wallet_dir.is_absolute()) {
47 chain.initError(strprintf(_("Specified -walletdir \"%s\" is a relative path"), fs::PathToString(wallet_dir)));
48 return false;
49 }
50 args.ForceSetArg("-walletdir", fs::PathToString(canonical_wallet_dir));
51 }
52
53 LogInfo("Using wallet directory %s", fs::PathToString(GetWalletDir()));
54 // Print general DB information
55 LogDBInfo();
56
57 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 if (!args.IsArgSet("wallet")) {
62 DatabaseOptions options;
63 DatabaseStatus status;
64 ReadDatabaseArgs(args, options);
65 bilingual_str error_string;
66 options.require_existing = true;
67 options.verify = false;
68 if (MakeWalletDatabase("", options, status, error_string)) {
69 common::SettingsValue wallets(common::SettingsValue::VARR);
70 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 chain.overwriteRwSetting("wallet", std::move(wallets), interfaces::SettingsAction::SKIP_WRITE);
75 }
76 }
77
78 // Keep track of each wallet absolute path to detect duplicates.
79 std::set<fs::path> wallet_paths;
80
81 for (const auto& wallet : chain.getSettingsList("wallet")) {
82 if (!wallet.isStr()) {
83 chain.initError(_("Invalid value detected for '-wallet' or '-nowallet'. "
84 "'-wallet' requires a string value, while '-nowallet' accepts only '1' to disable all wallets"));
85 return false;
86 }
87 const auto& wallet_file = wallet.get_str();
88 const fs::path path = fsbridge::AbsPathJoin(GetWalletDir(), fs::PathFromString(wallet_file));
89
90 if (!wallet_paths.insert(path).second) {
91 chain.initWarning(strprintf(_("Ignoring duplicate -wallet %s."), wallet_file));
92 continue;
93 }
94
95 DatabaseOptions options;
96 DatabaseStatus status;
97 ReadDatabaseArgs(args, options);
98 options.require_existing = true;
99 options.verify = true;
100 bilingual_str error_string;
101 if (!MakeWalletDatabase(wallet_file, options, status, error_string)) {
102 if (status == DatabaseStatus::FAILED_NOT_FOUND) {
103 chain.initWarning(Untranslated(strprintf("Skipping -wallet path that doesn't exist. %s", error_string.original)));
104 } 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 continue;
108 } else {
109 chain.initError(error_string);
110 return false;
111 }
112 }
113 }
114
115 return true;
116 }
117
118 bool LoadWallets(WalletContext& context)
119 {
120 interfaces::Chain& chain = *context.chain;
121 try {
122 std::set<fs::path> wallet_paths;
123 for (const auto& wallet : chain.getSettingsList("wallet")) {
124 if (!wallet.isStr()) {
125 chain.initError(_("Invalid value detected for '-wallet' or '-nowallet'. "
126 "'-wallet' requires a string value, while '-nowallet' accepts only '1' to disable all wallets"));
127 return false;
128 }
129 const auto& name = wallet.get_str();
130 if (!wallet_paths.insert(fs::PathFromString(name)).second) {
131 continue;
132 }
133 DatabaseOptions options;
134 DatabaseStatus status;
135 ReadDatabaseArgs(*context.args, options);
136 options.require_existing = true;
137 options.verify = false; // No need to verify, assuming verified earlier in VerifyWallets()
138 bilingual_str error;
139 std::vector<bilingual_str> warnings;
140 std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(name, options, status, error);
141 if (!database) {
142 if (status == DatabaseStatus::FAILED_NOT_FOUND) continue;
143 if (status == DatabaseStatus::FAILED_LEGACY_DISABLED) {
144 // Inform user that legacy wallet is not loaded and suggest upgrade options
145 chain.initWarning(error);
146 continue;
147 }
148 }
149 chain.initMessage(_("Loading wallet…"));
150 std::shared_ptr<CWallet> pwallet = database ? CWallet::LoadExisting(context, name, std::move(database), error, warnings) : nullptr;
151 if (!warnings.empty()) chain.initWarning(Join(warnings, Untranslated("\n")));
152 if (!pwallet) {
153 chain.initError(error);
154 return false;
155 }
156
157 NotifyWalletLoaded(context, pwallet);
158 AddWallet(context, pwallet);
159 }
160 return true;
161 } catch (const std::runtime_error& e) {
162 chain.initError(Untranslated(e.what()));
163 return false;
164 }
165 }
166
167 void StartWallets(WalletContext& context)
168 {
169 for (const std::shared_ptr<CWallet>& pwallet : GetWallets(context)) {
170 pwallet->postInitProcess();
171 }
172
173 context.scheduler->scheduleEvery([&context] { MaybeResendWalletTxs(context); }, 1min);
174 }
175
176 void UnloadWallets(WalletContext& context)
177 {
178 auto wallets = GetWallets(context);
179 while (!wallets.empty()) {
180 auto wallet = wallets.back();
181 wallets.pop_back();
182 std::vector<bilingual_str> warnings;
183 RemoveWallet(context, wallet, /* load_on_start= */ std::nullopt, warnings);
184 WaitForDeleteWallet(std::move(wallet));
185 }
186 }
187 } // namespace wallet
188