Changeset View
Changeset View
Standalone View
Standalone View
src/wallet/db.cpp
Show First 20 Lines • Show All 332 Lines • ▼ Show 20 Lines | |||||
u_int32_t BerkeleyBatch::SafeDbt::get_size() const { | u_int32_t BerkeleyBatch::SafeDbt::get_size() const { | ||||
return m_dbt.get_size(); | return m_dbt.get_size(); | ||||
} | } | ||||
BerkeleyBatch::SafeDbt::operator Dbt *() { | 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<uint8_t>, std::vector<uint8_t>> 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) { | |||||
// 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<Db> pdbCopy = std::make_unique<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, | bool BerkeleyBatch::VerifyEnvironment(const fs::path &file_path, | ||||
bilingual_str &errorStr) { | bilingual_str &errorStr) { | ||||
std::string walletFile; | std::string walletFile; | ||||
std::shared_ptr<BerkeleyEnvironment> env = | std::shared_ptr<BerkeleyEnvironment> env = | ||||
GetWalletEnv(file_path, walletFile); | GetWalletEnv(file_path, walletFile); | ||||
fs::path walletDir = env->Directory(); | fs::path walletDir = env->Directory(); | ||||
LogPrintf("Using BerkeleyDB version %s\n", | LogPrintf("Using BerkeleyDB version %s\n", | ||||
▲ Show 20 Lines • Show All 493 Lines • Show Last 20 Lines |