Skip to content

Commit

Permalink
Merge bitcoin#8574: [Wallet] refactor CWallet/CWalletDB/CDB
Browse files Browse the repository at this point in the history
7184e25 [Wallet] refactor CWallet/CWalletDB/CDB (Jonas Schnelli)

Tree-SHA512: a1993dcfc3505459613e8be3f6560ef32466fd7c649bff358f12af118e633aadd648a090f4af60743a827c9cb624e4ec63eb0202326da4779fc18249bb77da1e
  • Loading branch information
laanwj authored and PastaPastaPasta committed Feb 5, 2019
1 parent 67a8609 commit 29bbfc5
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 152 deletions.
167 changes: 165 additions & 2 deletions src/wallet/db.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#endif

#include <boost/filesystem.hpp>
#include <boost/foreach.hpp>
#include <boost/thread.hpp>
#include <boost/version.hpp>

Expand Down Expand Up @@ -142,7 +143,7 @@ void CDBEnv::MakeMock()
fMockDb = true;
}

CDBEnv::VerifyResult CDBEnv::Verify(const std::string& strFile, bool (*recoverFunc)(CDBEnv& dbenv, const std::string& strFile))
CDBEnv::VerifyResult CDBEnv::Verify(const std::string& strFile, bool (*recoverFunc)(const std::string& strFile))
{
LOCK(cs_db);
assert(mapFileUseCount.count(strFile) == 0);
Expand All @@ -155,10 +156,134 @@ CDBEnv::VerifyResult CDBEnv::Verify(const std::string& strFile, bool (*recoverFu
return RECOVER_FAIL;

// Try to recover:
bool fRecovered = (*recoverFunc)(*this, strFile);
bool fRecovered = (*recoverFunc)(strFile);
return (fRecovered ? RECOVER_OK : RECOVER_FAIL);
}

bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue))
{
// Recovery procedure:
// move wallet file to wallet.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("wallet.%d.bak", now);

int result = bitdb.dbenv->dbrename(NULL, filename.c_str(), NULL,
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;
}

std::vector<CDBEnv::KeyValPair> salvagedData;
bool fSuccess = bitdb.Salvage(newFilename, true, salvagedData);
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(new Db(bitdb.dbenv, 0));
int ret = pdbCopy->open(NULL, // 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);
return false;
}

DbTxn* ptxn = bitdb.TxnBegin();
BOOST_FOREACH(CDBEnv::KeyValPair& row, salvagedData)
{
if (recoverKVcallback)
{
CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION);
CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION);
std::string strType, strErr;
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 CDB::VerifyEnvironment(const std::string& walletFile, const boost::filesystem::path& dataDir, std::string& errorStr)
{
LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0));
LogPrintf("Using wallet %s\n", walletFile);

// Wallet file must be a plain filename without a directory
if (walletFile != boost::filesystem::basename(walletFile) + boost::filesystem::extension(walletFile))
{
errorStr = strprintf(_("Wallet %s resides outside data directory %s"), walletFile, dataDir.string());
return false;
}

if (!bitdb.Open(dataDir))
{
// try moving the database env out of the way
boost::filesystem::path pathDatabase = dataDir / "database";
boost::filesystem::path pathDatabaseBak = dataDir / strprintf("database.%d.bak", GetTime());
try {
boost::filesystem::rename(pathDatabase, pathDatabaseBak);
LogPrintf("Moved old %s to %s. Retrying.\n", pathDatabase.string(), pathDatabaseBak.string());
} catch (const boost::filesystem::filesystem_error&) {
// failure is ok (well, not really, but it's not worse than what we started with)
}

// try again
if (!bitdb.Open(dataDir)) {
// if it still fails, it probably means we can't even create the database env
errorStr = strprintf(_("Error initializing wallet database environment %s!"), GetDataDir());
return false;
}
}
return true;
}

bool CDB::VerifyDatabaseFile(const std::string& walletFile, const boost::filesystem::path& dataDir, std::string& warningStr, std::string& errorStr, bool (*recoverFunc)(const std::string& strFile))
{
if (boost::filesystem::exists(dataDir / walletFile))
{
CDBEnv::VerifyResult r = bitdb.Verify(walletFile, recoverFunc);
if (r == CDBEnv::RECOVER_OK)
{
warningStr = strprintf(_("Warning: Wallet file corrupt, data salvaged!"
" Original %s saved as %s in %s; if"
" your balance or transactions are incorrect you should"
" restore from a backup."),
walletFile, "wallet.{timestamp}.bak", dataDir);
}
if (r == CDBEnv::RECOVER_FAIL)
{
errorStr = strprintf(_("%s corrupt, salvage failed"), walletFile);
return false;
}
}
// also return true if files does not exists
return true;
}

/* End of headers, beginning of key/value data */
static const char *HEADER_END = "HEADER=END";
/* End of key/value data */
Expand Down Expand Up @@ -470,3 +595,41 @@ void CDBEnv::Flush(bool fShutdown)
}
}
}

bool CDB::PeriodicFlush(std::string strFile)
{
bool ret = false;
TRY_LOCK(bitdb.cs_db,lockDb);
if (lockDb)
{
// Don't do this if any databases are in use
int nRefCount = 0;
std::map<std::string, int>::iterator mi = bitdb.mapFileUseCount.begin();
while (mi != bitdb.mapFileUseCount.end())
{
nRefCount += (*mi).second;
mi++;
}

if (nRefCount == 0)
{
boost::this_thread::interruption_point();
std::map<std::string, int>::iterator mi = bitdb.mapFileUseCount.find(strFile);
if (mi != bitdb.mapFileUseCount.end())
{
LogPrint("db", "Flushing %s\n", strFile);
int64_t nStart = GetTimeMillis();

// Flush wallet file so it's self contained
bitdb.CloseDb(strFile);
bitdb.CheckpointLSN(strFile);

bitdb.mapFileUseCount.erase(mi++);
LogPrint("db", "Flushed %s %dms\n", strFile, GetTimeMillis() - nStart);
ret = true;
}
}
}

return ret;
}
11 changes: 10 additions & 1 deletion src/wallet/db.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class CDBEnv
enum VerifyResult { VERIFY_OK,
RECOVER_OK,
RECOVER_FAIL };
VerifyResult Verify(const std::string& strFile, bool (*recoverFunc)(CDBEnv& dbenv, const std::string& strFile));
VerifyResult Verify(const std::string& strFile, bool (*recoverFunc)(const std::string& strFile));
/**
* Salvage data from a file that Verify says is bad.
* fAggressive sets the DB_AGGRESSIVE flag (see berkeley DB->verify() method documentation).
Expand Down Expand Up @@ -104,6 +104,15 @@ class CDB
public:
void Flush();
void Close();
static bool Recover(const std::string& filename, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue));

/* flush the wallet passively (TRY_LOCK)
ideal to be called periodically */
static bool PeriodicFlush(std::string strFile);
/* verifies the database environment */
static bool VerifyEnvironment(const std::string& walletFile, const boost::filesystem::path& dataDir, std::string& errorStr);
/* verifies the database file */
static bool VerifyDatabaseFile(const std::string& walletFile, const boost::filesystem::path& dataDir, std::string& warningStr, std::string& errorStr, bool (*recoverFunc)(const std::string& strFile));

private:
CDB(const CDB&);
Expand Down
55 changes: 14 additions & 41 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -616,57 +616,30 @@ bool CWallet::Verify()
if (GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET))
return true;

LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0));
std::string walletFile = GetArg("-wallet", DEFAULT_WALLET_DAT);

LogPrintf("Using wallet %s\n", walletFile);
uiInterface.InitMessage(_("Verifying wallet..."));
std::string walletFile = GetArg("-wallet", DEFAULT_WALLET_DAT);

// Wallet file must be a plain filename without a directory
if (walletFile != boost::filesystem::basename(walletFile) + boost::filesystem::extension(walletFile))
return InitError(strprintf(_("Wallet %s resides outside data directory %s"), walletFile, GetDataDir().string()));
std::string strError;
if (!CWalletDB::VerifyEnvironment(walletFile, GetDataDir().string(), strError))
return InitError(strError);

if (!bitdb.Open(GetDataDir()))
{
// try moving the database env out of the way
boost::filesystem::path pathDatabase = GetDataDir() / "database";
boost::filesystem::path pathDatabaseBak = GetDataDir() / strprintf("database.%d.bak", GetTime());
try {
boost::filesystem::rename(pathDatabase, pathDatabaseBak);
LogPrintf("Moved old %s to %s. Retrying.\n", pathDatabase.string(), pathDatabaseBak.string());
} catch (const boost::filesystem::filesystem_error&) {
// failure is ok (well, not really, but it's not worse than what we started with)
}

// try again
if (!bitdb.Open(GetDataDir())) {
// if it still fails, it probably means we can't even create the database env
return InitError(strprintf(_("Error initializing wallet database environment %s!"), GetDataDir()));
}
}

if (GetBoolArg("-salvagewallet", false))
{
// Recover readable keypairs:
if (!CWalletDB::Recover(bitdb, walletFile, true))
CWallet dummyWallet;
if (!CWalletDB::Recover(walletFile, (void *)&dummyWallet, CWalletDB::RecoverKeysOnlyFilter))
return false;
}

if (boost::filesystem::exists(GetDataDir() / walletFile))

std::string strWarning;
bool dbV = CWalletDB::VerifyDatabaseFile(walletFile, GetDataDir().string(), strWarning, strError);
if (!strWarning.empty())
InitWarning(strWarning);
if (!dbV)
{
CDBEnv::VerifyResult r = bitdb.Verify(walletFile, CWalletDB::Recover);
if (r == CDBEnv::RECOVER_OK)
{
InitWarning(strprintf(_("Warning: Wallet file corrupt, data salvaged!"
" Original %s saved as %s in %s; if"
" your balance or transactions are incorrect you should"
" restore from a backup."),
walletFile, "wallet.{timestamp}.bak", GetDataDir()));
}
if (r == CDBEnv::RECOVER_FAIL)
return InitError(strprintf(_("%s corrupt, salvage failed"), walletFile));
InitError(strError);
return false;
}

return true;
}

Expand Down
Loading

0 comments on commit 29bbfc5

Please sign in to comment.