Skip to content

Commit

Permalink
Allow wallet files in multiple directories
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
ryanofsky committed Mar 3, 2018
1 parent 6012f1c commit d8a99f6
Show file tree
Hide file tree
Showing 13 changed files with 201 additions and 151 deletions.
2 changes: 1 addition & 1 deletion src/bench/coin_selection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector<CO
// (https://github.com/bitcoin/bitcoin/issues/7883#issuecomment-224807484)
static void CoinSelection(benchmark::State& state)
{
const CWallet wallet;
const CWallet wallet("dummy", CWalletDBWrapper::CreateDummy());
std::vector<COutput> vCoins;
LOCK(wallet.cs_wallet);

Expand Down
7 changes: 1 addition & 6 deletions src/qt/test/wallettests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,7 @@ void TestGUI()
for (int i = 0; i < 5; ++i) {
test.CreateAndProcessBlock({}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey()));
}
bitdb.MakeMock();
std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, "wallet_test.dat"));
CWallet wallet(std::move(dbw));
CWallet wallet("mock", CWalletDBWrapper::CreateMock());
bool firstRun;
wallet.LoadWallet(firstRun);
{
Expand Down Expand Up @@ -260,9 +258,6 @@ void TestGUI()
QPushButton* removeRequestButton = receiveCoinsDialog.findChild<QPushButton*>("removeRequestButton");
removeRequestButton->click();
QCOMPARE(requestTableModel->rowCount({}), currentRowCount-1);

bitdb.Flush(true);
bitdb.Reset();
}

}
Expand Down
108 changes: 78 additions & 30 deletions src/wallet/db.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,44 @@ void CheckUniqueFileid(const CDBEnv& env, const std::string& filename, Db& db)
}
}
}

CCriticalSection cs_db;
std::map<std::string, CDBEnv> g_dbenvs; //!< Map from directory name to open db environment.
} // namespace

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

//
// CDB
//

CDBEnv bitdb;

void CDBEnv::EnvShutdown()
void CDBEnv::Close()
{
if (!fDbEnvInit)
return;

fDbEnvInit = false;

for (auto& db : mapDb) {
auto count = mapFileUseCount.find(db.first);
assert(count == mapFileUseCount.end() || count->second == 0);
if (db.second) {
db.second->close(0);
delete db.second;
db.second = nullptr;
}
}

int ret = dbenv->close(0);
if (ret != 0)
LogPrintf("CDBEnv::EnvShutdown: Error %d shutting down database environment: %s\n", ret, DbEnv::strerror(ret));
Expand All @@ -80,29 +104,24 @@ void CDBEnv::Reset()
fMockDb = false;
}

CDBEnv::CDBEnv()
CDBEnv::CDBEnv(const fs::path& dir_path) : strPath(dir_path.string())
{
Reset();
}

CDBEnv::~CDBEnv()
{
EnvShutdown();
Close();
}

void CDBEnv::Close()
{
EnvShutdown();
}

bool CDBEnv::Open(const fs::path& pathIn, bool retry)
bool CDBEnv::Open(bool retry)
{
if (fDbEnvInit)
return true;

boost::this_thread::interruption_point();

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

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

bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& newFilename)
bool CDB::Recover(const fs::path& file_path, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& newFilename)
{
std::string filename;
CDBEnv* env = GetWalletEnv(file_path, filename);

// Recovery procedure:
// move wallet file to walletfilename.timestamp.bak
// Call Salvage with fAggressive=true to
Expand All @@ -225,7 +247,7 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco
int64_t now = GetTime();
newFilename = strprintf("%s.%d.bak", filename, now);

int result = bitdb.dbenv->dbrename(nullptr, filename.c_str(), nullptr,
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);
Expand All @@ -236,15 +258,15 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco
}

std::vector<CDBEnv::KeyValPair> salvagedData;
bool fSuccess = bitdb.Salvage(newFilename, true, salvagedData);
bool fSuccess = env->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 = MakeUnique<Db>(bitdb.dbenv.get(), 0);
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
Expand All @@ -257,7 +279,7 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco
return false;
}

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

bool CDB::VerifyEnvironment(const std::string& walletFile, const fs::path& walletDir, std::string& errorStr)
bool CDB::VerifyEnvironment(const fs::path& file_path, std::string& errorStr)
{
std::string walletFile;
CDBEnv* env = GetWalletEnv(file_path, walletFile);
fs::path walletDir = env->Directory();

LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0));
LogPrintf("Using wallet %s\n", walletFile);

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

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

return true;
}

bool CDB::VerifyDatabaseFile(const std::string& walletFile, const fs::path& walletDir, std::string& warningStr, std::string& errorStr, CDBEnv::recoverFunc_type recoverFunc)
bool CDB::VerifyDatabaseFile(const fs::path& file_path, std::string& warningStr, std::string& errorStr, CDBEnv::recoverFunc_type recoverFunc)
{
std::string walletFile;
CDBEnv* env = GetWalletEnv(file_path, walletFile);
fs::path walletDir = env->Directory();

if (fs::exists(walletDir / walletFile))
{
std::string backup_filename;
CDBEnv::VerifyResult r = bitdb.Verify(walletFile, recoverFunc, backup_filename);
CDBEnv::VerifyResult r = env->Verify(walletFile, recoverFunc, backup_filename);
if (r == CDBEnv::RECOVER_OK)
{
warningStr = strprintf(_("Warning: Wallet file corrupt, data salvaged!"
Expand Down Expand Up @@ -414,8 +444,8 @@ CDB::CDB(CWalletDBWrapper& dbw, const char* pszMode, bool fFlushOnCloseIn) : pdb
nFlags |= DB_CREATE;

{
LOCK(env->cs_db);
if (!env->Open(GetWalletDir()))
LOCK(cs_db);
if (!env->Open(false /* retry */))
throw std::runtime_error("CDB: Failed to open database environment.");

pdb = env->mapDb[strFilename];
Expand All @@ -442,7 +472,25 @@ CDB::CDB(CWalletDBWrapper& dbw, const char* pszMode, bool fFlushOnCloseIn) : pdb
if (ret != 0) {
throw std::runtime_error(strprintf("CDB: Error %d, can't open database %s", ret, strFilename));
}
CheckUniqueFileid(*env, strFilename, *pdb_temp);

// Call CheckUniqueFileid on the containing BDB environment to
// avoid BDB data consistency bugs that happen when different data
// files in the same environment have the same fileid.
//
// Also call CheckUniqueFileid on all the other g_dbenvs to prevent
// bitcoin from opening the same data file through another
// environment when the file is referenced through equivalent but
// not obviously identical symlinked or hard linked or bind mounted
// paths. In the future a more relaxed check for equal inode and
// device ids could be done instead, which would allow opening
// different backup copies of a wallet at the same time. Maybe even
// more ideally, an exclusive lock for accessing the database could
// be implemented, so no equality checks are needed at all. (Newer
// versions of BDB have an set_lk_exclusive method for this
// purpose, but the older version we use does not.)
for (auto& env : g_dbenvs) {
CheckUniqueFileid(env.second, strFilename, *pdb_temp);
}

pdb = pdb_temp.release();
env->mapDb[strFilename] = pdb;
Expand Down Expand Up @@ -490,7 +538,7 @@ void CDB::Close()
Flush();

{
LOCK(env->cs_db);
LOCK(cs_db);
--env->mapFileUseCount[strFile];
}
}
Expand Down Expand Up @@ -518,7 +566,7 @@ bool CDB::Rewrite(CWalletDBWrapper& dbw, const char* pszSkip)
const std::string& strFile = dbw.strFile;
while (true) {
{
LOCK(env->cs_db);
LOCK(cs_db);
if (!env->mapFileUseCount.count(strFile) || env->mapFileUseCount[strFile] == 0) {
// Flush log data to the dat file
env->CloseDb(strFile);
Expand Down Expand Up @@ -646,7 +694,7 @@ bool CDB::PeriodicFlush(CWalletDBWrapper& dbw)
bool ret = false;
CDBEnv *env = dbw.env;
const std::string& strFile = dbw.strFile;
TRY_LOCK(bitdb.cs_db,lockDb);
TRY_LOCK(cs_db, lockDb);
if (lockDb)
{
// Don't do this if any databases are in use
Expand Down Expand Up @@ -694,7 +742,7 @@ bool CWalletDBWrapper::Backup(const std::string& strDest)
while (true)
{
{
LOCK(env->cs_db);
LOCK(cs_db);
if (!env->mapFileUseCount.count(strFile) || env->mapFileUseCount[strFile] == 0)
{
// Flush log data to the dat file
Expand Down
Loading

0 comments on commit d8a99f6

Please sign in to comment.