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