Merge #15575: 0.17: Backport 15297
fe95f84542
qa: Test .walletlock file is closed (João Barbosa)2e9e904a5d
wallet: Close wallet env lock file (João Barbosa)22cdb6cf59
wallet: Close dbenv error file db.log (João Barbosa)f20513bd71
Tests: add unit tests for GetWalletEnv (Pierre Rochard)85c6263ddb
Trivial: add doxygen-compatible comments relating to BerkeleyEnvironment (Pierre Rochard)f22d02f537
Free BerkeleyEnvironment instances when not in use (Russell Yanofsky)0a9af2d4cb
wallet: Create IsDatabaseLoaded function (Chun Kuan Lee)7751ea37b6
Refactor: Move m_db pointers into BerkeleyDatabase (Russell Yanofsky)caf1146b13
wallet: Add trailing wallet.dat when detecting duplicate wallet if it's a directory. (Chun Kuan Lee)34da2b7c76
tests: add test case for loading copied wallet twice (Chun Kuan Lee)8965b6ab47
wallet: Fix duplicate fileid (Chun Kuan Lee)16e5759455
wallet: Refactor to use WalletLocation (João Barbosa)21693ff0b7
wallet: Add WalletLocation utility class (João Barbosa)1c98a758d0
No longer shutdown after encrypting the wallet (Andrew Chow)435df68c62
Move BerkeleyEnvironment deletion from internal method to callsite (Andrew Chow)048fda2a66
After encrypting the wallet, reload the database environment (Andrew Chow)f455979eb1
Add function to close all Db's and reload the databae environment (Andrew Chow) Pull request description: This PR backports the following pull requests: - #12493 [wallet] Reopen CDBEnv after encryption instead of shutting down - #14350 Add WalletLocation class - #14320 [bugfix] wallet: Fix duplicate fileid detection - #14552 wallet: detecting duplicate wallet by comparing the db filename. - #11911 Free BerkeleyEnvironment instances when not in use - #15297 wallet: Releases dangling files on BerkeleyEnvironment::Close Tree-SHA512: 52d759bc4f140ca96e39b37746cc20e786741b08ddc658a87ea77fbcfbb481f1c7b75aba4fc57ca9bca8ca7154e535da1fdd650fd114873655cd85c490c79f14
This commit is contained in:
commit
6cf81b01b4
27 changed files with 367 additions and 148 deletions
|
@ -95,6 +95,7 @@ BITCOIN_TESTS =\
|
|||
if ENABLE_WALLET
|
||||
BITCOIN_TESTS += \
|
||||
wallet/test/accounting_tests.cpp \
|
||||
wallet/test/db_tests.cpp \
|
||||
wallet/test/psbt_wallet_tests.cpp \
|
||||
wallet/test/wallet_tests.cpp \
|
||||
wallet/test/wallet_crypto_tests.cpp \
|
||||
|
|
|
@ -33,7 +33,7 @@ static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector<Ou
|
|||
// (https://github.com/bitcoin/bitcoin/issues/7883#issuecomment-224807484)
|
||||
static void CoinSelection(benchmark::State& state)
|
||||
{
|
||||
const CWallet wallet("dummy", WalletDatabase::CreateDummy());
|
||||
const CWallet wallet(WalletLocation(), WalletDatabase::CreateDummy());
|
||||
LOCK(wallet.cs_wallet);
|
||||
|
||||
// Add coins.
|
||||
|
@ -57,7 +57,7 @@ static void CoinSelection(benchmark::State& state)
|
|||
}
|
||||
|
||||
typedef std::set<CInputCoin> CoinSet;
|
||||
static const CWallet testWallet("dummy", WalletDatabase::CreateDummy());
|
||||
static const CWallet testWallet(WalletLocation(), WalletDatabase::CreateDummy());
|
||||
std::vector<std::unique_ptr<CWalletTx>> wtxn;
|
||||
|
||||
// Copied from src/wallet/test/coinselector_tests.cpp
|
||||
|
|
|
@ -123,16 +123,15 @@ void AskPassphraseDialog::accept()
|
|||
{
|
||||
QMessageBox::warning(this, tr("Wallet encrypted"),
|
||||
"<qt>" +
|
||||
tr("%1 will close now to finish the encryption process. "
|
||||
tr("Your wallet is now encrypted. "
|
||||
"Remember that encrypting your wallet cannot fully protect "
|
||||
"your bitcoins from being stolen by malware infecting your computer.").arg(tr(PACKAGE_NAME)) +
|
||||
"your bitcoins from being stolen by malware infecting your computer.") +
|
||||
"<br><br><b>" +
|
||||
tr("IMPORTANT: Any previous backups you have made of your wallet file "
|
||||
"should be replaced with the newly generated, encrypted wallet file. "
|
||||
"For security reasons, previous backups of the unencrypted wallet file "
|
||||
"will become useless as soon as you start using the new, encrypted wallet.") +
|
||||
"</b></qt>");
|
||||
QApplication::quit();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -57,7 +57,7 @@ void EditAddressAndSubmit(
|
|||
void TestAddAddressesToSendBook()
|
||||
{
|
||||
TestChain100Setup test;
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("mock", WalletDatabase::CreateMock());
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(WalletLocation(), WalletDatabase::CreateMock());
|
||||
bool firstRun;
|
||||
wallet->LoadWallet(firstRun);
|
||||
|
||||
|
|
|
@ -133,7 +133,7 @@ void TestGUI()
|
|||
for (int i = 0; i < 5; ++i) {
|
||||
test.CreateAndProcessBlock({}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey()));
|
||||
}
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("mock", WalletDatabase::CreateMock());
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(WalletLocation(), WalletDatabase::CreateMock());
|
||||
bool firstRun;
|
||||
wallet->LoadWallet(firstRun);
|
||||
{
|
||||
|
|
|
@ -174,6 +174,12 @@ bool LockDirectory(const fs::path& directory, const std::string lockfile_name, b
|
|||
return true;
|
||||
}
|
||||
|
||||
void UnlockDirectory(const fs::path& directory, const std::string& lockfile_name)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(cs_dir_locks);
|
||||
dir_locks.erase((directory / lockfile_name).string());
|
||||
}
|
||||
|
||||
void ReleaseDirectoryLocks()
|
||||
{
|
||||
std::lock_guard<std::mutex> ulock(cs_dir_locks);
|
||||
|
|
|
@ -77,6 +77,7 @@ int RaiseFileDescriptorLimit(int nMinFD);
|
|||
void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length);
|
||||
bool RenameOver(fs::path src, fs::path dest);
|
||||
bool LockDirectory(const fs::path& directory, const std::string lockfile_name, bool probe_only=false);
|
||||
void UnlockDirectory(const fs::path& directory, const std::string& lockfile_name);
|
||||
bool DirIsWritable(const fs::path& directory);
|
||||
|
||||
/** Release all directory locks. This is used for unit testing only, at runtime
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <boost/thread.hpp>
|
||||
|
||||
namespace {
|
||||
|
||||
//! Make sure database has a unique fileid within the environment. If it
|
||||
//! doesn't, throw an error. BDB caches do not work properly when more than one
|
||||
//! open database has the same fileid (values written to one database may show
|
||||
|
@ -29,36 +30,34 @@ namespace {
|
|||
//! (https://docs.oracle.com/cd/E17275_01/html/programmer_reference/program_copy.html),
|
||||
//! so bitcoin should never create different databases with the same fileid, but
|
||||
//! this error can be triggered if users manually copy database files.
|
||||
void CheckUniqueFileid(const BerkeleyEnvironment& env, const std::string& filename, Db& db)
|
||||
void CheckUniqueFileid(const BerkeleyEnvironment& env, const std::string& filename, Db& db, WalletDatabaseFileId& fileid)
|
||||
{
|
||||
if (env.IsMock()) return;
|
||||
|
||||
u_int8_t fileid[DB_FILE_ID_LEN];
|
||||
int ret = db.get_mpf()->get_fileid(fileid);
|
||||
int ret = db.get_mpf()->get_fileid(fileid.value);
|
||||
if (ret != 0) {
|
||||
throw std::runtime_error(strprintf("BerkeleyBatch: Can't open database %s (get_fileid failed with %d)", filename, ret));
|
||||
}
|
||||
|
||||
for (const auto& item : env.mapDb) {
|
||||
u_int8_t item_fileid[DB_FILE_ID_LEN];
|
||||
if (item.second && item.second->get_mpf()->get_fileid(item_fileid) == 0 &&
|
||||
memcmp(fileid, item_fileid, sizeof(fileid)) == 0) {
|
||||
const char* item_filename = nullptr;
|
||||
item.second->get_dbname(&item_filename, nullptr);
|
||||
for (const auto& item : env.m_fileids) {
|
||||
if (fileid == item.second && &fileid != &item.second) {
|
||||
throw std::runtime_error(strprintf("BerkeleyBatch: Can't open database %s (duplicates fileid %s from %s)", filename,
|
||||
HexStr(std::begin(item_fileid), std::end(item_fileid)),
|
||||
item_filename ? item_filename : "(unknown database)"));
|
||||
HexStr(std::begin(item.second.value), std::end(item.second.value)), item.first));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CCriticalSection cs_db;
|
||||
std::map<std::string, BerkeleyEnvironment> g_dbenvs GUARDED_BY(cs_db); //!< Map from directory name to open db environment.
|
||||
std::map<std::string, std::weak_ptr<BerkeleyEnvironment>> g_dbenvs GUARDED_BY(cs_db); //!< Map from directory name to db environment.
|
||||
} // namespace
|
||||
|
||||
BerkeleyEnvironment* GetWalletEnv(const fs::path& wallet_path, std::string& database_filename)
|
||||
bool WalletDatabaseFileId::operator==(const WalletDatabaseFileId& rhs) const
|
||||
{
|
||||
return memcmp(value, &rhs.value, sizeof(value)) == 0;
|
||||
}
|
||||
|
||||
static void SplitWalletPath(const fs::path& wallet_path, fs::path& env_directory, std::string& database_filename)
|
||||
{
|
||||
fs::path env_directory;
|
||||
if (fs::is_regular_file(wallet_path)) {
|
||||
// Special case for backwards compatibility: if wallet path points to an
|
||||
// existing file, treat it as the path to a BDB data file in a parent
|
||||
|
@ -71,12 +70,39 @@ BerkeleyEnvironment* GetWalletEnv(const fs::path& wallet_path, std::string& data
|
|||
env_directory = wallet_path;
|
||||
database_filename = "wallet.dat";
|
||||
}
|
||||
}
|
||||
|
||||
bool IsWalletLoaded(const fs::path& wallet_path)
|
||||
{
|
||||
fs::path env_directory;
|
||||
std::string database_filename;
|
||||
SplitWalletPath(wallet_path, env_directory, database_filename);
|
||||
LOCK(cs_db);
|
||||
// Note: An ununsed temporary BerkeleyEnvironment 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;
|
||||
auto env = g_dbenvs.find(env_directory.string());
|
||||
if (env == g_dbenvs.end()) return false;
|
||||
auto database = env->second.lock();
|
||||
return database && database->IsDatabaseLoaded(database_filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param[in] wallet_path Path to wallet directory. Or (for backwards compatibility only) a path to a berkeley btree data file inside a wallet directory.
|
||||
* @param[out] database_filename Filename of berkeley btree data file inside the wallet directory.
|
||||
* @return A shared pointer to the BerkeleyEnvironment object for the wallet directory, never empty because ~BerkeleyEnvironment
|
||||
* erases the weak pointer from the g_dbenvs map.
|
||||
* @post A new BerkeleyEnvironment weak pointer is inserted into g_dbenvs if the directory path key was not already in the map.
|
||||
*/
|
||||
std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& wallet_path, std::string& database_filename)
|
||||
{
|
||||
fs::path env_directory;
|
||||
SplitWalletPath(wallet_path, env_directory, database_filename);
|
||||
LOCK(cs_db);
|
||||
auto inserted = g_dbenvs.emplace(env_directory.string(), std::weak_ptr<BerkeleyEnvironment>());
|
||||
if (inserted.second) {
|
||||
auto env = std::make_shared<BerkeleyEnvironment>(env_directory.string());
|
||||
inserted.first->second = env;
|
||||
return env;
|
||||
}
|
||||
return inserted.first->second.lock();
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -90,21 +116,28 @@ void BerkeleyEnvironment::Close()
|
|||
|
||||
fDbEnvInit = false;
|
||||
|
||||
for (auto& db : mapDb) {
|
||||
for (auto& db : m_databases) {
|
||||
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;
|
||||
BerkeleyDatabase& database = db.second.get();
|
||||
if (database.m_db) {
|
||||
database.m_db->close(0);
|
||||
database.m_db.reset();
|
||||
}
|
||||
}
|
||||
|
||||
FILE* error_file = nullptr;
|
||||
dbenv->get_errfile(&error_file);
|
||||
|
||||
int ret = dbenv->close(0);
|
||||
if (ret != 0)
|
||||
LogPrintf("BerkeleyEnvironment::Close: Error %d closing database environment: %s\n", ret, DbEnv::strerror(ret));
|
||||
if (!fMockDb)
|
||||
DbEnv((u_int32_t)0).remove(strPath.c_str(), 0);
|
||||
|
||||
if (error_file) fclose(error_file);
|
||||
|
||||
UnlockDirectory(strPath, ".walletlock");
|
||||
}
|
||||
|
||||
void BerkeleyEnvironment::Reset()
|
||||
|
@ -121,6 +154,7 @@ BerkeleyEnvironment::BerkeleyEnvironment(const fs::path& dir_path) : strPath(dir
|
|||
|
||||
BerkeleyEnvironment::~BerkeleyEnvironment()
|
||||
{
|
||||
g_dbenvs.erase(strPath);
|
||||
Close();
|
||||
}
|
||||
|
||||
|
@ -198,10 +232,10 @@ bool BerkeleyEnvironment::Open(bool retry)
|
|||
return true;
|
||||
}
|
||||
|
||||
void BerkeleyEnvironment::MakeMock()
|
||||
//! Construct an in-memory mock Berkeley environment for testing and as a place-holder for g_dbenvs emplace
|
||||
BerkeleyEnvironment::BerkeleyEnvironment()
|
||||
{
|
||||
if (fDbEnvInit)
|
||||
throw std::runtime_error("BerkeleyEnvironment::MakeMock: Already initialized");
|
||||
Reset();
|
||||
|
||||
boost::this_thread::interruption_point();
|
||||
|
||||
|
@ -250,7 +284,7 @@ BerkeleyEnvironment::VerifyResult BerkeleyEnvironment::Verify(const std::string&
|
|||
bool BerkeleyBatch::Recover(const fs::path& file_path, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& newFilename)
|
||||
{
|
||||
std::string filename;
|
||||
BerkeleyEnvironment* env = GetWalletEnv(file_path, filename);
|
||||
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, filename);
|
||||
|
||||
// Recovery procedure:
|
||||
// move wallet file to walletfilename.timestamp.bak
|
||||
|
@ -319,7 +353,7 @@ bool BerkeleyBatch::Recover(const fs::path& file_path, void *callbackDataIn, boo
|
|||
bool BerkeleyBatch::VerifyEnvironment(const fs::path& file_path, std::string& errorStr)
|
||||
{
|
||||
std::string walletFile;
|
||||
BerkeleyEnvironment* env = GetWalletEnv(file_path, walletFile);
|
||||
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, walletFile);
|
||||
fs::path walletDir = env->Directory();
|
||||
|
||||
LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0));
|
||||
|
@ -343,7 +377,7 @@ bool BerkeleyBatch::VerifyEnvironment(const fs::path& file_path, std::string& er
|
|||
bool BerkeleyBatch::VerifyDatabaseFile(const fs::path& file_path, std::string& warningStr, std::string& errorStr, BerkeleyEnvironment::recoverFunc_type recoverFunc)
|
||||
{
|
||||
std::string walletFile;
|
||||
BerkeleyEnvironment* env = GetWalletEnv(file_path, walletFile);
|
||||
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, walletFile);
|
||||
fs::path walletDir = env->Directory();
|
||||
|
||||
if (fs::exists(walletDir / walletFile))
|
||||
|
@ -447,7 +481,7 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo
|
|||
{
|
||||
fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w'));
|
||||
fFlushOnClose = fFlushOnCloseIn;
|
||||
env = database.env;
|
||||
env = database.env.get();
|
||||
if (database.IsDummy()) {
|
||||
return;
|
||||
}
|
||||
|
@ -463,7 +497,7 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo
|
|||
if (!env->Open(false /* retry */))
|
||||
throw std::runtime_error("BerkeleyBatch: Failed to open database environment.");
|
||||
|
||||
pdb = env->mapDb[strFilename];
|
||||
pdb = database.m_db.get();
|
||||
if (pdb == nullptr) {
|
||||
int ret;
|
||||
std::unique_ptr<Db> pdb_temp = MakeUnique<Db>(env->dbenv.get(), 0);
|
||||
|
@ -504,11 +538,11 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo
|
|||
// 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);
|
||||
CheckUniqueFileid(*env.second.lock().get(), strFilename, *pdb_temp, this->env->m_fileids[strFilename]);
|
||||
}
|
||||
|
||||
pdb = pdb_temp.release();
|
||||
env->mapDb[strFilename] = pdb;
|
||||
database.m_db.reset(pdb);
|
||||
|
||||
if (fCreate && !Exists(std::string("version"))) {
|
||||
bool fTmp = fReadOnly;
|
||||
|
@ -556,28 +590,56 @@ void BerkeleyBatch::Close()
|
|||
LOCK(cs_db);
|
||||
--env->mapFileUseCount[strFile];
|
||||
}
|
||||
env->m_db_in_use.notify_all();
|
||||
}
|
||||
|
||||
void BerkeleyEnvironment::CloseDb(const std::string& strFile)
|
||||
{
|
||||
{
|
||||
LOCK(cs_db);
|
||||
if (mapDb[strFile] != nullptr) {
|
||||
auto it = m_databases.find(strFile);
|
||||
assert(it != m_databases.end());
|
||||
BerkeleyDatabase& database = it->second.get();
|
||||
if (database.m_db) {
|
||||
// Close the database handle
|
||||
Db* pdb = mapDb[strFile];
|
||||
pdb->close(0);
|
||||
delete pdb;
|
||||
mapDb[strFile] = nullptr;
|
||||
database.m_db->close(0);
|
||||
database.m_db.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BerkeleyEnvironment::ReloadDbEnv()
|
||||
{
|
||||
// Make sure that no Db's are in use
|
||||
AssertLockNotHeld(cs_db);
|
||||
std::unique_lock<CCriticalSection> lock(cs_db);
|
||||
m_db_in_use.wait(lock, [this](){
|
||||
for (auto& count : mapFileUseCount) {
|
||||
if (count.second > 0) return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
std::vector<std::string> filenames;
|
||||
for (auto it : m_databases) {
|
||||
filenames.push_back(it.first);
|
||||
}
|
||||
// Close the individual Db's
|
||||
for (const std::string& filename : filenames) {
|
||||
CloseDb(filename);
|
||||
}
|
||||
// Reset the environment
|
||||
Flush(true); // This will flush and close the environment
|
||||
Reset();
|
||||
Open(true);
|
||||
}
|
||||
|
||||
bool BerkeleyBatch::Rewrite(BerkeleyDatabase& database, const char* pszSkip)
|
||||
{
|
||||
if (database.IsDummy()) {
|
||||
return true;
|
||||
}
|
||||
BerkeleyEnvironment *env = database.env;
|
||||
BerkeleyEnvironment *env = database.env.get();
|
||||
const std::string& strFile = database.strFile;
|
||||
while (true) {
|
||||
{
|
||||
|
@ -697,7 +759,6 @@ void BerkeleyEnvironment::Flush(bool fShutdown)
|
|||
if (!fMockDb) {
|
||||
fs::remove_all(fs::path(strPath) / "database");
|
||||
}
|
||||
g_dbenvs.erase(strPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -709,7 +770,7 @@ bool BerkeleyBatch::PeriodicFlush(BerkeleyDatabase& database)
|
|||
return true;
|
||||
}
|
||||
bool ret = false;
|
||||
BerkeleyEnvironment *env = database.env;
|
||||
BerkeleyEnvironment *env = database.env.get();
|
||||
const std::string& strFile = database.strFile;
|
||||
TRY_LOCK(cs_db, lockDb);
|
||||
if (lockDb)
|
||||
|
@ -796,6 +857,24 @@ void BerkeleyDatabase::Flush(bool shutdown)
|
|||
{
|
||||
if (!IsDummy()) {
|
||||
env->Flush(shutdown);
|
||||
if (shutdown) env = nullptr;
|
||||
if (shutdown) {
|
||||
LOCK(cs_db);
|
||||
g_dbenvs.erase(env->Directory().string());
|
||||
env = nullptr;
|
||||
} else {
|
||||
// TODO: To avoid g_dbenvs.erase erasing the environment prematurely after the
|
||||
// first database shutdown when multiple databases are open in the same
|
||||
// environment, should replace raw database `env` pointers with shared or weak
|
||||
// pointers, or else separate the database and environment shutdowns so
|
||||
// environments can be shut down after databases.
|
||||
env->m_fileids.erase(strFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BerkeleyDatabase::ReloadDbEnv()
|
||||
{
|
||||
if (!IsDummy()) {
|
||||
env->ReloadDbEnv();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <db_cxx.h>
|
||||
|
@ -25,6 +26,13 @@
|
|||
static const unsigned int DEFAULT_WALLET_DBLOGSIZE = 100;
|
||||
static const bool DEFAULT_WALLET_PRIVDB = true;
|
||||
|
||||
struct WalletDatabaseFileId {
|
||||
u_int8_t value[DB_FILE_ID_LEN];
|
||||
bool operator==(const WalletDatabaseFileId& rhs) const;
|
||||
};
|
||||
|
||||
class BerkeleyDatabase;
|
||||
|
||||
class BerkeleyEnvironment
|
||||
{
|
||||
private:
|
||||
|
@ -37,15 +45,18 @@ private:
|
|||
public:
|
||||
std::unique_ptr<DbEnv> dbenv;
|
||||
std::map<std::string, int> mapFileUseCount;
|
||||
std::map<std::string, Db*> mapDb;
|
||||
std::map<std::string, std::reference_wrapper<BerkeleyDatabase>> m_databases;
|
||||
std::unordered_map<std::string, WalletDatabaseFileId> m_fileids;
|
||||
std::condition_variable_any m_db_in_use;
|
||||
|
||||
BerkeleyEnvironment(const fs::path& env_directory);
|
||||
BerkeleyEnvironment();
|
||||
~BerkeleyEnvironment();
|
||||
void Reset();
|
||||
|
||||
void MakeMock();
|
||||
bool IsMock() const { return fMockDb; }
|
||||
bool IsInitialized() const { return fDbEnvInit; }
|
||||
bool IsDatabaseLoaded(const std::string& db_filename) const { return m_databases.find(db_filename) != m_databases.end(); }
|
||||
fs::path Directory() const { return strPath; }
|
||||
|
||||
/**
|
||||
|
@ -75,6 +86,7 @@ public:
|
|||
void CheckpointLSN(const std::string& strFile);
|
||||
|
||||
void CloseDb(const std::string& strFile);
|
||||
void ReloadDbEnv();
|
||||
|
||||
DbTxn* TxnBegin(int flags = DB_TXN_WRITE_NOSYNC)
|
||||
{
|
||||
|
@ -86,8 +98,11 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
/** Return whether a wallet database is currently loaded. */
|
||||
bool IsWalletLoaded(const fs::path& wallet_path);
|
||||
|
||||
/** Get BerkeleyEnvironment and database filename given a wallet path. */
|
||||
BerkeleyEnvironment* GetWalletEnv(const fs::path& wallet_path, std::string& database_filename);
|
||||
std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& wallet_path, std::string& database_filename);
|
||||
|
||||
/** An instance of this class represents one database.
|
||||
* For BerkeleyDB this is just a (env, strFile) tuple.
|
||||
|
@ -102,21 +117,25 @@ public:
|
|||
}
|
||||
|
||||
/** Create DB handle to real database */
|
||||
BerkeleyDatabase(const fs::path& wallet_path, bool mock = false) :
|
||||
nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0)
|
||||
BerkeleyDatabase(std::shared_ptr<BerkeleyEnvironment> env, std::string filename) :
|
||||
nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0), env(std::move(env)), strFile(std::move(filename))
|
||||
{
|
||||
env = GetWalletEnv(wallet_path, strFile);
|
||||
if (mock) {
|
||||
env->Close();
|
||||
env->Reset();
|
||||
env->MakeMock();
|
||||
auto inserted = this->env->m_databases.emplace(strFile, std::ref(*this));
|
||||
assert(inserted.second);
|
||||
}
|
||||
|
||||
~BerkeleyDatabase() {
|
||||
if (env) {
|
||||
size_t erased = env->m_databases.erase(strFile);
|
||||
assert(erased == 1);
|
||||
}
|
||||
}
|
||||
|
||||
/** Return object for accessing database at specified path. */
|
||||
static std::unique_ptr<BerkeleyDatabase> Create(const fs::path& path)
|
||||
{
|
||||
return MakeUnique<BerkeleyDatabase>(path);
|
||||
std::string filename;
|
||||
return MakeUnique<BerkeleyDatabase>(GetWalletEnv(path, filename), std::move(filename));
|
||||
}
|
||||
|
||||
/** Return object for accessing dummy database with no read/write capabilities. */
|
||||
|
@ -128,7 +147,7 @@ public:
|
|||
/** Return object for accessing temporary in-memory database. */
|
||||
static std::unique_ptr<BerkeleyDatabase> CreateMock()
|
||||
{
|
||||
return MakeUnique<BerkeleyDatabase>("", true /* mock */);
|
||||
return MakeUnique<BerkeleyDatabase>(std::make_shared<BerkeleyEnvironment>(), "");
|
||||
}
|
||||
|
||||
/** Rewrite the entire database on disk, with the exception of key pszSkip if non-zero
|
||||
|
@ -145,14 +164,28 @@ public:
|
|||
|
||||
void IncrementUpdateCounter();
|
||||
|
||||
void ReloadDbEnv();
|
||||
|
||||
std::atomic<unsigned int> nUpdateCounter;
|
||||
unsigned int nLastSeen;
|
||||
unsigned int nLastFlushed;
|
||||
int64_t nLastWalletUpdate;
|
||||
|
||||
/**
|
||||
* Pointer to shared database environment.
|
||||
*
|
||||
* Normally there is only one BerkeleyDatabase object per
|
||||
* BerkeleyEnvivonment, but in the special, backwards compatible case where
|
||||
* multiple wallet BDB data files are loaded from the same directory, this
|
||||
* will point to a shared instance that gets freed when the last data file
|
||||
* is closed.
|
||||
*/
|
||||
std::shared_ptr<BerkeleyEnvironment> env;
|
||||
|
||||
/** Database pointer. This is initialized lazily and reset during flushes, so it can be null. */
|
||||
std::unique_ptr<Db> m_db;
|
||||
|
||||
private:
|
||||
/** BerkeleyDB specific */
|
||||
BerkeleyEnvironment *env;
|
||||
std::string strFile;
|
||||
|
||||
/** Return whether this database handle is a dummy for testing.
|
||||
|
|
|
@ -203,15 +203,15 @@ bool WalletInit::Verify() const
|
|||
std::set<fs::path> wallet_paths;
|
||||
|
||||
for (const auto& wallet_file : wallet_files) {
|
||||
fs::path wallet_path = fs::absolute(wallet_file, GetWalletDir());
|
||||
WalletLocation location(wallet_file);
|
||||
|
||||
if (!wallet_paths.insert(wallet_path).second) {
|
||||
if (!wallet_paths.insert(location.GetPath()).second) {
|
||||
return InitError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), wallet_file));
|
||||
}
|
||||
|
||||
std::string error_string;
|
||||
std::string warning_string;
|
||||
bool verify_success = CWallet::Verify(wallet_file, salvage_wallet, error_string, warning_string);
|
||||
bool verify_success = CWallet::Verify(location, salvage_wallet, error_string, warning_string);
|
||||
if (!error_string.empty()) InitError(error_string);
|
||||
if (!warning_string.empty()) InitWarning(warning_string);
|
||||
if (!verify_success) return false;
|
||||
|
@ -228,7 +228,7 @@ bool WalletInit::Open() const
|
|||
}
|
||||
|
||||
for (const std::string& walletFile : gArgs.GetArgs("-wallet")) {
|
||||
std::shared_ptr<CWallet> pwallet = CWallet::CreateWalletFromFile(walletFile, fs::absolute(walletFile, GetWalletDir()));
|
||||
std::shared_ptr<CWallet> pwallet = CWallet::CreateWalletFromFile(WalletLocation(walletFile));
|
||||
if (!pwallet) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -2740,7 +2740,6 @@ static UniValue encryptwallet(const JSONRPCRequest& request)
|
|||
"will require the passphrase to be set prior the making these calls.\n"
|
||||
"Use the walletpassphrase call for this, and then walletlock call.\n"
|
||||
"If the wallet is already encrypted, use the walletpassphrasechange call.\n"
|
||||
"Note that this will shutdown the server.\n"
|
||||
"\nArguments:\n"
|
||||
"1. \"passphrase\" (string) The pass phrase to encrypt the wallet with. It must be at least 1 character, but should be long.\n"
|
||||
"\nExamples:\n"
|
||||
|
@ -2778,11 +2777,7 @@ static UniValue encryptwallet(const JSONRPCRequest& request)
|
|||
throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Failed to encrypt the wallet.");
|
||||
}
|
||||
|
||||
// BDB seems to have a bad habit of writing old data into
|
||||
// slack space in .dat files; that is bad if the old data is
|
||||
// unencrypted private keys. So:
|
||||
StartShutdown();
|
||||
return "wallet encrypted; Bitcoin server stopping, restart to run with encrypted wallet. The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.";
|
||||
return "wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.";
|
||||
}
|
||||
|
||||
static UniValue lockunspent(const JSONRPCRequest& request)
|
||||
|
@ -3117,26 +3112,26 @@ static UniValue loadwallet(const JSONRPCRequest& request)
|
|||
+ HelpExampleCli("loadwallet", "\"test.dat\"")
|
||||
+ HelpExampleRpc("loadwallet", "\"test.dat\"")
|
||||
);
|
||||
std::string wallet_file = request.params[0].get_str();
|
||||
|
||||
WalletLocation location(request.params[0].get_str());
|
||||
std::string error;
|
||||
|
||||
fs::path wallet_path = fs::absolute(wallet_file, GetWalletDir());
|
||||
if (fs::symlink_status(wallet_path).type() == fs::file_not_found) {
|
||||
throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Wallet " + wallet_file + " not found.");
|
||||
} else if (fs::is_directory(wallet_path)) {
|
||||
if (!location.Exists()) {
|
||||
throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Wallet " + location.GetName() + " not found.");
|
||||
} else if (fs::is_directory(location.GetPath())) {
|
||||
// The given filename is a directory. Check that there's a wallet.dat file.
|
||||
fs::path wallet_dat_file = wallet_path / "wallet.dat";
|
||||
fs::path wallet_dat_file = location.GetPath() / "wallet.dat";
|
||||
if (fs::symlink_status(wallet_dat_file).type() == fs::file_not_found) {
|
||||
throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Directory " + wallet_file + " does not contain a wallet.dat file.");
|
||||
throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Directory " + location.GetName() + " does not contain a wallet.dat file.");
|
||||
}
|
||||
}
|
||||
|
||||
std::string warning;
|
||||
if (!CWallet::Verify(wallet_file, false, error, warning)) {
|
||||
if (!CWallet::Verify(location, false, error, warning)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error);
|
||||
}
|
||||
|
||||
std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(wallet_file, fs::absolute(wallet_file, GetWalletDir()));
|
||||
std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(location);
|
||||
if (!wallet) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet loading failed.");
|
||||
}
|
||||
|
@ -3170,7 +3165,6 @@ static UniValue createwallet(const JSONRPCRequest& request)
|
|||
+ HelpExampleRpc("createwallet", "\"testwallet\"")
|
||||
);
|
||||
}
|
||||
std::string wallet_name = request.params[0].get_str();
|
||||
std::string error;
|
||||
std::string warning;
|
||||
|
||||
|
@ -3179,17 +3173,17 @@ static UniValue createwallet(const JSONRPCRequest& request)
|
|||
disable_privatekeys = request.params[1].get_bool();
|
||||
}
|
||||
|
||||
fs::path wallet_path = fs::absolute(wallet_name, GetWalletDir());
|
||||
if (fs::symlink_status(wallet_path).type() != fs::file_not_found) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet " + wallet_name + " already exists.");
|
||||
WalletLocation location(request.params[0].get_str());
|
||||
if (location.Exists()) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet " + location.GetName() + " already exists.");
|
||||
}
|
||||
|
||||
// Wallet::Verify will check if we're trying to create a wallet with a duplication name.
|
||||
if (!CWallet::Verify(wallet_name, false, error, warning)) {
|
||||
if (!CWallet::Verify(location, false, error, warning)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error);
|
||||
}
|
||||
|
||||
std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(wallet_name, fs::absolute(wallet_name, GetWalletDir()), (disable_privatekeys ? (uint64_t)WALLET_FLAG_DISABLE_PRIVATE_KEYS : 0));
|
||||
std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(location, (disable_privatekeys ? (uint64_t)WALLET_FLAG_DISABLE_PRIVATE_KEYS : 0));
|
||||
if (!wallet) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed.");
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ std::vector<std::unique_ptr<CWalletTx>> wtxn;
|
|||
typedef std::set<CInputCoin> CoinSet;
|
||||
|
||||
static std::vector<COutput> vCoins;
|
||||
static CWallet testWallet("dummy", WalletDatabase::CreateDummy());
|
||||
static CWallet testWallet(WalletLocation(), WalletDatabase::CreateDummy());
|
||||
static CAmount balance = 0;
|
||||
|
||||
CoinEligibilityFilter filter_standard(1, 6, 0);
|
||||
|
|
72
src/wallet/test/db_tests.cpp
Normal file
72
src/wallet/test/db_tests.cpp
Normal file
|
@ -0,0 +1,72 @@
|
|||
// Copyright (c) 2018 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <fs.h>
|
||||
#include <test/test_bitcoin.h>
|
||||
#include <wallet/db.h>
|
||||
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(db_tests, BasicTestingSetup)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(getwalletenv_file)
|
||||
{
|
||||
std::string test_name = "test_name.dat";
|
||||
fs::path datadir = SetDataDir("tempdir");
|
||||
fs::path file_path = datadir / test_name;
|
||||
std::ofstream f(file_path.BOOST_FILESYSTEM_C_STR);
|
||||
f.close();
|
||||
|
||||
std::string filename;
|
||||
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, filename);
|
||||
BOOST_CHECK(filename == test_name);
|
||||
BOOST_CHECK(env->Directory() == datadir);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(getwalletenv_directory)
|
||||
{
|
||||
std::string expected_name = "wallet.dat";
|
||||
fs::path datadir = SetDataDir("tempdir");
|
||||
|
||||
std::string filename;
|
||||
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(datadir, filename);
|
||||
BOOST_CHECK(filename == expected_name);
|
||||
BOOST_CHECK(env->Directory() == datadir);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(getwalletenv_g_dbenvs_multiple)
|
||||
{
|
||||
fs::path datadir = SetDataDir("tempdir");
|
||||
fs::path datadir_2 = SetDataDir("tempdir_2");
|
||||
std::string filename;
|
||||
|
||||
std::shared_ptr<BerkeleyEnvironment> env_1 = GetWalletEnv(datadir, filename);
|
||||
std::shared_ptr<BerkeleyEnvironment> env_2 = GetWalletEnv(datadir, filename);
|
||||
std::shared_ptr<BerkeleyEnvironment> env_3 = GetWalletEnv(datadir_2, filename);
|
||||
|
||||
BOOST_CHECK(env_1 == env_2);
|
||||
BOOST_CHECK(env_2 != env_3);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(getwalletenv_g_dbenvs_free_instance)
|
||||
{
|
||||
fs::path datadir = SetDataDir("tempdir");
|
||||
fs::path datadir_2 = SetDataDir("tempdir_2");
|
||||
std::string filename;
|
||||
|
||||
std::shared_ptr <BerkeleyEnvironment> env_1_a = GetWalletEnv(datadir, filename);
|
||||
std::shared_ptr <BerkeleyEnvironment> env_2_a = GetWalletEnv(datadir_2, filename);
|
||||
env_1_a.reset();
|
||||
|
||||
std::shared_ptr<BerkeleyEnvironment> env_1_b = GetWalletEnv(datadir, filename);
|
||||
std::shared_ptr<BerkeleyEnvironment> env_2_b = GetWalletEnv(datadir_2, filename);
|
||||
|
||||
BOOST_CHECK(env_1_a != env_1_b);
|
||||
BOOST_CHECK(env_2_a == env_2_b);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
|
@ -6,9 +6,10 @@
|
|||
|
||||
#include <rpc/server.h>
|
||||
#include <wallet/db.h>
|
||||
#include <wallet/rpcwallet.h>
|
||||
|
||||
WalletTestingSetup::WalletTestingSetup(const std::string& chainName):
|
||||
TestingSetup(chainName), m_wallet("mock", WalletDatabase::CreateMock())
|
||||
TestingSetup(chainName), m_wallet(WalletLocation(), WalletDatabase::CreateMock())
|
||||
{
|
||||
bool fFirstRun;
|
||||
m_wallet.LoadWallet(fFirstRun);
|
||||
|
|
|
@ -47,7 +47,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
|
|||
// Verify ScanForWalletTransactions picks up transactions in both the old
|
||||
// and new block files.
|
||||
{
|
||||
CWallet wallet("dummy", WalletDatabase::CreateDummy());
|
||||
CWallet wallet(WalletLocation(), WalletDatabase::CreateDummy());
|
||||
AddKey(wallet, coinbaseKey);
|
||||
WalletRescanReserver reserver(&wallet);
|
||||
reserver.reserve();
|
||||
|
@ -62,7 +62,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
|
|||
// Verify ScanForWalletTransactions only picks transactions in the new block
|
||||
// file.
|
||||
{
|
||||
CWallet wallet("dummy", WalletDatabase::CreateDummy());
|
||||
CWallet wallet(WalletLocation(), WalletDatabase::CreateDummy());
|
||||
AddKey(wallet, coinbaseKey);
|
||||
WalletRescanReserver reserver(&wallet);
|
||||
reserver.reserve();
|
||||
|
@ -74,7 +74,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
|
|||
// before the missing block, and success for a key whose creation time is
|
||||
// after.
|
||||
{
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("dummy", WalletDatabase::CreateDummy());
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(WalletLocation(), WalletDatabase::CreateDummy());
|
||||
AddWallet(wallet);
|
||||
UniValue keys;
|
||||
keys.setArray();
|
||||
|
@ -135,7 +135,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
|
|||
|
||||
// Import key into wallet and call dumpwallet to create backup file.
|
||||
{
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("dummy", WalletDatabase::CreateDummy());
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(WalletLocation(), WalletDatabase::CreateDummy());
|
||||
LOCK(wallet->cs_wallet);
|
||||
wallet->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME;
|
||||
wallet->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
|
||||
|
@ -151,7 +151,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
|
|||
// Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME
|
||||
// were scanned, and no prior blocks were scanned.
|
||||
{
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("dummy", WalletDatabase::CreateDummy());
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(WalletLocation(), WalletDatabase::CreateDummy());
|
||||
|
||||
JSONRPCRequest request;
|
||||
request.params.setArray();
|
||||
|
@ -181,7 +181,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
|
|||
// debit functions.
|
||||
BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup)
|
||||
{
|
||||
CWallet wallet("dummy", WalletDatabase::CreateDummy());
|
||||
CWallet wallet(WalletLocation(), WalletDatabase::CreateDummy());
|
||||
CWalletTx wtx(&wallet, m_coinbase_txns.back());
|
||||
LOCK2(cs_main, wallet.cs_wallet);
|
||||
wtx.hashBlock = chainActive.Tip()->GetBlockHash();
|
||||
|
@ -274,7 +274,7 @@ public:
|
|||
ListCoinsTestingSetup()
|
||||
{
|
||||
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
|
||||
wallet = MakeUnique<CWallet>("mock", WalletDatabase::CreateMock());
|
||||
wallet = MakeUnique<CWallet>(WalletLocation(), WalletDatabase::CreateMock());
|
||||
bool firstRun;
|
||||
wallet->LoadWallet(firstRun);
|
||||
AddKey(*wallet, coinbaseKey);
|
||||
|
@ -368,7 +368,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
|
|||
|
||||
BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
|
||||
{
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("dummy", WalletDatabase::CreateDummy());
|
||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(WalletLocation(), WalletDatabase::CreateDummy());
|
||||
wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
|
||||
BOOST_CHECK(!wallet->TopUpKeyPool(1000));
|
||||
CPubKey pubkey;
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
#include <txmempool.h>
|
||||
#include <utilmoneystr.h>
|
||||
#include <wallet/fees.h>
|
||||
#include <wallet/walletutil.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
|
@ -760,6 +759,11 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
|
|||
// bits of the unencrypted private key in slack space in the database file.
|
||||
database->Rewrite();
|
||||
|
||||
// BDB seems to have a bad habit of writing old data into
|
||||
// slack space in .dat files; that is bad if the old data is
|
||||
// unencrypted private keys. So:
|
||||
database->ReloadDbEnv();
|
||||
|
||||
}
|
||||
NotifyStatusChanged(this);
|
||||
|
||||
|
@ -4005,7 +4009,7 @@ void CWallet::MarkPreSplitKeys()
|
|||
}
|
||||
}
|
||||
|
||||
bool CWallet::Verify(std::string wallet_file, bool salvage_wallet, std::string& error_string, std::string& warning_string)
|
||||
bool CWallet::Verify(const WalletLocation& location, bool salvage_wallet, std::string& error_string, std::string& warning_string)
|
||||
{
|
||||
// Do some checking on wallet path. It should be either a:
|
||||
//
|
||||
|
@ -4014,39 +4018,40 @@ bool CWallet::Verify(std::string wallet_file, bool salvage_wallet, std::string&
|
|||
// 3. Path to a symlink to a directory.
|
||||
// 4. For backwards compatibility, the name of a data file in -walletdir.
|
||||
LOCK(cs_wallets);
|
||||
fs::path wallet_path = fs::absolute(wallet_file, GetWalletDir());
|
||||
const fs::path& wallet_path = location.GetPath();
|
||||
fs::file_type path_type = fs::symlink_status(wallet_path).type();
|
||||
if (!(path_type == fs::file_not_found || path_type == fs::directory_file ||
|
||||
(path_type == fs::symlink_file && fs::is_directory(wallet_path)) ||
|
||||
(path_type == fs::regular_file && fs::path(wallet_file).filename() == wallet_file))) {
|
||||
(path_type == fs::regular_file && fs::path(location.GetName()).filename() == location.GetName()))) {
|
||||
error_string = strprintf(
|
||||
"Invalid -wallet path '%s'. -wallet path should point to a directory where wallet.dat and "
|
||||
"database/log.?????????? files can be stored, a location where such a directory could be created, "
|
||||
"or (for backwards compatibility) the name of an existing data file in -walletdir (%s)",
|
||||
wallet_file, GetWalletDir());
|
||||
location.GetName(), GetWalletDir());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure that the wallet path doesn't clash with an existing wallet path
|
||||
for (auto wallet : GetWallets()) {
|
||||
if (fs::absolute(wallet->GetName(), GetWalletDir()) == wallet_path) {
|
||||
error_string = strprintf("Error loading wallet %s. Duplicate -wallet filename specified.", wallet_file);
|
||||
if (IsWalletLoaded(wallet_path)) {
|
||||
error_string = strprintf("Error loading wallet %s. Duplicate -wallet filename specified.", location.GetName());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Keep same database environment instance across Verify/Recover calls below.
|
||||
std::unique_ptr<WalletDatabase> database = WalletDatabase::Create(wallet_path);
|
||||
|
||||
try {
|
||||
if (!WalletBatch::VerifyEnvironment(wallet_path, error_string)) {
|
||||
return false;
|
||||
}
|
||||
} catch (const fs::filesystem_error& e) {
|
||||
error_string = strprintf("Error loading wallet %s. %s", wallet_file, e.what());
|
||||
error_string = strprintf("Error loading wallet %s. %s", location.GetName(), e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (salvage_wallet) {
|
||||
// Recover readable keypairs:
|
||||
CWallet dummyWallet("dummy", WalletDatabase::CreateDummy());
|
||||
CWallet dummyWallet(WalletLocation(), WalletDatabase::CreateDummy());
|
||||
std::string backup_filename;
|
||||
if (!WalletBatch::Recover(wallet_path, (void *)&dummyWallet, WalletBatch::RecoverKeysOnlyFilter, backup_filename)) {
|
||||
return false;
|
||||
|
@ -4056,9 +4061,9 @@ bool CWallet::Verify(std::string wallet_file, bool salvage_wallet, std::string&
|
|||
return WalletBatch::VerifyDatabaseFile(wallet_path, warning_string, error_string);
|
||||
}
|
||||
|
||||
std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, const fs::path& path, uint64_t wallet_creation_flags)
|
||||
std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const WalletLocation& location, uint64_t wallet_creation_flags)
|
||||
{
|
||||
const std::string& walletFile = name;
|
||||
const std::string& walletFile = location.GetName();
|
||||
|
||||
// needed to restore wallet transaction meta data after -zapwallettxes
|
||||
std::vector<CWalletTx> vWtx;
|
||||
|
@ -4066,7 +4071,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name,
|
|||
if (gArgs.GetBoolArg("-zapwallettxes", false)) {
|
||||
uiInterface.InitMessage(_("Zapping all transactions from wallet..."));
|
||||
|
||||
std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(name, WalletDatabase::Create(path));
|
||||
std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(location, WalletDatabase::Create(location.GetPath()));
|
||||
DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx);
|
||||
if (nZapWalletRet != DBErrors::LOAD_OK) {
|
||||
InitError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile));
|
||||
|
@ -4080,7 +4085,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name,
|
|||
bool fFirstRun = true;
|
||||
// TODO: Can't use std::make_shared because we need a custom deleter but
|
||||
// should be possible to use std::allocate_shared.
|
||||
std::shared_ptr<CWallet> walletInstance(new CWallet(name, WalletDatabase::Create(path)), ReleaseWallet);
|
||||
std::shared_ptr<CWallet> walletInstance(new CWallet(location, WalletDatabase::Create(location.GetPath())), ReleaseWallet);
|
||||
DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun);
|
||||
if (nLoadWalletRet != DBErrors::LOAD_OK)
|
||||
{
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
#include <wallet/crypter.h>
|
||||
#include <wallet/coinselection.h>
|
||||
#include <wallet/walletdb.h>
|
||||
#include <wallet/rpcwallet.h>
|
||||
#include <wallet/walletutil.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
|
@ -755,12 +755,8 @@ private:
|
|||
*/
|
||||
bool AddWatchOnly(const CScript& dest) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
||||
/**
|
||||
* Wallet filename from wallet=<path> command line or config option.
|
||||
* Used in debug logs and to send RPCs to the right wallet instance when
|
||||
* more than one wallet is loaded.
|
||||
*/
|
||||
std::string m_name;
|
||||
/** Wallet location which includes wallet name (see WalletLocation). */
|
||||
WalletLocation m_location;
|
||||
|
||||
/** Internal database handle. */
|
||||
std::unique_ptr<WalletDatabase> database;
|
||||
|
@ -800,9 +796,11 @@ public:
|
|||
bool SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet,
|
||||
const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params, bool& bnb_used) const;
|
||||
|
||||
const WalletLocation& GetLocation() const { return m_location; }
|
||||
|
||||
/** Get a name for this wallet for logging/debugging purposes.
|
||||
*/
|
||||
const std::string& GetName() const { return m_name; }
|
||||
const std::string& GetName() const { return m_location.GetName(); }
|
||||
|
||||
void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
void MarkPreSplitKeys();
|
||||
|
@ -818,7 +816,7 @@ public:
|
|||
unsigned int nMasterKeyMaxID = 0;
|
||||
|
||||
/** Construct wallet with specified name and database implementation. */
|
||||
CWallet(std::string name, std::unique_ptr<WalletDatabase> database) : m_name(std::move(name)), database(std::move(database))
|
||||
CWallet(const WalletLocation& location, std::unique_ptr<WalletDatabase> database) : m_location(location), database(std::move(database))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -1147,10 +1145,10 @@ public:
|
|||
bool MarkReplaced(const uint256& originalHash, const uint256& newHash);
|
||||
|
||||
//! Verify wallet naming and perform salvage on the wallet if required
|
||||
static bool Verify(std::string wallet_file, bool salvage_wallet, std::string& error_string, std::string& warning_string);
|
||||
static bool Verify(const WalletLocation& location, bool salvage_wallet, std::string& error_string, std::string& warning_string);
|
||||
|
||||
/* Initializes the wallet, returns a new CWallet instance or a null pointer in case of an error */
|
||||
static std::shared_ptr<CWallet> CreateWalletFromFile(const std::string& name, const fs::path& path, uint64_t wallet_creation_flags = 0);
|
||||
static std::shared_ptr<CWallet> CreateWalletFromFile(const WalletLocation& location, uint64_t wallet_creation_flags = 0);
|
||||
|
||||
/**
|
||||
* Wallet post-init setup
|
||||
|
|
|
@ -25,3 +25,14 @@ fs::path GetWalletDir()
|
|||
|
||||
return path;
|
||||
}
|
||||
|
||||
WalletLocation::WalletLocation(const std::string& name)
|
||||
: m_name(name)
|
||||
, m_path(fs::absolute(name, GetWalletDir()))
|
||||
{
|
||||
}
|
||||
|
||||
bool WalletLocation::Exists() const
|
||||
{
|
||||
return fs::symlink_status(m_path).type() != fs::file_not_found;
|
||||
}
|
||||
|
|
|
@ -11,4 +11,24 @@
|
|||
//! Get the path of the wallet directory.
|
||||
fs::path GetWalletDir();
|
||||
|
||||
//! The WalletLocation class provides wallet information.
|
||||
class WalletLocation final
|
||||
{
|
||||
std::string m_name;
|
||||
fs::path m_path;
|
||||
|
||||
public:
|
||||
explicit WalletLocation() {}
|
||||
explicit WalletLocation(const std::string& name);
|
||||
|
||||
//! Get wallet name.
|
||||
const std::string& GetName() const { return m_name; }
|
||||
|
||||
//! Get wallet absolute path.
|
||||
const fs::path& GetPath() const { return m_path; }
|
||||
|
||||
//! Return whether the wallet exists.
|
||||
bool Exists() const;
|
||||
};
|
||||
|
||||
#endif // BITCOIN_WALLET_WALLETUTIL_H
|
||||
|
|
|
@ -478,10 +478,8 @@ class RawTransactionsTest(BitcoinTestFramework):
|
|||
|
||||
############################################################
|
||||
# locked wallet test
|
||||
self.stop_node(0)
|
||||
self.nodes[1].node_encrypt_wallet("test")
|
||||
self.stop_node(2)
|
||||
self.stop_node(3)
|
||||
self.nodes[1].encryptwallet("test")
|
||||
self.stop_nodes()
|
||||
|
||||
self.start_nodes()
|
||||
# This test is not meant to test fee estimation and we'd like
|
||||
|
|
|
@ -305,14 +305,6 @@ class TestNode():
|
|||
assert_msg = "bitcoind should have exited with expected error " + expected_msg
|
||||
self._raise_assertion_error(assert_msg)
|
||||
|
||||
def node_encrypt_wallet(self, passphrase):
|
||||
""""Encrypts the wallet.
|
||||
|
||||
This causes bitcoind to shutdown, so this method takes
|
||||
care of cleaning up resources."""
|
||||
self.encryptwallet(passphrase)
|
||||
self.wait_until_stopped()
|
||||
|
||||
def add_p2p_connection(self, p2p_conn, *, wait_for_verack=True, **kwargs):
|
||||
"""Add a p2p connection to the node.
|
||||
|
||||
|
|
|
@ -41,8 +41,7 @@ class BumpFeeTest(BitcoinTestFramework):
|
|||
|
||||
def run_test(self):
|
||||
# Encrypt wallet for test_locked_wallet_fails test
|
||||
self.nodes[1].node_encrypt_wallet(WALLET_PASSPHRASE)
|
||||
self.start_node(1)
|
||||
self.nodes[1].encryptwallet(WALLET_PASSPHRASE)
|
||||
self.nodes[1].walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT)
|
||||
|
||||
connect_nodes_bi(self.nodes, 0, 1)
|
||||
|
|
|
@ -128,8 +128,7 @@ class WalletDumpTest(BitcoinTestFramework):
|
|||
assert_equal(witness_addr_ret, witness_addr) # p2sh-p2wsh address added to the first key
|
||||
|
||||
#encrypt wallet, restart, unlock and dump
|
||||
self.nodes[0].node_encrypt_wallet('test')
|
||||
self.start_node(0)
|
||||
self.nodes[0].encryptwallet('test')
|
||||
self.nodes[0].walletpassphrase('test', 10)
|
||||
# Should be a no-op:
|
||||
self.nodes[0].keypoolrefill()
|
||||
|
|
|
@ -33,8 +33,7 @@ class WalletEncryptionTest(BitcoinTestFramework):
|
|||
assert_equal(len(privkey), 52)
|
||||
|
||||
# Encrypt the wallet
|
||||
self.nodes[0].node_encrypt_wallet(passphrase)
|
||||
self.start_node(0)
|
||||
self.nodes[0].encryptwallet(passphrase)
|
||||
|
||||
# Test that the wallet is encrypted
|
||||
assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first", self.nodes[0].dumpprivkey, address)
|
||||
|
|
|
@ -25,9 +25,7 @@ class KeyPoolTest(BitcoinTestFramework):
|
|||
assert(addr_before_encrypting_data['hdseedid'] == wallet_info_old['hdseedid'])
|
||||
|
||||
# Encrypt wallet and wait to terminate
|
||||
nodes[0].node_encrypt_wallet('test')
|
||||
# Restart node 0
|
||||
self.start_node(0)
|
||||
nodes[0].encryptwallet('test')
|
||||
# Keep creating keys
|
||||
addr = nodes[0].getnewaddress()
|
||||
addr_data = nodes[0].getaddressinfo(addr)
|
||||
|
|
|
@ -217,9 +217,16 @@ class MultiWalletTest(BitcoinTestFramework):
|
|||
# Fail to load duplicate wallets
|
||||
assert_raises_rpc_error(-4, 'Wallet file verification failed: Error loading wallet w1. Duplicate -wallet filename specified.', self.nodes[0].loadwallet, wallet_names[0])
|
||||
|
||||
# Fail to load duplicate wallets by different ways (directory and filepath)
|
||||
assert_raises_rpc_error(-4, "Wallet file verification failed: Error loading wallet wallet.dat. Duplicate -wallet filename specified.", self.nodes[0].loadwallet, 'wallet.dat')
|
||||
|
||||
# Fail to load if one wallet is a copy of another
|
||||
assert_raises_rpc_error(-1, "BerkeleyBatch: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
|
||||
|
||||
# Fail to load if one wallet is a copy of another, test this twice to make sure that we don't re-introduce #14304
|
||||
assert_raises_rpc_error(-1, "BerkeleyBatch: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
|
||||
|
||||
|
||||
# Fail to load if wallet file is a symlink
|
||||
assert_raises_rpc_error(-4, "Wallet file verification failed: Invalid -wallet path 'w8_symlink'", self.nodes[0].loadwallet, 'w8_symlink')
|
||||
|
||||
|
@ -304,6 +311,14 @@ class MultiWalletTest(BitcoinTestFramework):
|
|||
self.nodes[0].loadwallet(wallet_name)
|
||||
assert_equal(rpc.getaddressinfo(addr)['ismine'], True)
|
||||
|
||||
# Test .walletlock file is closed
|
||||
self.start_node(1)
|
||||
wallet = os.path.join(self.options.tmpdir, 'my_wallet')
|
||||
self.nodes[0].createwallet(wallet)
|
||||
assert_raises_rpc_error(-4, "Error initializing wallet database environment", self.nodes[1].loadwallet, wallet)
|
||||
self.nodes[0].unloadwallet(wallet)
|
||||
self.nodes[1].loadwallet(wallet)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
MultiWalletTest().main()
|
||||
|
|
|
@ -30,7 +30,6 @@ EXPECTED_CIRCULAR_DEPENDENCIES=(
|
|||
"validation -> validationinterface -> validation"
|
||||
"wallet/coincontrol -> wallet/wallet -> wallet/coincontrol"
|
||||
"wallet/fees -> wallet/wallet -> wallet/fees"
|
||||
"wallet/rpcwallet -> wallet/wallet -> wallet/rpcwallet"
|
||||
"wallet/wallet -> wallet/walletdb -> wallet/wallet"
|
||||
"policy/fees -> policy/policy -> validation -> policy/fees"
|
||||
"policy/rbf -> txmempool -> validation -> policy/rbf"
|
||||
|
|
Loading…
Reference in a new issue