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