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