wallettool.cpp raw

   1  // Copyright (c) 2016-2022 The Limenka developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #include <limenka-build-config.h> // IWYU pragma: keep
   6  
   7  #include <wallet/wallettool.h>
   8  
   9  #include <common/args.h>
  10  #include <tinyformat.h>
  11  #include <univalue.h>
  12  #include <util/fs.h>
  13  #include <util/translation.h>
  14  #include <wallet/dump.h>
  15  #include <wallet/salvage.h>
  16  #include <wallet/wallet.h>
  17  #include <wallet/walletutil.h>
  18  
  19  #include <cassert>
  20  #include <fstream>
  21  #include <string>
  22  
  23  namespace wallet {
  24  
  25  UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, const int64_t timestamp, const std::vector<CExtKey>& master_keys = {})
  26      EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
  27  
  28  namespace WalletTool {
  29  
  30  // The standard wallet deleter function blocks on the validation interface
  31  // queue, which doesn't exist for the limenka-wallet. Define our own
  32  // deleter here.
  33  static void WalletToolReleaseWallet(CWallet* wallet)
  34  {
  35      wallet->WalletLogPrintf("Releasing wallet\n");
  36      wallet->Close();
  37      delete wallet;
  38  }
  39  
  40  static void WalletCreate(CWallet* wallet_instance, uint64_t wallet_creation_flags)
  41  {
  42      LOCK(wallet_instance->cs_wallet);
  43  
  44      wallet_instance->SetMinVersion(FEATURE_LATEST);
  45      wallet_instance->InitWalletFlags(wallet_creation_flags);
  46  
  47      if (wallet_instance->IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET)) {
  48          return;
  49      }
  50  
  51      if (!wallet_instance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
  52          auto spk_man = wallet_instance->GetOrCreateLegacyScriptPubKeyMan();
  53          spk_man->SetupGeneration(false);
  54      } else {
  55          wallet_instance->SetupDescriptorScriptPubKeyMans();
  56      }
  57  
  58      tfm::format(std::cout, "Topping up keypool...\n");
  59      wallet_instance->TopUpKeyPool();
  60  }
  61  
  62  static std::shared_ptr<CWallet> MakeWallet(const std::string& name, const fs::path& path, DatabaseOptions options, CWallet::do_init_used_flag do_init_used_flag_val = CWallet::do_init_used_flag::Init)
  63  {
  64      DatabaseStatus status;
  65      bilingual_str error;
  66      std::unique_ptr<WalletDatabase> database = MakeDatabase(path, options, status, error);
  67      if (!database) {
  68          tfm::format(std::cerr, "%s\n", error.original);
  69          return nullptr;
  70      }
  71  
  72      // dummy chain interface
  73      std::shared_ptr<CWallet> wallet_instance{new CWallet(/*chain=*/nullptr, name, std::move(database)), WalletToolReleaseWallet};
  74      DBErrors load_wallet_ret;
  75      try {
  76          load_wallet_ret = wallet_instance->LoadWallet(do_init_used_flag_val);
  77      } catch (const std::runtime_error&) {
  78          tfm::format(std::cerr, "Error loading %s. Is wallet being used by another process?\n", name);
  79          return nullptr;
  80      }
  81  
  82      if (load_wallet_ret != DBErrors::LOAD_OK) {
  83          if (load_wallet_ret == DBErrors::CORRUPT) {
  84              tfm::format(std::cerr, "Error loading %s: Wallet corrupted", name);
  85              return nullptr;
  86          } else if (load_wallet_ret == DBErrors::NONCRITICAL_ERROR) {
  87              tfm::format(std::cerr, "Error reading %s! All keys read correctly, but transaction data"
  88                              " or address book entries might be missing or incorrect.",
  89                  name);
  90          } else if (load_wallet_ret == DBErrors::TOO_NEW) {
  91              tfm::format(std::cerr, "Error loading %s: Wallet requires newer version of %s",
  92                  name, CLIENT_NAME);
  93              return nullptr;
  94          } else if (load_wallet_ret == DBErrors::NEED_REWRITE) {
  95              tfm::format(std::cerr, "Wallet needed to be rewritten: restart %s to complete", CLIENT_NAME);
  96              return nullptr;
  97          } else if (load_wallet_ret == DBErrors::NEED_RESCAN) {
  98              tfm::format(std::cerr, "Error reading %s! Some transaction data might be missing or"
  99                             " incorrect. Wallet requires a rescan.",
 100                  name);
 101          } else {
 102              tfm::format(std::cerr, "Error loading %s", name);
 103              return nullptr;
 104          }
 105      }
 106  
 107      if (options.require_create) WalletCreate(wallet_instance.get(), options.create_flags);
 108  
 109      return wallet_instance;
 110  }
 111  
 112  static void WalletShowInfo(CWallet* wallet_instance)
 113  {
 114      LOCK(wallet_instance->cs_wallet);
 115  
 116      tfm::format(std::cout, "Wallet info\n===========\n");
 117      tfm::format(std::cout, "Name: %s\n", wallet_instance->GetName());
 118      tfm::format(std::cout, "Format: %s\n", wallet_instance->GetDatabase().Format());
 119      tfm::format(std::cout, "Descriptors: %s\n", wallet_instance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) ? "yes" : "no");
 120      tfm::format(std::cout, "Encrypted: %s\n", wallet_instance->IsCrypted() ? "yes" : "no");
 121      tfm::format(std::cout, "HD (hd seed available): %s\n", wallet_instance->IsHDEnabled() ? "yes" : "no");
 122      tfm::format(std::cout, "Keypool Size: %u\n", wallet_instance->GetKeyPoolSize());
 123      tfm::format(std::cout, "Transactions: %zu\n", wallet_instance->mapWallet.size());
 124      tfm::format(std::cout, "Address Book: %zu\n", wallet_instance->m_address_book.size());
 125  }
 126  
 127  static bool ReadAndParseColdcardFile(const fs::path& path, UniValue& decriptors)
 128  {
 129      std::ifstream file;
 130      file.open(path);
 131      if (!file.is_open()) {
 132          tfm::format(std::cerr, "%s. Please check permissions.\n", fs::PathToString(path));
 133          return false;
 134      }
 135  
 136      std::string line;
 137      while (std::getline(file, line)) {
 138          if (line.substr(0, 22) == "importdescriptors \'[{\"") break;
 139      }
 140  
 141      file.close();
 142  
 143      decriptors.clear();
 144      if (!decriptors.read(line.substr(19, line.size() - 20))) {
 145          tfm::format(std::cerr, "Unable to parse %s\n", fs::PathToString(path));
 146          return false;
 147      }
 148  
 149      assert(decriptors.isArray());
 150      return true;
 151  }
 152  
 153  bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command)
 154  {
 155      if (args.IsArgSet("-format") && command != "createfromdump") {
 156          tfm::format(std::cerr, "The -format option can only be used with the \"createfromdump\" command.\n");
 157          return false;
 158      }
 159      if (args.IsArgSet("-dumpfile") && command != "dump" && command != "createfromdump" && command != "importfromcoldcard") {
 160          tfm::format(std::cerr, "The -dumpfile option can only be used with the \"dump\", \"createfromdump\" and \"importfromcoldcard\" commands.\n");
 161          return false;
 162      }
 163      if (args.IsArgSet("-descriptors") && command != "create") {
 164          tfm::format(std::cerr, "The -descriptors option can only be used with the 'create' command.\n");
 165          return false;
 166      }
 167      if (args.IsArgSet("-legacy") && command != "create") {
 168          tfm::format(std::cerr, "The -legacy option can only be used with the 'create' command.\n");
 169          return false;
 170      }
 171      if (command == "create" && !args.IsArgSet("-wallet")) {
 172          tfm::format(std::cerr, "Wallet name must be provided when creating a new wallet.\n");
 173          return false;
 174      }
 175      const std::string name = args.GetArg("-wallet", "");
 176      const fs::path path = fsbridge::AbsPathJoin(GetWalletDir(), fs::PathFromString(name));
 177  
 178      if (command == "create") {
 179          DatabaseOptions options;
 180          ReadDatabaseArgs(args, options);
 181          options.require_create = true;
 182          // If -legacy is set, use it. Otherwise default to false.
 183          bool make_legacy = args.GetBoolArg("-legacy", false);
 184          // If neither -legacy nor -descriptors is set, default to true. If -descriptors is set, use its value.
 185          bool make_descriptors = (!args.IsArgSet("-descriptors") && !args.IsArgSet("-legacy")) || (args.IsArgSet("-descriptors") && args.GetBoolArg("-descriptors", true));
 186          if (make_legacy && make_descriptors) {
 187              tfm::format(std::cerr, "Only one of -legacy or -descriptors can be set to true, not both\n");
 188              return false;
 189          }
 190          if (!make_legacy && !make_descriptors) {
 191              tfm::format(std::cerr, "One of -legacy or -descriptors must be set to true (or omitted)\n");
 192              return false;
 193          }
 194          if (make_descriptors) {
 195              options.create_flags |= WALLET_FLAG_DESCRIPTORS;
 196              options.require_format = DatabaseFormat::SQLITE;
 197          }
 198  
 199          const std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, options);
 200          if (wallet_instance) {
 201              WalletShowInfo(wallet_instance.get());
 202              wallet_instance->Close();
 203          }
 204      } else if (command == "info") {
 205          DatabaseOptions options;
 206          ReadDatabaseArgs(args, options);
 207          options.require_existing = true;
 208          // NOTE: We need to skip initialisation of the m_used flag, or else the address book count might be wrong
 209          const std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, options, CWallet::do_init_used_flag::Skip);
 210          if (!wallet_instance) return false;
 211          WalletShowInfo(wallet_instance.get());
 212          wallet_instance->Close();
 213      } else if (command == "salvage") {
 214  #ifdef USE_BDB
 215          bilingual_str error;
 216          std::vector<bilingual_str> warnings;
 217          bool ret = RecoverDatabaseFile(args, path, error, warnings);
 218          if (!ret) {
 219              for (const auto& warning : warnings) {
 220                  tfm::format(std::cerr, "%s\n", warning.original);
 221              }
 222              if (!error.empty()) {
 223                  tfm::format(std::cerr, "%s\n", error.original);
 224              }
 225          }
 226          return ret;
 227  #else
 228          tfm::format(std::cerr, "Salvage command is not available as BDB support is not compiled");
 229          return false;
 230  #endif
 231      } else if (command == "dump") {
 232          DatabaseOptions options;
 233          ReadDatabaseArgs(args, options);
 234          options.require_existing = true;
 235  
 236          // Get the dumpfile
 237          std::string dump_filename = args.GetArg("-dumpfile", "");
 238          if (dump_filename.empty()) {
 239              tfm::format(std::cerr, "No dump file provided. To use dump, -dumpfile=<filename> must be provided.\n");
 240              return false;
 241          }
 242  
 243          DatabaseStatus status;
 244  
 245          if (args.GetBoolArg("-withinternalbdb", false) && IsBDBFile(BDBDataFile(path))) {
 246              options.require_format = DatabaseFormat::BERKELEY_RO;
 247          }
 248  
 249          bilingual_str error;
 250          std::unique_ptr<WalletDatabase> database = MakeDatabase(path, options, status, error);
 251          if (!database) {
 252              tfm::format(std::cerr, "%s\n", error.original);
 253              return false;
 254          }
 255  
 256          if (database->Format().starts_with("bdb")) {
 257              tfm::format(std::cerr, "dump: WARNING: BDB-backed wallets have a wallet id that is not currently dumped.\n");
 258          }
 259  
 260          bool ret = DumpWallet(*database, error, dump_filename);
 261          if (!ret && !error.empty()) {
 262              tfm::format(std::cerr, "%s\n", error.original);
 263              return ret;
 264          }
 265          tfm::format(std::cerr, "The dumpfile may contain private keys. To ensure the safety of your Limenka, do not share the dumpfile.\n");
 266          return ret;
 267      } else if (command == "createfromdump") {
 268          bilingual_str error;
 269          std::vector<bilingual_str> warnings;
 270          bool ret = CreateFromDump(args, name, path, error, warnings);
 271          for (const auto& warning : warnings) {
 272              tfm::format(std::cerr, "%s\n", warning.original);
 273          }
 274          if (!ret && !error.empty()) {
 275              tfm::format(std::cerr, "%s\n", error.original);
 276          }
 277          return ret;
 278      } else if (command == "importfromcoldcard") {
 279          tfm::format(std::cerr, "WARNING: The \"importfromcoldcard\" command is experimental and will likely be removed or changed incompatibly in a future version.\n");
 280  
 281          std::string filename = gArgs.GetArg("-dumpfile", "");
 282          if (filename.empty()) {
 283              tfm::format(std::cerr, "To use importfromcoldcard, -dumpfile=<filename> must be provided.\n");
 284              return false;
 285          }
 286  
 287          const fs::path import_file_path{fs::absolute(fs::PathFromString(filename))};
 288          if (!fs::exists(import_file_path)) {
 289              tfm::format(std::cerr, "File %s does not exist.\n", fs::PathToString(import_file_path));
 290              return false;
 291          }
 292  
 293          UniValue descriptors;
 294          if (!ReadAndParseColdcardFile(import_file_path, descriptors)) {
 295              return false;
 296          }
 297  
 298          DatabaseOptions options;
 299          options.require_create = true;
 300          options.create_flags |= WALLET_FLAG_DESCRIPTORS;
 301          options.create_flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS;
 302          options.create_flags |= WALLET_FLAG_BLANK_WALLET;
 303          options.require_format = DatabaseFormat::SQLITE;
 304          std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, options);
 305          if (!wallet_instance) {
 306              return false;
 307          }
 308  
 309          LOCK(wallet_instance->cs_wallet);
 310          for (const UniValue& descriptor : descriptors.getValues()) {
 311              const UniValue result = ProcessDescriptorImport(*wallet_instance, descriptor, 0);
 312              tfm::format(std::cerr, "%s\n", result.write(2));
 313          }
 314  
 315          WalletShowInfo(wallet_instance.get());
 316          wallet_instance->Close();
 317      } else {
 318          tfm::format(std::cerr, "Invalid command: %s\n", command);
 319          return false;
 320      }
 321  
 322      return true;
 323  }
 324  } // namespace WalletTool
 325  } // namespace wallet
 326