load.cpp
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