dump.cpp raw

   1  // Copyright (c) 2020-present The Bitcoin Core 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 <wallet/dump.h>
   6  
   7  #include <common/args.h>
   8  #include <util/fs.h>
   9  #include <util/translation.h>
  10  #include <wallet/wallet.h>
  11  #include <wallet/walletdb.h>
  12  
  13  #include <algorithm>
  14  #include <fstream>
  15  #include <memory>
  16  #include <string>
  17  #include <utility>
  18  #include <vector>
  19  
  20  namespace wallet {
  21  static const std::string DUMP_MAGIC = "BITCOIN_CORE_WALLET_DUMP";
  22  uint32_t DUMP_VERSION = 1;
  23  
  24  bool DumpWallet(const ArgsManager& args, WalletDatabase& db, bilingual_str& error)
  25  {
  26      // Get the dumpfile
  27      std::string dump_filename = args.GetArg("-dumpfile", "");
  28      if (dump_filename.empty()) {
  29          error = _("No dump file provided. To use dump, -dumpfile=<filename> must be provided.");
  30          return false;
  31      }
  32  
  33      fs::path path = fs::PathFromString(dump_filename);
  34      path = fs::absolute(path);
  35      if (fs::exists(path)) {
  36          error = strprintf(_("File %s already exists. If you are sure this is what you want, move it out of the way first."), fs::PathToString(path));
  37          return false;
  38      }
  39      std::ofstream dump_file;
  40      dump_file.open(path.std_path());
  41      if (dump_file.fail()) {
  42          error = strprintf(_("Unable to open %s for writing"), fs::PathToString(path));
  43          return false;
  44      }
  45  
  46      HashWriter hasher{};
  47  
  48      std::unique_ptr<DatabaseBatch> batch = db.MakeBatch();
  49  
  50      bool ret = true;
  51      std::unique_ptr<DatabaseCursor> cursor = batch->GetNewCursor();
  52      if (!cursor) {
  53          error = _("Error: Couldn't create cursor into database");
  54          ret = false;
  55      }
  56  
  57      // Write out a magic string with version
  58      std::string line = strprintf("%s,%u\n", DUMP_MAGIC, DUMP_VERSION);
  59      dump_file.write(line.data(), line.size());
  60      hasher << std::span{line};
  61  
  62      // Write out the file format
  63      std::string format = db.Format();
  64      // BDB files that are opened using BerkeleyRODatabase have its format as "bdb_ro"
  65      // We want to override that format back to "bdb"
  66      if (format == "bdb_ro") {
  67          format = "bdb";
  68      }
  69      line = strprintf("%s,%s\n", "format", format);
  70      dump_file.write(line.data(), line.size());
  71      hasher << std::span{line};
  72  
  73      if (ret) {
  74  
  75          // Read the records
  76          while (true) {
  77              DataStream ss_key{};
  78              DataStream ss_value{};
  79              DatabaseCursor::Status status = cursor->Next(ss_key, ss_value);
  80              if (status == DatabaseCursor::Status::DONE) {
  81                  ret = true;
  82                  break;
  83              } else if (status == DatabaseCursor::Status::FAIL) {
  84                  error = _("Error reading next record from wallet database");
  85                  ret = false;
  86                  break;
  87              }
  88              std::string key_str = HexStr(ss_key);
  89              std::string value_str = HexStr(ss_value);
  90              line = strprintf("%s,%s\n", key_str, value_str);
  91              dump_file.write(line.data(), line.size());
  92              hasher << std::span{line};
  93          }
  94      }
  95  
  96      cursor.reset();
  97      batch.reset();
  98  
  99      if (ret) {
 100          // Write the hash
 101          tfm::format(dump_file, "checksum,%s\n", HexStr(hasher.GetHash()));
 102          dump_file.close();
 103      } else {
 104          // Remove the dumpfile on failure
 105          dump_file.close();
 106          fs::remove(path);
 107      }
 108  
 109      return ret;
 110  }
 111  
 112  // The standard wallet deleter function blocks on the validation interface
 113  // queue, which doesn't exist for the bitcoin-wallet. Define our own
 114  // deleter here.
 115  static void WalletToolReleaseWallet(CWallet* wallet)
 116  {
 117      wallet->WalletLogPrintf("Releasing wallet\n");
 118      wallet->Close();
 119      delete wallet;
 120  }
 121  
 122  bool CreateFromDump(const ArgsManager& args, const std::string& name, const fs::path& wallet_path, bilingual_str& error, std::vector<bilingual_str>& warnings)
 123  {
 124      if (name.empty()) {
 125          tfm::format(std::cerr, "Wallet name cannot be empty\n");
 126          return false;
 127      }
 128  
 129      // Get the dumpfile
 130      std::string dump_filename = args.GetArg("-dumpfile", "");
 131      if (dump_filename.empty()) {
 132          error = _("No dump file provided. To use createfromdump, -dumpfile=<filename> must be provided.");
 133          return false;
 134      }
 135  
 136      fs::path dump_path = fs::PathFromString(dump_filename);
 137      dump_path = fs::absolute(dump_path);
 138      if (!fs::exists(dump_path)) {
 139          error = strprintf(_("Dump file %s does not exist."), fs::PathToString(dump_path));
 140          return false;
 141      }
 142      std::ifstream dump_file{dump_path.std_path()};
 143  
 144      // Compute the checksum
 145      HashWriter hasher{};
 146      uint256 checksum;
 147  
 148      // Check the magic and version
 149      std::string magic_key;
 150      std::getline(dump_file, magic_key, ',');
 151      std::string version_value;
 152      std::getline(dump_file, version_value, '\n');
 153      if (magic_key != DUMP_MAGIC) {
 154          error = strprintf(_("Error: Dumpfile identifier record is incorrect. Got \"%s\", expected \"%s\"."), magic_key, DUMP_MAGIC);
 155          dump_file.close();
 156          return false;
 157      }
 158      // Check the version number (value of first record)
 159      const auto ver{ToIntegral<uint32_t>(version_value)};
 160      if (!ver) {
 161          error = strprintf(_("Error: Unable to parse version %u as a uint32_t"), version_value);
 162          dump_file.close();
 163          return false;
 164      }
 165      if (*ver != DUMP_VERSION) {
 166          error = strprintf(_("Error: Dumpfile version is not supported. This version of bitcoin-wallet only supports version 1 dumpfiles. Got dumpfile with version %s"), version_value);
 167          dump_file.close();
 168          return false;
 169      }
 170      std::string magic_hasher_line = strprintf("%s,%s\n", magic_key, version_value);
 171      hasher << std::span{magic_hasher_line};
 172  
 173      // Get the stored file format
 174      std::string format_key;
 175      std::getline(dump_file, format_key, ',');
 176      std::string format_value;
 177      std::getline(dump_file, format_value, '\n');
 178      if (format_key != "format") {
 179          error = strprintf(_("Error: Dumpfile format record is incorrect. Got \"%s\", expected \"format\"."), format_key);
 180          dump_file.close();
 181          return false;
 182      }
 183      // Make sure that the dump was created from a sqlite database only as that is the only
 184      // type of database that we still support.
 185      // Other formats such as BDB should not be loaded into a sqlite database since they also
 186      // use a different type of wallet entirely which is no longer compatible with this software.
 187      if (format_value != "sqlite") {
 188          error = strprintf(_("Error: Dumpfile specifies an unsupported database format (%s). Only sqlite database dumps are supported"), format_value);
 189          return false;
 190      }
 191      std::string format_hasher_line = strprintf("%s,%s\n", format_key, format_value);
 192      hasher << std::span{format_hasher_line};
 193  
 194      DatabaseOptions options;
 195      DatabaseStatus status;
 196      ReadDatabaseArgs(args, options);
 197      options.require_create = true;
 198      options.require_format = DatabaseFormat::SQLITE;
 199      std::unique_ptr<WalletDatabase> database = MakeDatabase(wallet_path, options, status, error);
 200      if (!database) return false;
 201  
 202      // dummy chain interface
 203      bool ret = true;
 204      std::shared_ptr<CWallet> wallet(new CWallet(/*chain=*/nullptr, name, std::move(database)), WalletToolReleaseWallet);
 205      {
 206          // Get the database handle
 207          WalletDatabase& db = wallet->GetDatabase();
 208          std::unique_ptr<DatabaseBatch> batch = db.MakeBatch();
 209          batch->TxnBegin();
 210  
 211          // Read the records from the dump file and write them to the database
 212          while (dump_file.good()) {
 213              std::string key;
 214              std::getline(dump_file, key, ',');
 215              std::string value;
 216              std::getline(dump_file, value, '\n');
 217  
 218              if (key == "checksum") {
 219                  std::vector<unsigned char> parsed_checksum = ParseHex(value);
 220                  if (parsed_checksum.size() != checksum.size()) {
 221                      error = Untranslated("Error: Checksum is not the correct size");
 222                      ret = false;
 223                      break;
 224                  }
 225                  std::copy(parsed_checksum.begin(), parsed_checksum.end(), checksum.begin());
 226                  break;
 227              }
 228  
 229              std::string line = strprintf("%s,%s\n", key, value);
 230              hasher << std::span{line};
 231  
 232              if (key.empty() || value.empty()) {
 233                  continue;
 234              }
 235  
 236              if (!IsHex(key)) {
 237                  error = strprintf(_("Error: Got key that was not hex: %s"), key);
 238                  ret = false;
 239                  break;
 240              }
 241              if (!IsHex(value)) {
 242                  error = strprintf(_("Error: Got value that was not hex: %s"), value);
 243                  ret = false;
 244                  break;
 245              }
 246  
 247              std::vector<unsigned char> k = ParseHex(key);
 248              std::vector<unsigned char> v = ParseHex(value);
 249              if (!batch->Write(std::span{k}, std::span{v})) {
 250                  error = strprintf(_("Error: Unable to write record to new wallet"));
 251                  ret = false;
 252                  break;
 253              }
 254          }
 255  
 256          if (ret) {
 257              uint256 comp_checksum = hasher.GetHash();
 258              if (checksum.IsNull()) {
 259                  error = _("Error: Missing checksum");
 260                  ret = false;
 261              } else if (checksum != comp_checksum) {
 262                  error = strprintf(_("Error: Dumpfile checksum does not match. Computed %s, expected %s"), HexStr(comp_checksum), HexStr(checksum));
 263                  ret = false;
 264              }
 265          }
 266  
 267          if (ret) {
 268              batch->TxnCommit();
 269          } else {
 270              batch->TxnAbort();
 271          }
 272  
 273          batch.reset();
 274  
 275          dump_file.close();
 276      }
 277      // On failure, gather the paths to remove
 278      std::vector<fs::path> paths_to_remove = wallet->GetDatabase().Files();
 279      if (!name.empty()) paths_to_remove.push_back(wallet_path);
 280  
 281      wallet.reset(); // The pointer deleter will close the wallet for us.
 282  
 283      // Remove the wallet dir if we have a failure
 284      if (!ret) {
 285          for (const auto& p : paths_to_remove) {
 286              fs::remove(p);
 287          }
 288      }
 289  
 290      return ret;
 291  }
 292  } // namespace wallet
 293