Skip to content

Commit 8a3b075

Browse files
committed
Merge #8574: [Wallet] refactor CWallet/CWalletDB/CDB
7184e25 [Wallet] refactor CWallet/CWalletDB/CDB (Jonas Schnelli) Tree-SHA512: a1993dcfc3505459613e8be3f6560ef32466fd7c649bff358f12af118e633aadd648a090f4af60743a827c9cb624e4ec63eb0202326da4779fc18249bb77da1e
2 parents fa625b0 + 7184e25 commit 8a3b075

File tree

5 files changed

+237
-152
lines changed

5 files changed

+237
-152
lines changed

src/wallet/db.cpp

Lines changed: 165 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#endif
1919

2020
#include <boost/filesystem.hpp>
21+
#include <boost/foreach.hpp>
2122
#include <boost/thread.hpp>
2223
#include <boost/version.hpp>
2324

@@ -145,7 +146,7 @@ void CDBEnv::MakeMock()
145146
fMockDb = true;
146147
}
147148

148-
CDBEnv::VerifyResult CDBEnv::Verify(const std::string& strFile, bool (*recoverFunc)(CDBEnv& dbenv, const std::string& strFile))
149+
CDBEnv::VerifyResult CDBEnv::Verify(const std::string& strFile, bool (*recoverFunc)(const std::string& strFile))
149150
{
150151
LOCK(cs_db);
151152
assert(mapFileUseCount.count(strFile) == 0);
@@ -158,10 +159,134 @@ CDBEnv::VerifyResult CDBEnv::Verify(const std::string& strFile, bool (*recoverFu
158159
return RECOVER_FAIL;
159160

160161
// Try to recover:
161-
bool fRecovered = (*recoverFunc)(*this, strFile);
162+
bool fRecovered = (*recoverFunc)(strFile);
162163
return (fRecovered ? RECOVER_OK : RECOVER_FAIL);
163164
}
164165

166+
bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue))
167+
{
168+
// Recovery procedure:
169+
// move wallet file to wallet.timestamp.bak
170+
// Call Salvage with fAggressive=true to
171+
// get as much data as possible.
172+
// Rewrite salvaged data to fresh wallet file
173+
// Set -rescan so any missing transactions will be
174+
// found.
175+
int64_t now = GetTime();
176+
std::string newFilename = strprintf("wallet.%d.bak", now);
177+
178+
int result = bitdb.dbenv->dbrename(NULL, filename.c_str(), NULL,
179+
newFilename.c_str(), DB_AUTO_COMMIT);
180+
if (result == 0)
181+
LogPrintf("Renamed %s to %s\n", filename, newFilename);
182+
else
183+
{
184+
LogPrintf("Failed to rename %s to %s\n", filename, newFilename);
185+
return false;
186+
}
187+
188+
std::vector<CDBEnv::KeyValPair> salvagedData;
189+
bool fSuccess = bitdb.Salvage(newFilename, true, salvagedData);
190+
if (salvagedData.empty())
191+
{
192+
LogPrintf("Salvage(aggressive) found no records in %s.\n", newFilename);
193+
return false;
194+
}
195+
LogPrintf("Salvage(aggressive) found %u records\n", salvagedData.size());
196+
197+
std::unique_ptr<Db> pdbCopy(new Db(bitdb.dbenv, 0));
198+
int ret = pdbCopy->open(NULL, // Txn pointer
199+
filename.c_str(), // Filename
200+
"main", // Logical db name
201+
DB_BTREE, // Database type
202+
DB_CREATE, // Flags
203+
0);
204+
if (ret > 0)
205+
{
206+
LogPrintf("Cannot create database file %s\n", filename);
207+
return false;
208+
}
209+
210+
DbTxn* ptxn = bitdb.TxnBegin();
211+
BOOST_FOREACH(CDBEnv::KeyValPair& row, salvagedData)
212+
{
213+
if (recoverKVcallback)
214+
{
215+
CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION);
216+
CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION);
217+
string strType, strErr;
218+
if (!(*recoverKVcallback)(callbackDataIn, ssKey, ssValue))
219+
continue;
220+
}
221+
Dbt datKey(&row.first[0], row.first.size());
222+
Dbt datValue(&row.second[0], row.second.size());
223+
int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE);
224+
if (ret2 > 0)
225+
fSuccess = false;
226+
}
227+
ptxn->commit(0);
228+
pdbCopy->close(0);
229+
230+
return fSuccess;
231+
}
232+
233+
bool CDB::VerifyEnvironment(const std::string& walletFile, const boost::filesystem::path& dataDir, std::string& errorStr)
234+
{
235+
LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0));
236+
LogPrintf("Using wallet %s\n", walletFile);
237+
238+
// Wallet file must be a plain filename without a directory
239+
if (walletFile != boost::filesystem::basename(walletFile) + boost::filesystem::extension(walletFile))
240+
{
241+
errorStr = strprintf(_("Wallet %s resides outside data directory %s"), walletFile, dataDir.string());
242+
return false;
243+
}
244+
245+
if (!bitdb.Open(dataDir))
246+
{
247+
// try moving the database env out of the way
248+
boost::filesystem::path pathDatabase = dataDir / "database";
249+
boost::filesystem::path pathDatabaseBak = dataDir / strprintf("database.%d.bak", GetTime());
250+
try {
251+
boost::filesystem::rename(pathDatabase, pathDatabaseBak);
252+
LogPrintf("Moved old %s to %s. Retrying.\n", pathDatabase.string(), pathDatabaseBak.string());
253+
} catch (const boost::filesystem::filesystem_error&) {
254+
// failure is ok (well, not really, but it's not worse than what we started with)
255+
}
256+
257+
// try again
258+
if (!bitdb.Open(dataDir)) {
259+
// if it still fails, it probably means we can't even create the database env
260+
errorStr = strprintf(_("Error initializing wallet database environment %s!"), GetDataDir());
261+
return false;
262+
}
263+
}
264+
return true;
265+
}
266+
267+
bool CDB::VerifyDatabaseFile(const std::string& walletFile, const boost::filesystem::path& dataDir, std::string& warningStr, std::string& errorStr, bool (*recoverFunc)(const std::string& strFile))
268+
{
269+
if (boost::filesystem::exists(dataDir / walletFile))
270+
{
271+
CDBEnv::VerifyResult r = bitdb.Verify(walletFile, recoverFunc);
272+
if (r == CDBEnv::RECOVER_OK)
273+
{
274+
warningStr = strprintf(_("Warning: Wallet file corrupt, data salvaged!"
275+
" Original %s saved as %s in %s; if"
276+
" your balance or transactions are incorrect you should"
277+
" restore from a backup."),
278+
walletFile, "wallet.{timestamp}.bak", dataDir);
279+
}
280+
if (r == CDBEnv::RECOVER_FAIL)
281+
{
282+
errorStr = strprintf(_("%s corrupt, salvage failed"), walletFile);
283+
return false;
284+
}
285+
}
286+
// also return true if files does not exists
287+
return true;
288+
}
289+
165290
/* End of headers, beginning of key/value data */
166291
static const char *HEADER_END = "HEADER=END";
167292
/* End of key/value data */
@@ -473,3 +598,41 @@ void CDBEnv::Flush(bool fShutdown)
473598
}
474599
}
475600
}
601+
602+
bool CDB::PeriodicFlush(std::string strFile)
603+
{
604+
bool ret = false;
605+
TRY_LOCK(bitdb.cs_db,lockDb);
606+
if (lockDb)
607+
{
608+
// Don't do this if any databases are in use
609+
int nRefCount = 0;
610+
map<string, int>::iterator mi = bitdb.mapFileUseCount.begin();
611+
while (mi != bitdb.mapFileUseCount.end())
612+
{
613+
nRefCount += (*mi).second;
614+
mi++;
615+
}
616+
617+
if (nRefCount == 0)
618+
{
619+
boost::this_thread::interruption_point();
620+
map<string, int>::iterator mi = bitdb.mapFileUseCount.find(strFile);
621+
if (mi != bitdb.mapFileUseCount.end())
622+
{
623+
LogPrint("db", "Flushing %s\n", strFile);
624+
int64_t nStart = GetTimeMillis();
625+
626+
// Flush wallet file so it's self contained
627+
bitdb.CloseDb(strFile);
628+
bitdb.CheckpointLSN(strFile);
629+
630+
bitdb.mapFileUseCount.erase(mi++);
631+
LogPrint("db", "Flushed %s %dms\n", strFile, GetTimeMillis() - nStart);
632+
ret = true;
633+
}
634+
}
635+
}
636+
637+
return ret;
638+
}

src/wallet/db.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class CDBEnv
5656
enum VerifyResult { VERIFY_OK,
5757
RECOVER_OK,
5858
RECOVER_FAIL };
59-
VerifyResult Verify(const std::string& strFile, bool (*recoverFunc)(CDBEnv& dbenv, const std::string& strFile));
59+
VerifyResult Verify(const std::string& strFile, bool (*recoverFunc)(const std::string& strFile));
6060
/**
6161
* Salvage data from a file that Verify says is bad.
6262
* fAggressive sets the DB_AGGRESSIVE flag (see berkeley DB->verify() method documentation).
@@ -104,6 +104,15 @@ class CDB
104104
public:
105105
void Flush();
106106
void Close();
107+
static bool Recover(const std::string& filename, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue));
108+
109+
/* flush the wallet passively (TRY_LOCK)
110+
ideal to be called periodically */
111+
static bool PeriodicFlush(std::string strFile);
112+
/* verifies the database environment */
113+
static bool VerifyEnvironment(const std::string& walletFile, const boost::filesystem::path& dataDir, std::string& errorStr);
114+
/* verifies the database file */
115+
static bool VerifyDatabaseFile(const std::string& walletFile, const boost::filesystem::path& dataDir, std::string& warningStr, std::string& errorStr, bool (*recoverFunc)(const std::string& strFile));
107116

108117
private:
109118
CDB(const CDB&);

src/wallet/wallet.cpp

Lines changed: 14 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -444,57 +444,30 @@ bool CWallet::Verify()
444444
if (GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET))
445445
return true;
446446

447-
LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0));
448-
std::string walletFile = GetArg("-wallet", DEFAULT_WALLET_DAT);
449-
450-
LogPrintf("Using wallet %s\n", walletFile);
451447
uiInterface.InitMessage(_("Verifying wallet..."));
448+
std::string walletFile = GetArg("-wallet", DEFAULT_WALLET_DAT);
452449

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

457-
if (!bitdb.Open(GetDataDir()))
458-
{
459-
// try moving the database env out of the way
460-
boost::filesystem::path pathDatabase = GetDataDir() / "database";
461-
boost::filesystem::path pathDatabaseBak = GetDataDir() / strprintf("database.%d.bak", GetTime());
462-
try {
463-
boost::filesystem::rename(pathDatabase, pathDatabaseBak);
464-
LogPrintf("Moved old %s to %s. Retrying.\n", pathDatabase.string(), pathDatabaseBak.string());
465-
} catch (const boost::filesystem::filesystem_error&) {
466-
// failure is ok (well, not really, but it's not worse than what we started with)
467-
}
468-
469-
// try again
470-
if (!bitdb.Open(GetDataDir())) {
471-
// if it still fails, it probably means we can't even create the database env
472-
return InitError(strprintf(_("Error initializing wallet database environment %s!"), GetDataDir()));
473-
}
474-
}
475-
476454
if (GetBoolArg("-salvagewallet", false))
477455
{
478456
// Recover readable keypairs:
479-
if (!CWalletDB::Recover(bitdb, walletFile, true))
457+
CWallet dummyWallet;
458+
if (!CWalletDB::Recover(walletFile, (void *)&dummyWallet, CWalletDB::RecoverKeysOnlyFilter))
480459
return false;
481460
}
482-
483-
if (boost::filesystem::exists(GetDataDir() / walletFile))
461+
462+
std::string strWarning;
463+
bool dbV = CWalletDB::VerifyDatabaseFile(walletFile, GetDataDir().string(), strWarning, strError);
464+
if (!strWarning.empty())
465+
InitWarning(strWarning);
466+
if (!dbV)
484467
{
485-
CDBEnv::VerifyResult r = bitdb.Verify(walletFile, CWalletDB::Recover);
486-
if (r == CDBEnv::RECOVER_OK)
487-
{
488-
InitWarning(strprintf(_("Warning: Wallet file corrupt, data salvaged!"
489-
" Original %s saved as %s in %s; if"
490-
" your balance or transactions are incorrect you should"
491-
" restore from a backup."),
492-
walletFile, "wallet.{timestamp}.bak", GetDataDir()));
493-
}
494-
if (r == CDBEnv::RECOVER_FAIL)
495-
return InitError(strprintf(_("%s corrupt, salvage failed"), walletFile));
468+
InitError(strError);
469+
return false;
496470
}
497-
498471
return true;
499472
}
500473

0 commit comments

Comments
 (0)