salvage.cpp raw

   1  // Copyright (c) 2009-2010 Satoshi Nakamoto
   2  // Copyright (c) 2009-2021 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 <streams.h>
   7  #include <util/fs.h>
   8  #include <util/translation.h>
   9  #include <wallet/bdb.h>
  10  #include <wallet/salvage.h>
  11  #include <wallet/wallet.h>
  12  #include <wallet/walletdb.h>
  13  
  14  #include <db_cxx.h>
  15  
  16  namespace wallet {
  17  /* End of headers, beginning of key/value data */
  18  static const char *HEADER_END = "HEADER=END";
  19  /* End of key/value data */
  20  static const char *DATA_END = "DATA=END";
  21  typedef std::pair<std::vector<unsigned char>, std::vector<unsigned char> > KeyValPair;
  22  
  23  class DummyCursor : public DatabaseCursor
  24  {
  25      Status Next(DataStream& key, DataStream& value) override { return Status::FAIL; }
  26  };
  27  
  28  /** RAII class that provides access to a DummyDatabase. Never fails. */
  29  class DummyBatch : public DatabaseBatch
  30  {
  31  private:
  32      bool ReadKey(DataStream&& key, DataStream& value) override { return true; }
  33      bool WriteKey(DataStream&& key, DataStream&& value, bool overwrite=true) override { return true; }
  34      bool EraseKey(DataStream&& key) override { return true; }
  35      bool HasKey(DataStream&& key) override { return true; }
  36      bool ErasePrefix(Span<const std::byte> prefix) override { return true; }
  37  
  38  public:
  39      void Flush() override {}
  40      void Close() override {}
  41  
  42      std::unique_ptr<DatabaseCursor> GetNewCursor() override { return std::make_unique<DummyCursor>(); }
  43      std::unique_ptr<DatabaseCursor> GetNewPrefixCursor(Span<const std::byte> prefix) override { return GetNewCursor(); }
  44      bool TxnBegin() override { return true; }
  45      bool TxnCommit() override { return true; }
  46      bool TxnAbort() override { return true; }
  47      bool HasActiveTxn() override { return false; }
  48  };
  49  
  50  /** A dummy WalletDatabase that does nothing and never fails. Only used by salvage.
  51   **/
  52  class DummyDatabase : public WalletDatabase
  53  {
  54  public:
  55      void Open() override {};
  56      void AddRef() override {}
  57      void RemoveRef() override {}
  58      bool Rewrite(const char* pszSkip=nullptr) override { return true; }
  59      bool Backup(const std::string& strDest) const override { return true; }
  60      void Close() override {}
  61      void Flush() override {}
  62      bool PeriodicFlush() override { return true; }
  63      void IncrementUpdateCounter() override { ++nUpdateCounter; }
  64      void ReloadDbEnv() override {}
  65      std::string Filename() override { return "dummy"; }
  66      std::vector<fs::path> Files() override { return {}; }
  67      std::string Format() override { return "dummy"; }
  68      std::unique_ptr<DatabaseBatch> MakeBatch(bool flush_on_close = true) override { return std::make_unique<DummyBatch>(); }
  69  };
  70  
  71  bool RecoverDatabaseFile(const ArgsManager& args, const fs::path& file_path, bilingual_str& error, std::vector<bilingual_str>& warnings)
  72  {
  73      DatabaseOptions options;
  74      DatabaseStatus status;
  75      ReadDatabaseArgs(args, options);
  76      options.require_existing = true;
  77      options.verify = false;
  78      options.require_format = DatabaseFormat::BERKELEY;
  79      std::unique_ptr<WalletDatabase> database = MakeDatabase(file_path, options, status, error);
  80      if (!database) return false;
  81  
  82      BerkeleyDatabase& berkeley_database = static_cast<BerkeleyDatabase&>(*database);
  83      std::string filename = berkeley_database.Filename();
  84      std::shared_ptr<BerkeleyEnvironment> env = berkeley_database.env;
  85  
  86      if (!env->Open(error)) {
  87          return false;
  88      }
  89  
  90      // Recovery procedure:
  91      // move wallet file to walletfilename.timestamp.bak
  92      // Call Salvage with fAggressive=true to
  93      // get as much data as possible.
  94      // Rewrite salvaged data to fresh wallet file
  95      // Rescan so any missing transactions will be
  96      // found.
  97      int64_t now = GetTime();
  98      std::string newFilename = strprintf("%s.%d.bak", filename, now);
  99  
 100      int result = env->dbenv->dbrename(nullptr, filename.c_str(), nullptr,
 101                                         newFilename.c_str(), DB_AUTO_COMMIT);
 102      if (result != 0)
 103      {
 104          error = Untranslated(strprintf("Failed to rename %s to %s", filename, newFilename));
 105          return false;
 106      }
 107  
 108      /**
 109       * Salvage data from a file. The DB_AGGRESSIVE flag is being used (see berkeley DB->verify() method documentation).
 110       * key/value pairs are appended to salvagedData which are then written out to a new wallet file.
 111       * NOTE: reads the entire database into memory, so cannot be used
 112       * for huge databases.
 113       */
 114      std::vector<KeyValPair> salvagedData;
 115  
 116      std::stringstream strDump;
 117  
 118      Db db(env->dbenv.get(), 0);
 119      result = db.verify(newFilename.c_str(), nullptr, &strDump, DB_SALVAGE | DB_AGGRESSIVE);
 120      if (result == DB_VERIFY_BAD) {
 121          warnings.emplace_back(Untranslated("Salvage: Database salvage found errors, all data may not be recoverable."));
 122      }
 123      if (result != 0 && result != DB_VERIFY_BAD) {
 124          error = Untranslated(strprintf("Salvage: Database salvage failed with result %d.", result));
 125          return false;
 126      }
 127  
 128      // Format of bdb dump is ascii lines:
 129      // header lines...
 130      // HEADER=END
 131      //  hexadecimal key
 132      //  hexadecimal value
 133      //  ... repeated
 134      // DATA=END
 135  
 136      std::string strLine;
 137      while (!strDump.eof() && strLine != HEADER_END)
 138          getline(strDump, strLine); // Skip past header
 139  
 140      std::string keyHex, valueHex;
 141      while (!strDump.eof() && keyHex != DATA_END) {
 142          getline(strDump, keyHex);
 143          if (keyHex != DATA_END) {
 144              if (strDump.eof())
 145                  break;
 146              getline(strDump, valueHex);
 147              if (valueHex == DATA_END) {
 148                  warnings.emplace_back(Untranslated("Salvage: WARNING: Number of keys in data does not match number of values."));
 149                  break;
 150              }
 151              salvagedData.emplace_back(ParseHex(keyHex), ParseHex(valueHex));
 152          }
 153      }
 154  
 155      bool fSuccess;
 156      if (keyHex != DATA_END) {
 157          warnings.emplace_back(Untranslated("Salvage: WARNING: Unexpected end of file while reading salvage output."));
 158          fSuccess = false;
 159      } else {
 160          fSuccess = (result == 0);
 161      }
 162  
 163      if (salvagedData.empty())
 164      {
 165          error = Untranslated(strprintf("Salvage(aggressive) found no records in %s.", newFilename));
 166          return false;
 167      }
 168  
 169      std::unique_ptr<Db> pdbCopy = std::make_unique<Db>(env->dbenv.get(), 0);
 170      int ret = pdbCopy->open(nullptr,               // Txn pointer
 171                              filename.c_str(),   // Filename
 172                              "main",             // Logical db name
 173                              DB_BTREE,           // Database type
 174                              DB_CREATE,          // Flags
 175                              0);
 176      if (ret > 0) {
 177          error = Untranslated(strprintf("Cannot create database file %s", filename));
 178          pdbCopy->close(0);
 179          return false;
 180      }
 181  
 182      DbTxn* ptxn = env->TxnBegin(DB_TXN_WRITE_NOSYNC);
 183      CWallet dummyWallet(nullptr, "", std::make_unique<DummyDatabase>());
 184      for (KeyValPair& row : salvagedData)
 185      {
 186          /* Filter for only private key type KV pairs to be added to the salvaged wallet */
 187          DataStream ssKey{row.first};
 188          DataStream ssValue(row.second);
 189          std::string strType, strErr;
 190  
 191          // We only care about KEY, MASTER_KEY, CRYPTED_KEY, and HDCHAIN types
 192          ssKey >> strType;
 193          bool fReadOK = false;
 194          if (strType == DBKeys::KEY) {
 195              fReadOK = LoadKey(&dummyWallet, ssKey, ssValue, strErr);
 196          } else if (strType == DBKeys::CRYPTED_KEY) {
 197              fReadOK = LoadCryptedKey(&dummyWallet, ssKey, ssValue, strErr);
 198          } else if (strType == DBKeys::MASTER_KEY) {
 199              fReadOK = LoadEncryptionKey(&dummyWallet, ssKey, ssValue, strErr);
 200          } else if (strType == DBKeys::HDCHAIN) {
 201              fReadOK = LoadHDChain(&dummyWallet, ssValue, strErr);
 202          } else {
 203              continue;
 204          }
 205  
 206          if (!fReadOK)
 207          {
 208              warnings.push_back(Untranslated(strprintf("WARNING: WalletBatch::Recover skipping %s: %s", strType, strErr)));
 209              continue;
 210          }
 211          Dbt datKey(row.first.data(), row.first.size());
 212          Dbt datValue(row.second.data(), row.second.size());
 213          int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE);
 214          if (ret2 > 0)
 215              fSuccess = false;
 216      }
 217      ptxn->commit(0);
 218      pdbCopy->close(0);
 219  
 220      return fSuccess;
 221  }
 222  } // namespace wallet
 223