diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -21,6 +21,7 @@ load.cpp rpcdump.cpp rpcwallet.cpp + salvage.cpp scriptpubkeyman.cpp wallet.cpp walletdb.cpp diff --git a/src/wallet/db.h b/src/wallet/db.h --- a/src/wallet/db.h +++ b/src/wallet/db.h @@ -411,10 +411,4 @@ const char *pszSkip = nullptr); }; -bool RecoverDatabaseFile(const fs::path &file_path, void *callbackDataIn, - bool (*recoverKVcallback)(void *callbackData, - CDataStream ssKey, - CDataStream ssValue), - std::string &out_backup_filename); - #endif // BITCOIN_WALLET_DB_H diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp --- a/src/wallet/db.cpp +++ b/src/wallet/db.cpp @@ -338,145 +338,6 @@ return &m_dbt; } -/* 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, void *callbackDataIn, - bool (*recoverKVcallback)(void *callbackData, - CDataStream ssKey, - CDataStream ssValue), - std::string &newFilename) { - std::string filename; - std::shared_ptr env = - GetWalletEnv(file_path, filename); - - // 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(); - 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) { - // Skip past header - getline(strDump, strLine); - } - - 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 = std::make_unique(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(); - for (KeyValPair &row : salvagedData) { - if (recoverKVcallback) { - CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION); - CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION); - if (!(*recoverKVcallback)(callbackDataIn, ssKey, ssValue)) { - 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; -} - bool BerkeleyBatch::VerifyEnvironment(const fs::path &file_path, bilingual_str &errorStr) { std::string walletFile; diff --git a/src/wallet/salvage.h b/src/wallet/salvage.h new file mode 100644 --- /dev/null +++ b/src/wallet/salvage.h @@ -0,0 +1,23 @@ +// 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. + +#ifndef BITCOIN_WALLET_SALVAGE_H +#define BITCOIN_WALLET_SALVAGE_H + +#include +#include + +bool RecoverDatabaseFile(const fs::path &file_path, void *callbackDataIn, + bool (*recoverKVcallback)(void *callbackData, + CDataStream ssKey, + CDataStream ssValue), + std::string &out_backup_filename); + +/* Recover filter (used as callback), will only let keys (cryptographical keys) + * as KV/key-type pass through */ +bool RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey, + CDataStream ssValue); + +#endif // BITCOIN_WALLET_SALVAGE_H diff --git a/src/wallet/salvage.cpp b/src/wallet/salvage.cpp new file mode 100644 --- /dev/null +++ b/src/wallet/salvage.cpp @@ -0,0 +1,170 @@ +// 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 + +/* 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, void *callbackDataIn, + bool (*recoverKVcallback)(void *callbackData, + CDataStream ssKey, + CDataStream ssValue), + std::string &newFilename) { + std::string filename; + std::shared_ptr env = + GetWalletEnv(file_path, filename); + + // 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(); + 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 = std::make_unique(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(); + for (KeyValPair &row : salvagedData) { + if (recoverKVcallback) { + CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION); + CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION); + if (!(*recoverKVcallback)(callbackDataIn, ssKey, ssValue)) { + 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; +} + +bool RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey, + CDataStream ssValue) { + CWallet *dummyWallet = reinterpret_cast(callbackData); + 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) { + return false; + } + if (!fReadOK) { + LogPrintf("WARNING: WalletBatch::Recover skipping %s: %s\n", strType, + strErr); + return false; + } + + return true; +} diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -55,6 +55,38 @@ NEED_REWRITE }; +namespace DBKeys { +extern const std::string ACENTRY; +extern const std::string ACTIVEEXTERNALSPK; +extern const std::string ACTIVEINTERNALSPK; +extern const std::string BESTBLOCK; +extern const std::string BESTBLOCK_NOMERKLE; +extern const std::string CRYPTED_KEY; +extern const std::string CSCRIPT; +extern const std::string DEFAULTKEY; +extern const std::string DESTDATA; +extern const std::string FLAGS; +extern const std::string HDCHAIN; +extern const std::string KEY; +extern const std::string KEYMETA; +extern const std::string MASTER_KEY; +extern const std::string MINVERSION; +extern const std::string NAME; +extern const std::string OLD_KEY; +extern const std::string ORDERPOSNEXT; +extern const std::string POOL; +extern const std::string PURPOSE; +extern const std::string SETTINGS; +extern const std::string TX; +extern const std::string VERSION; +extern const std::string WALLETDESCRIPTOR; +extern const std::string WALLETDESCRIPTORCACHE; +extern const std::string WALLETDESCRIPTORCKEY; +extern const std::string WALLETDESCRIPTORKEY; +extern const std::string WATCHMETA; +extern const std::string WATCHS; +} // namespace DBKeys + /* simple HD chain data model */ class CHDChain { public: @@ -279,9 +311,4 @@ bool ReadKeyValue(CWallet *pwallet, CDataStream &ssKey, CDataStream &ssValue, std::string &strType, std::string &strErr); -/* Recover filter (used as callback), will only let keys (cryptographical keys) - * as KV/key-type pass through */ -bool RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey, - CDataStream ssValue); - #endif // BITCOIN_WALLET_WALLETDB_H diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -965,28 +965,6 @@ fOneThread = false; } -bool RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey, - CDataStream ssValue) { - CWallet *dummyWallet = reinterpret_cast(callbackData); - 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) { - return false; - } - if (!fReadOK) { - LogPrintf("WARNING: WalletBatch::Recover skipping %s: %s\n", strType, - strErr); - return false; - } - - return true; -} - bool WalletBatch::VerifyEnvironment(const fs::path &wallet_path, bilingual_str &errorStr) { return BerkeleyBatch::VerifyEnvironment(wallet_path, errorStr); diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp --- a/src/wallet/wallettool.cpp +++ b/src/wallet/wallettool.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include