// Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2020 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include /* End of headers, beginning of key/value data */ static const char *HEADER_END = "HEADER=END"; /* End of key/value data */ static const char *DATA_END = "DATA=END"; typedef std::pair, std::vector > KeyValPair; bool RecoverDatabaseFile(const fs::path& file_path) { std::string filename; std::shared_ptr env = GetWalletEnv(file_path, filename); bilingual_str open_err; if (!env->Open(open_err)) { tfm::format(std::cerr, "%s\n", open_err.original); return false; } // Recovery procedure: // move wallet file to walletfilename.timestamp.bak // Call Salvage with fAggressive=true to // get as much data as possible. // Rewrite salvaged data to fresh wallet file // Set -rescan so any missing transactions will be // found. int64_t now = GetTime(); std::string newFilename = strprintf("%s.%d.bak", filename, now); int result = env->dbenv->dbrename(nullptr, filename.c_str(), nullptr, newFilename.c_str(), DB_AUTO_COMMIT); if (result == 0) LogPrintf("Renamed %s to %s\n", filename, newFilename); else { LogPrintf("Failed to rename %s to %s\n", filename, newFilename); return false; } /** * Salvage data from a file. The DB_AGGRESSIVE flag is being used (see berkeley DB->verify() method documentation). * key/value pairs are appended to salvagedData which are then written out to a new wallet file. * NOTE: reads the entire database into memory, so cannot be used * for huge databases. */ std::vector salvagedData; std::stringstream strDump; Db db(env->dbenv.get(), 0); result = db.verify(newFilename.c_str(), nullptr, &strDump, DB_SALVAGE | DB_AGGRESSIVE); if (result == DB_VERIFY_BAD) { LogPrintf("Salvage: Database salvage found errors, all data may not be recoverable.\n"); } if (result != 0 && result != DB_VERIFY_BAD) { LogPrintf("Salvage: Database salvage failed with result %d.\n", result); return false; } // Format of bdb dump is ascii lines: // header lines... // HEADER=END // hexadecimal key // hexadecimal value // ... repeated // DATA=END std::string strLine; while (!strDump.eof() && strLine != HEADER_END) getline(strDump, strLine); // Skip past header std::string keyHex, valueHex; while (!strDump.eof() && keyHex != DATA_END) { getline(strDump, keyHex); if (keyHex != DATA_END) { if (strDump.eof()) break; getline(strDump, valueHex); if (valueHex == DATA_END) { LogPrintf("Salvage: WARNING: Number of keys in data does not match number of values.\n"); break; } salvagedData.push_back(make_pair(ParseHex(keyHex), ParseHex(valueHex))); } } bool fSuccess; if (keyHex != DATA_END) { LogPrintf("Salvage: WARNING: Unexpected end of file while reading salvage output.\n"); fSuccess = false; } else { fSuccess = (result == 0); } if (salvagedData.empty()) { LogPrintf("Salvage(aggressive) found no records in %s.\n", newFilename); return false; } LogPrintf("Salvage(aggressive) found %u records\n", salvagedData.size()); std::unique_ptr pdbCopy = MakeUnique(env->dbenv.get(), 0); int ret = pdbCopy->open(nullptr, // Txn pointer filename.c_str(), // Filename "main", // Logical db name DB_BTREE, // Database type DB_CREATE, // Flags 0); if (ret > 0) { LogPrintf("Cannot create database file %s\n", filename); pdbCopy->close(0); return false; } DbTxn* ptxn = env->TxnBegin(); CWallet dummyWallet(nullptr, WalletLocation(), CreateDummyWalletDatabase()); for (KeyValPair& row : salvagedData) { /* Filter for only private key type KV pairs to be added to the salvaged wallet */ CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION); CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION); std::string strType, strErr; bool fReadOK; { // Required in LoadKeyMetadata(): LOCK(dummyWallet.cs_wallet); fReadOK = ReadKeyValue(&dummyWallet, ssKey, ssValue, strType, strErr); } if (!WalletBatch::IsKeyType(strType) && strType != DBKeys::HDCHAIN) { continue; } if (!fReadOK) { LogPrintf("WARNING: WalletBatch::Recover skipping %s: %s\n", strType, strErr); continue; } Dbt datKey(&row.first[0], row.first.size()); Dbt datValue(&row.second[0], row.second.size()); int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE); if (ret2 > 0) fSuccess = false; } ptxn->commit(0); pdbCopy->close(0); return fSuccess; }