/ src / wallet / load.cpp
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