dump.cpp raw

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