load.cpp raw
1 // Copyright (c) 2009-2010 Satoshi Nakamoto
2 // Copyright (c) 2009-2022 The Limenka 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 <node/interface_ui.h>
11 #include <scheduler.h>
12 #include <util/check.h>
13 #include <util/fs.h>
14 #include <util/string.h>
15 #include <util/translation.h>
16 #include <wallet/context.h>
17 #include <wallet/spend.h>
18 #include <wallet/wallet.h>
19 #include <wallet/walletdb.h>
20
21 #include <univalue.h>
22
23 #include <system_error>
24
25 using util::Join;
26
27 namespace wallet {
28
29 bool HandleWalletLoadError(interfaces::Chain& chain, const std::string& wallet_file, const bilingual_str& error_string)
30 {
31 if (!chain.initQuestion(error_string + Untranslated("\n\n") + _("Continue without this wallet?"), error_string, _("Error"), CClientUIInterface::MSG_ERROR | CClientUIInterface::MODAL | CClientUIInterface::BTN_OK | CClientUIInterface::BTN_ABORT)) {
32 return false;
33 }
34
35 RemoveWalletSetting(chain, wallet_file);
36 return true;
37 }
38
39 bool VerifyWallets(WalletContext& context)
40 {
41 interfaces::Chain& chain = *context.chain;
42 ArgsManager& args = *Assert(context.args);
43
44 if (args.IsArgSet("-walletdir")) {
45 const fs::path wallet_dir{args.GetPathArg("-walletdir")};
46 std::error_code error;
47 // The canonical path cleans the path, preventing >1 Berkeley environment instances for the same directory
48 // It also lets the fs::exists and fs::is_directory checks below pass on windows, since they return false
49 // if a path has trailing slashes, and it strips trailing slashes.
50 fs::path canonical_wallet_dir = fs::canonical(wallet_dir, error);
51 if (error || !fs::exists(canonical_wallet_dir)) {
52 chain.initError(strprintf(_("Specified -walletdir \"%s\" does not exist"), fs::PathToString(wallet_dir)));
53 return false;
54 } else if (!fs::is_directory(canonical_wallet_dir)) {
55 chain.initError(strprintf(_("Specified -walletdir \"%s\" is not a directory"), fs::PathToString(wallet_dir)));
56 return false;
57 // The canonical path transforms relative paths into absolute ones, so we check the non-canonical version
58 } else if (!wallet_dir.is_absolute()) {
59 chain.initError(strprintf(_("Specified -walletdir \"%s\" is a relative path"), fs::PathToString(wallet_dir)));
60 return false;
61 }
62 args.ForceSetArg("-walletdir", fs::PathToString(canonical_wallet_dir));
63 }
64
65 LogPrintf("Using wallet directory %s\n", fs::PathToString(GetWalletDir()));
66
67 chain.initMessage(_("Verifying wallet(s)…"));
68
69 // For backwards compatibility if an unnamed top level wallet exists in the
70 // wallets directory, include it in the default list of wallets to load.
71 if (!args.IsArgSet("wallet")) {
72 DatabaseOptions options;
73 DatabaseStatus status;
74 ReadDatabaseArgs(args, options);
75 bilingual_str error_string;
76 options.require_existing = true;
77 options.verify = false;
78 if (MakeWalletDatabase("", options, status, error_string)) {
79 common::SettingsValue wallets(common::SettingsValue::VARR);
80 wallets.push_back(""); // Default wallet name is ""
81 // Pass write=false because no need to write file and probably
82 // better not to. If unnamed wallet needs to be added next startup
83 // and the setting is empty, this code will just run again.
84 chain.overwriteRwSetting("wallet", std::move(wallets), interfaces::SettingsAction::SKIP_WRITE);
85 }
86 }
87
88 // Keep track of each wallet absolute path to detect duplicates.
89 std::set<fs::path> wallet_paths;
90
91 bool modified_wallet_list = false;
92 for (const auto& wallet : chain.getSettingsList("wallet")) {
93 if (!wallet.isStr()) {
94 chain.initError(_("Invalid value detected for '-wallet' or '-nowallet'. "
95 "'-wallet' requires a string value, while '-nowallet' accepts only '1' to disable all wallets"));
96 return false;
97 }
98 const auto& wallet_file = wallet.get_str();
99 const fs::path path = fsbridge::AbsPathJoin(GetWalletDir(), fs::PathFromString(wallet_file));
100
101 if (!wallet_paths.insert(path).second) {
102 chain.initWarning(strprintf(_("Ignoring duplicate -wallet %s."), wallet_file));
103 continue;
104 }
105
106 DatabaseOptions options;
107 DatabaseStatus status;
108 ReadDatabaseArgs(args, options);
109 options.require_existing = true;
110 options.verify = true;
111 bilingual_str error_string;
112 if (!MakeWalletDatabase(wallet_file, options, status, error_string)) {
113 if (status == DatabaseStatus::FAILED_NOT_FOUND) {
114 chain.initWarning(Untranslated(strprintf("Skipping -wallet path that doesn't exist. %s", error_string.original)));
115 } else {
116 if (HandleWalletLoadError(chain, wallet_file, error_string)) {
117 modified_wallet_list = true;
118 } else {
119 return false;
120 }
121 }
122 }
123 }
124
125 if (modified_wallet_list) {
126 // Ensure new wallet list overrides commandline options
127 args.ForceSetArgV("wallet", chain.getRwSetting("wallet"));
128 }
129
130 return true;
131 }
132
133 bool LoadWallets(WalletContext& context)
134 {
135 interfaces::Chain& chain = *context.chain;
136 try {
137 std::set<fs::path> wallet_paths;
138 for (const auto& wallet : chain.getSettingsList("wallet")) {
139 if (!wallet.isStr()) {
140 chain.initError(_("Invalid value detected for '-wallet' or '-nowallet'. "
141 "'-wallet' requires a string value, while '-nowallet' accepts only '1' to disable all wallets"));
142 return false;
143 }
144 const auto& name = wallet.get_str();
145 if (!wallet_paths.insert(fs::PathFromString(name)).second) {
146 continue;
147 }
148 DatabaseOptions options;
149 DatabaseStatus status;
150 ReadDatabaseArgs(*context.args, options);
151 options.require_existing = true;
152 options.verify = false; // No need to verify, assuming verified earlier in VerifyWallets()
153 bilingual_str error;
154 std::vector<bilingual_str> warnings;
155 std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(name, options, status, error);
156 if (!database && status == DatabaseStatus::FAILED_NOT_FOUND) {
157 continue;
158 }
159 chain.initMessage(_("Loading wallet…"));
160 std::shared_ptr<CWallet> pwallet = database ? CWallet::Create(context, name, std::move(database), options.create_flags, error, warnings) : nullptr;
161 if (!warnings.empty()) chain.initWarning(Join(warnings, Untranslated("\n")));
162 if (!pwallet) {
163 if (HandleWalletLoadError(chain, name, error)) {
164 continue;
165 } else {
166 return false;
167 }
168 }
169
170 NotifyWalletLoaded(context, pwallet);
171 AddWallet(context, pwallet);
172 }
173 return true;
174 } catch (const std::runtime_error& e) {
175 chain.initError(Untranslated(e.what()));
176 return false;
177 }
178 }
179
180 void StartWallets(WalletContext& context)
181 {
182 for (const std::shared_ptr<CWallet>& pwallet : GetWallets(context)) {
183 pwallet->postInitProcess();
184 }
185
186 // Schedule periodic wallet flushes and tx rebroadcasts
187 if (context.args->GetBoolArg("-flushwallet", DEFAULT_FLUSHWALLET)) {
188 context.scheduler->scheduleEvery([&context] { MaybeCompactWalletDB(context); }, 500ms);
189 }
190 context.scheduler->scheduleEvery([&context] { MaybeResendWalletTxs(context); }, 1min);
191 }
192
193 void FlushWallets(WalletContext& context)
194 {
195 for (const std::shared_ptr<CWallet>& pwallet : GetWallets(context)) {
196 pwallet->Flush();
197 }
198 }
199
200 void UnloadWallets(WalletContext& context)
201 {
202 auto wallets = GetWallets(context);
203 while (!wallets.empty()) {
204 auto wallet = wallets.back();
205 wallets.pop_back();
206 std::vector<bilingual_str> warnings;
207 RemoveWallet(context, wallet, /* load_on_start= */ std::nullopt, warnings);
208 WaitForDeleteWallet(std::move(wallet));
209 }
210 }
211 } // namespace wallet
212