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