mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-06-11 01:11:50 +02:00
Move RecoverDatabaseFile and RecoverKeysOnlyFilter into salvage.{cpp/h}
This commit is contained in:
parent
b426c7764d
commit
9ea2d258b4
@ -243,6 +243,7 @@ BITCOIN_CORE_H = \
|
|||||||
wallet/ismine.h \
|
wallet/ismine.h \
|
||||||
wallet/load.h \
|
wallet/load.h \
|
||||||
wallet/rpcwallet.h \
|
wallet/rpcwallet.h \
|
||||||
|
wallet/salvage.h \
|
||||||
wallet/scriptpubkeyman.h \
|
wallet/scriptpubkeyman.h \
|
||||||
wallet/wallet.h \
|
wallet/wallet.h \
|
||||||
wallet/walletdb.h \
|
wallet/walletdb.h \
|
||||||
@ -351,6 +352,7 @@ libbitcoin_wallet_a_SOURCES = \
|
|||||||
wallet/load.cpp \
|
wallet/load.cpp \
|
||||||
wallet/rpcdump.cpp \
|
wallet/rpcdump.cpp \
|
||||||
wallet/rpcwallet.cpp \
|
wallet/rpcwallet.cpp \
|
||||||
|
wallet/salvage.cpp \
|
||||||
wallet/scriptpubkeyman.cpp \
|
wallet/scriptpubkeyman.cpp \
|
||||||
wallet/wallet.cpp \
|
wallet/wallet.cpp \
|
||||||
wallet/walletdb.cpp \
|
wallet/walletdb.cpp \
|
||||||
|
@ -317,134 +317,6 @@ BerkeleyBatch::SafeDbt::operator Dbt*()
|
|||||||
return &m_dbt;
|
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<unsigned char>, std::vector<unsigned char> > 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<BerkeleyEnvironment> 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<KeyValPair> 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<Db> pdbCopy = MakeUnique<Db>(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)
|
bool BerkeleyBatch::VerifyEnvironment(const fs::path& file_path, bilingual_str& errorStr)
|
||||||
{
|
{
|
||||||
std::string walletFile;
|
std::string walletFile;
|
||||||
|
@ -389,8 +389,6 @@ public:
|
|||||||
bool static Rewrite(BerkeleyDatabase& database, const char* pszSkip = nullptr);
|
bool static Rewrite(BerkeleyDatabase& database, 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);
|
|
||||||
|
|
||||||
std::string BerkeleyDatabaseVersion();
|
std::string BerkeleyDatabaseVersion();
|
||||||
|
|
||||||
#endif // BITCOIN_WALLET_DB_H
|
#endif // BITCOIN_WALLET_DB_H
|
||||||
|
160
src/wallet/salvage.cpp
Normal file
160
src/wallet/salvage.cpp
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
// 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 <fs.h>
|
||||||
|
#include <streams.h>
|
||||||
|
#include <wallet/salvage.h>
|
||||||
|
#include <wallet/wallet.h>
|
||||||
|
#include <wallet/walletdb.h>
|
||||||
|
|
||||||
|
/* 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<unsigned char>, std::vector<unsigned char> > 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<BerkeleyEnvironment> 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<KeyValPair> 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<Db> pdbCopy = MakeUnique<Db>(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<CWallet*>(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;
|
||||||
|
}
|
17
src/wallet/salvage.h
Normal file
17
src/wallet/salvage.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// 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 <fs.h>
|
||||||
|
#include <streams.h>
|
||||||
|
|
||||||
|
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
|
@ -885,28 +885,6 @@ void MaybeCompactWalletDB()
|
|||||||
fOneThread = false;
|
fOneThread = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey, CDataStream ssValue)
|
|
||||||
{
|
|
||||||
CWallet *dummyWallet = reinterpret_cast<CWallet*>(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)
|
bool WalletBatch::VerifyEnvironment(const fs::path& wallet_path, bilingual_str& errorStr)
|
||||||
{
|
{
|
||||||
return BerkeleyBatch::VerifyEnvironment(wallet_path, errorStr);
|
return BerkeleyBatch::VerifyEnvironment(wallet_path, errorStr);
|
||||||
|
@ -291,7 +291,4 @@ void MaybeCompactWalletDB();
|
|||||||
//! Unserialize a given Key-Value pair and load it into the wallet
|
//! Unserialize a given Key-Value pair and load it into the wallet
|
||||||
bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, std::string& strType, std::string& strErr);
|
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
|
#endif // BITCOIN_WALLET_WALLETDB_H
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include <fs.h>
|
#include <fs.h>
|
||||||
#include <util/system.h>
|
#include <util/system.h>
|
||||||
#include <util/translation.h>
|
#include <util/translation.h>
|
||||||
|
#include <wallet/salvage.h>
|
||||||
#include <wallet/wallet.h>
|
#include <wallet/wallet.h>
|
||||||
#include <wallet/walletutil.h>
|
#include <wallet/walletutil.h>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user