Skip to content

Commit d8a99f6

Browse files
committed
Allow wallet files in multiple directories
Remove requirement that two wallet files can only be opened at the same time if they are contained in the same directory. This change mostly consists of updates to function signatures (updating functions to take fs::path arguments, instead of combinations of strings, fs::path, and CDBEnv / CWalletDBWrapper arguments).
1 parent 6012f1c commit d8a99f6

File tree

13 files changed

+201
-151
lines changed

13 files changed

+201
-151
lines changed

src/bench/coin_selection.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector<CO
3232
// (https://github.com/bitcoin/bitcoin/issues/7883#issuecomment-224807484)
3333
static void CoinSelection(benchmark::State& state)
3434
{
35-
const CWallet wallet;
35+
const CWallet wallet("dummy", CWalletDBWrapper::CreateDummy());
3636
std::vector<COutput> vCoins;
3737
LOCK(wallet.cs_wallet);
3838

src/qt/test/wallettests.cpp

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,7 @@ void TestGUI()
157157
for (int i = 0; i < 5; ++i) {
158158
test.CreateAndProcessBlock({}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey()));
159159
}
160-
bitdb.MakeMock();
161-
std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, "wallet_test.dat"));
162-
CWallet wallet(std::move(dbw));
160+
CWallet wallet("mock", CWalletDBWrapper::CreateMock());
163161
bool firstRun;
164162
wallet.LoadWallet(firstRun);
165163
{
@@ -260,9 +258,6 @@ void TestGUI()
260258
QPushButton* removeRequestButton = receiveCoinsDialog.findChild<QPushButton*>("removeRequestButton");
261259
removeRequestButton->click();
262260
QCOMPARE(requestTableModel->rowCount({}), currentRowCount-1);
263-
264-
bitdb.Flush(true);
265-
bitdb.Reset();
266261
}
267262

268263
}

src/wallet/db.cpp

Lines changed: 78 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -52,20 +52,44 @@ void CheckUniqueFileid(const CDBEnv& env, const std::string& filename, Db& db)
5252
}
5353
}
5454
}
55+
56+
CCriticalSection cs_db;
57+
std::map<std::string, CDBEnv> g_dbenvs; //!< Map from directory name to open db environment.
5558
} // namespace
5659

60+
CDBEnv* GetWalletEnv(const fs::path& wallet_path, std::string& database_filename)
61+
{
62+
fs::path env_directory = wallet_path.parent_path();
63+
database_filename = wallet_path.filename().string();
64+
LOCK(cs_db);
65+
// Note: An ununsed temporary CDBEnv object may be created inside the
66+
// emplace function if the key already exists. This is a little inefficient,
67+
// but not a big concern since the map will be changed in the future to hold
68+
// pointers instead of objects, anyway.
69+
return &g_dbenvs.emplace(std::piecewise_construct, std::forward_as_tuple(env_directory.string()), std::forward_as_tuple(env_directory)).first->second;
70+
}
71+
5772
//
5873
// CDB
5974
//
6075

61-
CDBEnv bitdb;
62-
63-
void CDBEnv::EnvShutdown()
76+
void CDBEnv::Close()
6477
{
6578
if (!fDbEnvInit)
6679
return;
6780

6881
fDbEnvInit = false;
82+
83+
for (auto& db : mapDb) {
84+
auto count = mapFileUseCount.find(db.first);
85+
assert(count == mapFileUseCount.end() || count->second == 0);
86+
if (db.second) {
87+
db.second->close(0);
88+
delete db.second;
89+
db.second = nullptr;
90+
}
91+
}
92+
6993
int ret = dbenv->close(0);
7094
if (ret != 0)
7195
LogPrintf("CDBEnv::EnvShutdown: Error %d shutting down database environment: %s\n", ret, DbEnv::strerror(ret));
@@ -80,29 +104,24 @@ void CDBEnv::Reset()
80104
fMockDb = false;
81105
}
82106

83-
CDBEnv::CDBEnv()
107+
CDBEnv::CDBEnv(const fs::path& dir_path) : strPath(dir_path.string())
84108
{
85109
Reset();
86110
}
87111

88112
CDBEnv::~CDBEnv()
89113
{
90-
EnvShutdown();
114+
Close();
91115
}
92116

93-
void CDBEnv::Close()
94-
{
95-
EnvShutdown();
96-
}
97-
98-
bool CDBEnv::Open(const fs::path& pathIn, bool retry)
117+
bool CDBEnv::Open(bool retry)
99118
{
100119
if (fDbEnvInit)
101120
return true;
102121

103122
boost::this_thread::interruption_point();
104123

105-
strPath = pathIn.string();
124+
fs::path pathIn = strPath;
106125
if (!LockDirectory(pathIn, ".walletlock")) {
107126
LogPrintf("Cannot obtain a lock on wallet directory %s. Another instance of bitcoin may be using it.\n", strPath);
108127
return false;
@@ -150,7 +169,7 @@ bool CDBEnv::Open(const fs::path& pathIn, bool retry)
150169
// failure is ok (well, not really, but it's not worse than what we started with)
151170
}
152171
// try opening it again one more time
153-
if (!Open(pathIn, false)) {
172+
if (!Open(false /* retry */)) {
154173
// if it still fails, it probably means we can't even create the database env
155174
return false;
156175
}
@@ -209,12 +228,15 @@ CDBEnv::VerifyResult CDBEnv::Verify(const std::string& strFile, recoverFunc_type
209228
return RECOVER_FAIL;
210229

211230
// Try to recover:
212-
bool fRecovered = (*recoverFunc)(strFile, out_backup_filename);
231+
bool fRecovered = (*recoverFunc)(fs::path(strPath) / strFile, out_backup_filename);
213232
return (fRecovered ? RECOVER_OK : RECOVER_FAIL);
214233
}
215234

216-
bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& newFilename)
235+
bool CDB::Recover(const fs::path& file_path, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& newFilename)
217236
{
237+
std::string filename;
238+
CDBEnv* env = GetWalletEnv(file_path, filename);
239+
218240
// Recovery procedure:
219241
// move wallet file to walletfilename.timestamp.bak
220242
// Call Salvage with fAggressive=true to
@@ -225,7 +247,7 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco
225247
int64_t now = GetTime();
226248
newFilename = strprintf("%s.%d.bak", filename, now);
227249

228-
int result = bitdb.dbenv->dbrename(nullptr, filename.c_str(), nullptr,
250+
int result = env->dbenv->dbrename(nullptr, filename.c_str(), nullptr,
229251
newFilename.c_str(), DB_AUTO_COMMIT);
230252
if (result == 0)
231253
LogPrintf("Renamed %s to %s\n", filename, newFilename);
@@ -236,15 +258,15 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco
236258
}
237259

238260
std::vector<CDBEnv::KeyValPair> salvagedData;
239-
bool fSuccess = bitdb.Salvage(newFilename, true, salvagedData);
261+
bool fSuccess = env->Salvage(newFilename, true, salvagedData);
240262
if (salvagedData.empty())
241263
{
242264
LogPrintf("Salvage(aggressive) found no records in %s.\n", newFilename);
243265
return false;
244266
}
245267
LogPrintf("Salvage(aggressive) found %u records\n", salvagedData.size());
246268

247-
std::unique_ptr<Db> pdbCopy = MakeUnique<Db>(bitdb.dbenv.get(), 0);
269+
std::unique_ptr<Db> pdbCopy = MakeUnique<Db>(env->dbenv.get(), 0);
248270
int ret = pdbCopy->open(nullptr, // Txn pointer
249271
filename.c_str(), // Filename
250272
"main", // Logical db name
@@ -257,7 +279,7 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco
257279
return false;
258280
}
259281

260-
DbTxn* ptxn = bitdb.TxnBegin();
282+
DbTxn* ptxn = env->TxnBegin();
261283
for (CDBEnv::KeyValPair& row : salvagedData)
262284
{
263285
if (recoverKVcallback)
@@ -279,8 +301,12 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco
279301
return fSuccess;
280302
}
281303

282-
bool CDB::VerifyEnvironment(const std::string& walletFile, const fs::path& walletDir, std::string& errorStr)
304+
bool CDB::VerifyEnvironment(const fs::path& file_path, std::string& errorStr)
283305
{
306+
std::string walletFile;
307+
CDBEnv* env = GetWalletEnv(file_path, walletFile);
308+
fs::path walletDir = env->Directory();
309+
284310
LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0));
285311
LogPrintf("Using wallet %s\n", walletFile);
286312

@@ -291,20 +317,24 @@ bool CDB::VerifyEnvironment(const std::string& walletFile, const fs::path& walle
291317
return false;
292318
}
293319

294-
if (!bitdb.Open(walletDir, true)) {
320+
if (!env->Open(true /* retry */)) {
295321
errorStr = strprintf(_("Error initializing wallet database environment %s!"), walletDir);
296322
return false;
297323
}
298324

299325
return true;
300326
}
301327

302-
bool CDB::VerifyDatabaseFile(const std::string& walletFile, const fs::path& walletDir, std::string& warningStr, std::string& errorStr, CDBEnv::recoverFunc_type recoverFunc)
328+
bool CDB::VerifyDatabaseFile(const fs::path& file_path, std::string& warningStr, std::string& errorStr, CDBEnv::recoverFunc_type recoverFunc)
303329
{
330+
std::string walletFile;
331+
CDBEnv* env = GetWalletEnv(file_path, walletFile);
332+
fs::path walletDir = env->Directory();
333+
304334
if (fs::exists(walletDir / walletFile))
305335
{
306336
std::string backup_filename;
307-
CDBEnv::VerifyResult r = bitdb.Verify(walletFile, recoverFunc, backup_filename);
337+
CDBEnv::VerifyResult r = env->Verify(walletFile, recoverFunc, backup_filename);
308338
if (r == CDBEnv::RECOVER_OK)
309339
{
310340
warningStr = strprintf(_("Warning: Wallet file corrupt, data salvaged!"
@@ -414,8 +444,8 @@ CDB::CDB(CWalletDBWrapper& dbw, const char* pszMode, bool fFlushOnCloseIn) : pdb
414444
nFlags |= DB_CREATE;
415445

416446
{
417-
LOCK(env->cs_db);
418-
if (!env->Open(GetWalletDir()))
447+
LOCK(cs_db);
448+
if (!env->Open(false /* retry */))
419449
throw std::runtime_error("CDB: Failed to open database environment.");
420450

421451
pdb = env->mapDb[strFilename];
@@ -442,7 +472,25 @@ CDB::CDB(CWalletDBWrapper& dbw, const char* pszMode, bool fFlushOnCloseIn) : pdb
442472
if (ret != 0) {
443473
throw std::runtime_error(strprintf("CDB: Error %d, can't open database %s", ret, strFilename));
444474
}
445-
CheckUniqueFileid(*env, strFilename, *pdb_temp);
475+
476+
// Call CheckUniqueFileid on the containing BDB environment to
477+
// avoid BDB data consistency bugs that happen when different data
478+
// files in the same environment have the same fileid.
479+
//
480+
// Also call CheckUniqueFileid on all the other g_dbenvs to prevent
481+
// bitcoin from opening the same data file through another
482+
// environment when the file is referenced through equivalent but
483+
// not obviously identical symlinked or hard linked or bind mounted
484+
// paths. In the future a more relaxed check for equal inode and
485+
// device ids could be done instead, which would allow opening
486+
// different backup copies of a wallet at the same time. Maybe even
487+
// more ideally, an exclusive lock for accessing the database could
488+
// be implemented, so no equality checks are needed at all. (Newer
489+
// versions of BDB have an set_lk_exclusive method for this
490+
// purpose, but the older version we use does not.)
491+
for (auto& env : g_dbenvs) {
492+
CheckUniqueFileid(env.second, strFilename, *pdb_temp);
493+
}
446494

447495
pdb = pdb_temp.release();
448496
env->mapDb[strFilename] = pdb;
@@ -490,7 +538,7 @@ void CDB::Close()
490538
Flush();
491539

492540
{
493-
LOCK(env->cs_db);
541+
LOCK(cs_db);
494542
--env->mapFileUseCount[strFile];
495543
}
496544
}
@@ -518,7 +566,7 @@ bool CDB::Rewrite(CWalletDBWrapper& dbw, const char* pszSkip)
518566
const std::string& strFile = dbw.strFile;
519567
while (true) {
520568
{
521-
LOCK(env->cs_db);
569+
LOCK(cs_db);
522570
if (!env->mapFileUseCount.count(strFile) || env->mapFileUseCount[strFile] == 0) {
523571
// Flush log data to the dat file
524572
env->CloseDb(strFile);
@@ -646,7 +694,7 @@ bool CDB::PeriodicFlush(CWalletDBWrapper& dbw)
646694
bool ret = false;
647695
CDBEnv *env = dbw.env;
648696
const std::string& strFile = dbw.strFile;
649-
TRY_LOCK(bitdb.cs_db,lockDb);
697+
TRY_LOCK(cs_db, lockDb);
650698
if (lockDb)
651699
{
652700
// Don't do this if any databases are in use
@@ -694,7 +742,7 @@ bool CWalletDBWrapper::Backup(const std::string& strDest)
694742
while (true)
695743
{
696744
{
697-
LOCK(env->cs_db);
745+
LOCK(cs_db);
698746
if (!env->mapFileUseCount.count(strFile) || env->mapFileUseCount[strFile] == 0)
699747
{
700748
// Flush log data to the dat file

0 commit comments

Comments
 (0)