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