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
|
if ENABLE_WALLET
|
||||||
BITCOIN_TESTS += \
|
BITCOIN_TESTS += \
|
||||||
wallet/test/accounting_tests.cpp \
|
wallet/test/accounting_tests.cpp \
|
||||||
|
wallet/test/db_tests.cpp \
|
||||||
wallet/test/psbt_wallet_tests.cpp \
|
wallet/test/psbt_wallet_tests.cpp \
|
||||||
wallet/test/wallet_tests.cpp \
|
wallet/test/wallet_tests.cpp \
|
||||||
wallet/test/wallet_crypto_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)
|
// (https://github.com/bitcoin/bitcoin/issues/7883#issuecomment-224807484)
|
||||||
static void CoinSelection(benchmark::State& state)
|
static void CoinSelection(benchmark::State& state)
|
||||||
{
|
{
|
||||||
const CWallet wallet("dummy", WalletDatabase::CreateDummy());
|
const CWallet wallet(WalletLocation(), WalletDatabase::CreateDummy());
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
|
|
||||||
// Add coins.
|
// Add coins.
|
||||||
|
@ -57,7 +57,7 @@ static void CoinSelection(benchmark::State& state)
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef std::set<CInputCoin> CoinSet;
|
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;
|
std::vector<std::unique_ptr<CWalletTx>> wtxn;
|
||||||
|
|
||||||
// Copied from src/wallet/test/coinselector_tests.cpp
|
// Copied from src/wallet/test/coinselector_tests.cpp
|
||||||
|
|
|
@ -123,16 +123,15 @@ void AskPassphraseDialog::accept()
|
||||||
{
|
{
|
||||||
QMessageBox::warning(this, tr("Wallet encrypted"),
|
QMessageBox::warning(this, tr("Wallet encrypted"),
|
||||||
"<qt>" +
|
"<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 "
|
"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>" +
|
"<br><br><b>" +
|
||||||
tr("IMPORTANT: Any previous backups you have made of your wallet file "
|
tr("IMPORTANT: Any previous backups you have made of your wallet file "
|
||||||
"should be replaced with the newly generated, encrypted wallet file. "
|
"should be replaced with the newly generated, encrypted wallet file. "
|
||||||
"For security reasons, previous backups of the unencrypted 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.") +
|
"will become useless as soon as you start using the new, encrypted wallet.") +
|
||||||
"</b></qt>");
|
"</b></qt>");
|
||||||
QApplication::quit();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -57,7 +57,7 @@ void EditAddressAndSubmit(
|
||||||
void TestAddAddressesToSendBook()
|
void TestAddAddressesToSendBook()
|
||||||
{
|
{
|
||||||
TestChain100Setup test;
|
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;
|
bool firstRun;
|
||||||
wallet->LoadWallet(firstRun);
|
wallet->LoadWallet(firstRun);
|
||||||
|
|
||||||
|
|
|
@ -133,7 +133,7 @@ void TestGUI()
|
||||||
for (int i = 0; i < 5; ++i) {
|
for (int i = 0; i < 5; ++i) {
|
||||||
test.CreateAndProcessBlock({}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey()));
|
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;
|
bool firstRun;
|
||||||
wallet->LoadWallet(firstRun);
|
wallet->LoadWallet(firstRun);
|
||||||
{
|
{
|
||||||
|
|
|
@ -174,6 +174,12 @@ bool LockDirectory(const fs::path& directory, const std::string lockfile_name, b
|
||||||
return true;
|
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()
|
void ReleaseDirectoryLocks()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> ulock(cs_dir_locks);
|
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);
|
void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length);
|
||||||
bool RenameOver(fs::path src, fs::path dest);
|
bool RenameOver(fs::path src, fs::path dest);
|
||||||
bool LockDirectory(const fs::path& directory, const std::string lockfile_name, bool probe_only=false);
|
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);
|
bool DirIsWritable(const fs::path& directory);
|
||||||
|
|
||||||
/** Release all directory locks. This is used for unit testing only, at runtime
|
/** Release all directory locks. This is used for unit testing only, at runtime
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include <boost/thread.hpp>
|
#include <boost/thread.hpp>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
//! Make sure database has a unique fileid within the environment. If it
|
//! 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
|
//! 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
|
//! 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),
|
//! (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
|
//! so bitcoin should never create different databases with the same fileid, but
|
||||||
//! this error can be triggered if users manually copy database files.
|
//! 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;
|
if (env.IsMock()) return;
|
||||||
|
|
||||||
u_int8_t fileid[DB_FILE_ID_LEN];
|
int ret = db.get_mpf()->get_fileid(fileid.value);
|
||||||
int ret = db.get_mpf()->get_fileid(fileid);
|
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
throw std::runtime_error(strprintf("BerkeleyBatch: Can't open database %s (get_fileid failed with %d)", filename, ret));
|
throw std::runtime_error(strprintf("BerkeleyBatch: Can't open database %s (get_fileid failed with %d)", filename, ret));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& item : env.mapDb) {
|
for (const auto& item : env.m_fileids) {
|
||||||
u_int8_t item_fileid[DB_FILE_ID_LEN];
|
if (fileid == item.second && &fileid != &item.second) {
|
||||||
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);
|
|
||||||
throw std::runtime_error(strprintf("BerkeleyBatch: Can't open database %s (duplicates fileid %s from %s)", filename,
|
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)),
|
HexStr(std::begin(item.second.value), std::end(item.second.value)), item.first));
|
||||||
item_filename ? item_filename : "(unknown database)"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CCriticalSection cs_db;
|
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
|
} // 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)) {
|
if (fs::is_regular_file(wallet_path)) {
|
||||||
// Special case for backwards compatibility: if wallet path points to an
|
// 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
|
// 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;
|
env_directory = wallet_path;
|
||||||
database_filename = "wallet.dat";
|
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);
|
LOCK(cs_db);
|
||||||
// Note: An ununsed temporary BerkeleyEnvironment object may be created inside the
|
auto env = g_dbenvs.find(env_directory.string());
|
||||||
// emplace function if the key already exists. This is a little inefficient,
|
if (env == g_dbenvs.end()) return false;
|
||||||
// but not a big concern since the map will be changed in the future to hold
|
auto database = env->second.lock();
|
||||||
// pointers instead of objects, anyway.
|
return database && database->IsDatabaseLoaded(database_filename);
|
||||||
return &g_dbenvs.emplace(std::piecewise_construct, std::forward_as_tuple(env_directory.string()), std::forward_as_tuple(env_directory)).first->second;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
fDbEnvInit = false;
|
||||||
|
|
||||||
for (auto& db : mapDb) {
|
for (auto& db : m_databases) {
|
||||||
auto count = mapFileUseCount.find(db.first);
|
auto count = mapFileUseCount.find(db.first);
|
||||||
assert(count == mapFileUseCount.end() || count->second == 0);
|
assert(count == mapFileUseCount.end() || count->second == 0);
|
||||||
if (db.second) {
|
BerkeleyDatabase& database = db.second.get();
|
||||||
db.second->close(0);
|
if (database.m_db) {
|
||||||
delete db.second;
|
database.m_db->close(0);
|
||||||
db.second = nullptr;
|
database.m_db.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FILE* error_file = nullptr;
|
||||||
|
dbenv->get_errfile(&error_file);
|
||||||
|
|
||||||
int ret = dbenv->close(0);
|
int ret = dbenv->close(0);
|
||||||
if (ret != 0)
|
if (ret != 0)
|
||||||
LogPrintf("BerkeleyEnvironment::Close: Error %d closing database environment: %s\n", ret, DbEnv::strerror(ret));
|
LogPrintf("BerkeleyEnvironment::Close: Error %d closing database environment: %s\n", ret, DbEnv::strerror(ret));
|
||||||
if (!fMockDb)
|
if (!fMockDb)
|
||||||
DbEnv((u_int32_t)0).remove(strPath.c_str(), 0);
|
DbEnv((u_int32_t)0).remove(strPath.c_str(), 0);
|
||||||
|
|
||||||
|
if (error_file) fclose(error_file);
|
||||||
|
|
||||||
|
UnlockDirectory(strPath, ".walletlock");
|
||||||
}
|
}
|
||||||
|
|
||||||
void BerkeleyEnvironment::Reset()
|
void BerkeleyEnvironment::Reset()
|
||||||
|
@ -121,6 +154,7 @@ BerkeleyEnvironment::BerkeleyEnvironment(const fs::path& dir_path) : strPath(dir
|
||||||
|
|
||||||
BerkeleyEnvironment::~BerkeleyEnvironment()
|
BerkeleyEnvironment::~BerkeleyEnvironment()
|
||||||
{
|
{
|
||||||
|
g_dbenvs.erase(strPath);
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,10 +232,10 @@ bool BerkeleyEnvironment::Open(bool retry)
|
||||||
return true;
|
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)
|
Reset();
|
||||||
throw std::runtime_error("BerkeleyEnvironment::MakeMock: Already initialized");
|
|
||||||
|
|
||||||
boost::this_thread::interruption_point();
|
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)
|
bool BerkeleyBatch::Recover(const fs::path& file_path, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& newFilename)
|
||||||
{
|
{
|
||||||
std::string filename;
|
std::string filename;
|
||||||
BerkeleyEnvironment* env = GetWalletEnv(file_path, filename);
|
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, filename);
|
||||||
|
|
||||||
// Recovery procedure:
|
// Recovery procedure:
|
||||||
// move wallet file to walletfilename.timestamp.bak
|
// 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)
|
bool BerkeleyBatch::VerifyEnvironment(const fs::path& file_path, std::string& errorStr)
|
||||||
{
|
{
|
||||||
std::string walletFile;
|
std::string walletFile;
|
||||||
BerkeleyEnvironment* env = GetWalletEnv(file_path, walletFile);
|
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, walletFile);
|
||||||
fs::path walletDir = env->Directory();
|
fs::path walletDir = env->Directory();
|
||||||
|
|
||||||
LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0));
|
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)
|
bool BerkeleyBatch::VerifyDatabaseFile(const fs::path& file_path, std::string& warningStr, std::string& errorStr, BerkeleyEnvironment::recoverFunc_type recoverFunc)
|
||||||
{
|
{
|
||||||
std::string walletFile;
|
std::string walletFile;
|
||||||
BerkeleyEnvironment* env = GetWalletEnv(file_path, walletFile);
|
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, walletFile);
|
||||||
fs::path walletDir = env->Directory();
|
fs::path walletDir = env->Directory();
|
||||||
|
|
||||||
if (fs::exists(walletDir / walletFile))
|
if (fs::exists(walletDir / walletFile))
|
||||||
|
@ -447,7 +481,7 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo
|
||||||
{
|
{
|
||||||
fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w'));
|
fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w'));
|
||||||
fFlushOnClose = fFlushOnCloseIn;
|
fFlushOnClose = fFlushOnCloseIn;
|
||||||
env = database.env;
|
env = database.env.get();
|
||||||
if (database.IsDummy()) {
|
if (database.IsDummy()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -463,7 +497,7 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo
|
||||||
if (!env->Open(false /* retry */))
|
if (!env->Open(false /* retry */))
|
||||||
throw std::runtime_error("BerkeleyBatch: Failed to open database environment.");
|
throw std::runtime_error("BerkeleyBatch: Failed to open database environment.");
|
||||||
|
|
||||||
pdb = env->mapDb[strFilename];
|
pdb = database.m_db.get();
|
||||||
if (pdb == nullptr) {
|
if (pdb == nullptr) {
|
||||||
int ret;
|
int ret;
|
||||||
std::unique_ptr<Db> pdb_temp = MakeUnique<Db>(env->dbenv.get(), 0);
|
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
|
// versions of BDB have an set_lk_exclusive method for this
|
||||||
// purpose, but the older version we use does not.)
|
// purpose, but the older version we use does not.)
|
||||||
for (auto& env : g_dbenvs) {
|
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();
|
pdb = pdb_temp.release();
|
||||||
env->mapDb[strFilename] = pdb;
|
database.m_db.reset(pdb);
|
||||||
|
|
||||||
if (fCreate && !Exists(std::string("version"))) {
|
if (fCreate && !Exists(std::string("version"))) {
|
||||||
bool fTmp = fReadOnly;
|
bool fTmp = fReadOnly;
|
||||||
|
@ -556,28 +590,56 @@ void BerkeleyBatch::Close()
|
||||||
LOCK(cs_db);
|
LOCK(cs_db);
|
||||||
--env->mapFileUseCount[strFile];
|
--env->mapFileUseCount[strFile];
|
||||||
}
|
}
|
||||||
|
env->m_db_in_use.notify_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BerkeleyEnvironment::CloseDb(const std::string& strFile)
|
void BerkeleyEnvironment::CloseDb(const std::string& strFile)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
LOCK(cs_db);
|
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
|
// Close the database handle
|
||||||
Db* pdb = mapDb[strFile];
|
database.m_db->close(0);
|
||||||
pdb->close(0);
|
database.m_db.reset();
|
||||||
delete pdb;
|
|
||||||
mapDb[strFile] = nullptr;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
bool BerkeleyBatch::Rewrite(BerkeleyDatabase& database, const char* pszSkip)
|
||||||
{
|
{
|
||||||
if (database.IsDummy()) {
|
if (database.IsDummy()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
BerkeleyEnvironment *env = database.env;
|
BerkeleyEnvironment *env = database.env.get();
|
||||||
const std::string& strFile = database.strFile;
|
const std::string& strFile = database.strFile;
|
||||||
while (true) {
|
while (true) {
|
||||||
{
|
{
|
||||||
|
@ -697,7 +759,6 @@ void BerkeleyEnvironment::Flush(bool fShutdown)
|
||||||
if (!fMockDb) {
|
if (!fMockDb) {
|
||||||
fs::remove_all(fs::path(strPath) / "database");
|
fs::remove_all(fs::path(strPath) / "database");
|
||||||
}
|
}
|
||||||
g_dbenvs.erase(strPath);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -709,7 +770,7 @@ bool BerkeleyBatch::PeriodicFlush(BerkeleyDatabase& database)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
BerkeleyEnvironment *env = database.env;
|
BerkeleyEnvironment *env = database.env.get();
|
||||||
const std::string& strFile = database.strFile;
|
const std::string& strFile = database.strFile;
|
||||||
TRY_LOCK(cs_db, lockDb);
|
TRY_LOCK(cs_db, lockDb);
|
||||||
if (lockDb)
|
if (lockDb)
|
||||||
|
@ -796,6 +857,24 @@ void BerkeleyDatabase::Flush(bool shutdown)
|
||||||
{
|
{
|
||||||
if (!IsDummy()) {
|
if (!IsDummy()) {
|
||||||
env->Flush(shutdown);
|
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 <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <db_cxx.h>
|
#include <db_cxx.h>
|
||||||
|
@ -25,6 +26,13 @@
|
||||||
static const unsigned int DEFAULT_WALLET_DBLOGSIZE = 100;
|
static const unsigned int DEFAULT_WALLET_DBLOGSIZE = 100;
|
||||||
static const bool DEFAULT_WALLET_PRIVDB = true;
|
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
|
class BerkeleyEnvironment
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
@ -37,15 +45,18 @@ private:
|
||||||
public:
|
public:
|
||||||
std::unique_ptr<DbEnv> dbenv;
|
std::unique_ptr<DbEnv> dbenv;
|
||||||
std::map<std::string, int> mapFileUseCount;
|
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(const fs::path& env_directory);
|
||||||
|
BerkeleyEnvironment();
|
||||||
~BerkeleyEnvironment();
|
~BerkeleyEnvironment();
|
||||||
void Reset();
|
void Reset();
|
||||||
|
|
||||||
void MakeMock();
|
|
||||||
bool IsMock() const { return fMockDb; }
|
bool IsMock() const { return fMockDb; }
|
||||||
bool IsInitialized() const { return fDbEnvInit; }
|
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; }
|
fs::path Directory() const { return strPath; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -75,6 +86,7 @@ public:
|
||||||
void CheckpointLSN(const std::string& strFile);
|
void CheckpointLSN(const std::string& strFile);
|
||||||
|
|
||||||
void CloseDb(const std::string& strFile);
|
void CloseDb(const std::string& strFile);
|
||||||
|
void ReloadDbEnv();
|
||||||
|
|
||||||
DbTxn* TxnBegin(int flags = DB_TXN_WRITE_NOSYNC)
|
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. */
|
/** 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.
|
/** An instance of this class represents one database.
|
||||||
* For BerkeleyDB this is just a (env, strFile) tuple.
|
* For BerkeleyDB this is just a (env, strFile) tuple.
|
||||||
|
@ -102,21 +117,25 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create DB handle to real database */
|
/** Create DB handle to real database */
|
||||||
BerkeleyDatabase(const fs::path& wallet_path, bool mock = false) :
|
BerkeleyDatabase(std::shared_ptr<BerkeleyEnvironment> env, std::string filename) :
|
||||||
nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0)
|
nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0), env(std::move(env)), strFile(std::move(filename))
|
||||||
{
|
{
|
||||||
env = GetWalletEnv(wallet_path, strFile);
|
auto inserted = this->env->m_databases.emplace(strFile, std::ref(*this));
|
||||||
if (mock) {
|
assert(inserted.second);
|
||||||
env->Close();
|
}
|
||||||
env->Reset();
|
|
||||||
env->MakeMock();
|
~BerkeleyDatabase() {
|
||||||
|
if (env) {
|
||||||
|
size_t erased = env->m_databases.erase(strFile);
|
||||||
|
assert(erased == 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return object for accessing database at specified path. */
|
/** Return object for accessing database at specified path. */
|
||||||
static std::unique_ptr<BerkeleyDatabase> Create(const fs::path& 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. */
|
/** Return object for accessing dummy database with no read/write capabilities. */
|
||||||
|
@ -128,7 +147,7 @@ public:
|
||||||
/** Return object for accessing temporary in-memory database. */
|
/** Return object for accessing temporary in-memory database. */
|
||||||
static std::unique_ptr<BerkeleyDatabase> CreateMock()
|
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
|
/** Rewrite the entire database on disk, with the exception of key pszSkip if non-zero
|
||||||
|
@ -145,14 +164,28 @@ public:
|
||||||
|
|
||||||
void IncrementUpdateCounter();
|
void IncrementUpdateCounter();
|
||||||
|
|
||||||
|
void ReloadDbEnv();
|
||||||
|
|
||||||
std::atomic<unsigned int> nUpdateCounter;
|
std::atomic<unsigned int> nUpdateCounter;
|
||||||
unsigned int nLastSeen;
|
unsigned int nLastSeen;
|
||||||
unsigned int nLastFlushed;
|
unsigned int nLastFlushed;
|
||||||
int64_t nLastWalletUpdate;
|
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:
|
private:
|
||||||
/** BerkeleyDB specific */
|
|
||||||
BerkeleyEnvironment *env;
|
|
||||||
std::string strFile;
|
std::string strFile;
|
||||||
|
|
||||||
/** Return whether this database handle is a dummy for testing.
|
/** Return whether this database handle is a dummy for testing.
|
||||||
|
|
|
@ -203,15 +203,15 @@ bool WalletInit::Verify() const
|
||||||
std::set<fs::path> wallet_paths;
|
std::set<fs::path> wallet_paths;
|
||||||
|
|
||||||
for (const auto& wallet_file : wallet_files) {
|
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));
|
return InitError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), wallet_file));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string error_string;
|
std::string error_string;
|
||||||
std::string warning_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 (!error_string.empty()) InitError(error_string);
|
||||||
if (!warning_string.empty()) InitWarning(warning_string);
|
if (!warning_string.empty()) InitWarning(warning_string);
|
||||||
if (!verify_success) return false;
|
if (!verify_success) return false;
|
||||||
|
@ -228,7 +228,7 @@ bool WalletInit::Open() const
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const std::string& walletFile : gArgs.GetArgs("-wallet")) {
|
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) {
|
if (!pwallet) {
|
||||||
return false;
|
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"
|
"will require the passphrase to be set prior the making these calls.\n"
|
||||||
"Use the walletpassphrase call for this, and then walletlock call.\n"
|
"Use the walletpassphrase call for this, and then walletlock call.\n"
|
||||||
"If the wallet is already encrypted, use the walletpassphrasechange call.\n"
|
"If the wallet is already encrypted, use the walletpassphrasechange call.\n"
|
||||||
"Note that this will shutdown the server.\n"
|
|
||||||
"\nArguments:\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"
|
"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"
|
"\nExamples:\n"
|
||||||
|
@ -2778,11 +2777,7 @@ static UniValue encryptwallet(const JSONRPCRequest& request)
|
||||||
throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Failed to encrypt the wallet.");
|
throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Failed to encrypt the wallet.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// BDB seems to have a bad habit of writing old data into
|
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.";
|
||||||
// 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.";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static UniValue lockunspent(const JSONRPCRequest& request)
|
static UniValue lockunspent(const JSONRPCRequest& request)
|
||||||
|
@ -3117,26 +3112,26 @@ static UniValue loadwallet(const JSONRPCRequest& request)
|
||||||
+ HelpExampleCli("loadwallet", "\"test.dat\"")
|
+ HelpExampleCli("loadwallet", "\"test.dat\"")
|
||||||
+ HelpExampleRpc("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;
|
std::string error;
|
||||||
|
|
||||||
fs::path wallet_path = fs::absolute(wallet_file, GetWalletDir());
|
if (!location.Exists()) {
|
||||||
if (fs::symlink_status(wallet_path).type() == fs::file_not_found) {
|
throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Wallet " + location.GetName() + " not found.");
|
||||||
throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Wallet " + wallet_file + " not found.");
|
} else if (fs::is_directory(location.GetPath())) {
|
||||||
} else if (fs::is_directory(wallet_path)) {
|
|
||||||
// The given filename is a directory. Check that there's a wallet.dat file.
|
// 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) {
|
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;
|
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);
|
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) {
|
if (!wallet) {
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet loading failed.");
|
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet loading failed.");
|
||||||
}
|
}
|
||||||
|
@ -3170,7 +3165,6 @@ static UniValue createwallet(const JSONRPCRequest& request)
|
||||||
+ HelpExampleRpc("createwallet", "\"testwallet\"")
|
+ HelpExampleRpc("createwallet", "\"testwallet\"")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
std::string wallet_name = request.params[0].get_str();
|
|
||||||
std::string error;
|
std::string error;
|
||||||
std::string warning;
|
std::string warning;
|
||||||
|
|
||||||
|
@ -3179,17 +3173,17 @@ static UniValue createwallet(const JSONRPCRequest& request)
|
||||||
disable_privatekeys = request.params[1].get_bool();
|
disable_privatekeys = request.params[1].get_bool();
|
||||||
}
|
}
|
||||||
|
|
||||||
fs::path wallet_path = fs::absolute(wallet_name, GetWalletDir());
|
WalletLocation location(request.params[0].get_str());
|
||||||
if (fs::symlink_status(wallet_path).type() != fs::file_not_found) {
|
if (location.Exists()) {
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet " + wallet_name + " already 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.
|
// 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);
|
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) {
|
if (!wallet) {
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed.");
|
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ std::vector<std::unique_ptr<CWalletTx>> wtxn;
|
||||||
typedef std::set<CInputCoin> CoinSet;
|
typedef std::set<CInputCoin> CoinSet;
|
||||||
|
|
||||||
static std::vector<COutput> vCoins;
|
static std::vector<COutput> vCoins;
|
||||||
static CWallet testWallet("dummy", WalletDatabase::CreateDummy());
|
static CWallet testWallet(WalletLocation(), WalletDatabase::CreateDummy());
|
||||||
static CAmount balance = 0;
|
static CAmount balance = 0;
|
||||||
|
|
||||||
CoinEligibilityFilter filter_standard(1, 6, 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 <rpc/server.h>
|
||||||
#include <wallet/db.h>
|
#include <wallet/db.h>
|
||||||
|
#include <wallet/rpcwallet.h>
|
||||||
|
|
||||||
WalletTestingSetup::WalletTestingSetup(const std::string& chainName):
|
WalletTestingSetup::WalletTestingSetup(const std::string& chainName):
|
||||||
TestingSetup(chainName), m_wallet("mock", WalletDatabase::CreateMock())
|
TestingSetup(chainName), m_wallet(WalletLocation(), WalletDatabase::CreateMock())
|
||||||
{
|
{
|
||||||
bool fFirstRun;
|
bool fFirstRun;
|
||||||
m_wallet.LoadWallet(fFirstRun);
|
m_wallet.LoadWallet(fFirstRun);
|
||||||
|
|
|
@ -47,7 +47,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
|
||||||
// Verify ScanForWalletTransactions picks up transactions in both the old
|
// Verify ScanForWalletTransactions picks up transactions in both the old
|
||||||
// and new block files.
|
// and new block files.
|
||||||
{
|
{
|
||||||
CWallet wallet("dummy", WalletDatabase::CreateDummy());
|
CWallet wallet(WalletLocation(), WalletDatabase::CreateDummy());
|
||||||
AddKey(wallet, coinbaseKey);
|
AddKey(wallet, coinbaseKey);
|
||||||
WalletRescanReserver reserver(&wallet);
|
WalletRescanReserver reserver(&wallet);
|
||||||
reserver.reserve();
|
reserver.reserve();
|
||||||
|
@ -62,7 +62,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
|
||||||
// Verify ScanForWalletTransactions only picks transactions in the new block
|
// Verify ScanForWalletTransactions only picks transactions in the new block
|
||||||
// file.
|
// file.
|
||||||
{
|
{
|
||||||
CWallet wallet("dummy", WalletDatabase::CreateDummy());
|
CWallet wallet(WalletLocation(), WalletDatabase::CreateDummy());
|
||||||
AddKey(wallet, coinbaseKey);
|
AddKey(wallet, coinbaseKey);
|
||||||
WalletRescanReserver reserver(&wallet);
|
WalletRescanReserver reserver(&wallet);
|
||||||
reserver.reserve();
|
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
|
// before the missing block, and success for a key whose creation time is
|
||||||
// after.
|
// 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);
|
AddWallet(wallet);
|
||||||
UniValue keys;
|
UniValue keys;
|
||||||
keys.setArray();
|
keys.setArray();
|
||||||
|
@ -135,7 +135,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
|
||||||
|
|
||||||
// Import key into wallet and call dumpwallet to create backup file.
|
// 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);
|
LOCK(wallet->cs_wallet);
|
||||||
wallet->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME;
|
wallet->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME;
|
||||||
wallet->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
|
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
|
// Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME
|
||||||
// were scanned, and no prior blocks were scanned.
|
// 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;
|
JSONRPCRequest request;
|
||||||
request.params.setArray();
|
request.params.setArray();
|
||||||
|
@ -181,7 +181,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
|
||||||
// debit functions.
|
// debit functions.
|
||||||
BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup)
|
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());
|
CWalletTx wtx(&wallet, m_coinbase_txns.back());
|
||||||
LOCK2(cs_main, wallet.cs_wallet);
|
LOCK2(cs_main, wallet.cs_wallet);
|
||||||
wtx.hashBlock = chainActive.Tip()->GetBlockHash();
|
wtx.hashBlock = chainActive.Tip()->GetBlockHash();
|
||||||
|
@ -274,7 +274,7 @@ public:
|
||||||
ListCoinsTestingSetup()
|
ListCoinsTestingSetup()
|
||||||
{
|
{
|
||||||
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
|
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
|
||||||
wallet = MakeUnique<CWallet>("mock", WalletDatabase::CreateMock());
|
wallet = MakeUnique<CWallet>(WalletLocation(), WalletDatabase::CreateMock());
|
||||||
bool firstRun;
|
bool firstRun;
|
||||||
wallet->LoadWallet(firstRun);
|
wallet->LoadWallet(firstRun);
|
||||||
AddKey(*wallet, coinbaseKey);
|
AddKey(*wallet, coinbaseKey);
|
||||||
|
@ -368,7 +368,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
|
||||||
|
|
||||||
BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
|
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);
|
wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
|
||||||
BOOST_CHECK(!wallet->TopUpKeyPool(1000));
|
BOOST_CHECK(!wallet->TopUpKeyPool(1000));
|
||||||
CPubKey pubkey;
|
CPubKey pubkey;
|
||||||
|
|
|
@ -27,7 +27,6 @@
|
||||||
#include <txmempool.h>
|
#include <txmempool.h>
|
||||||
#include <utilmoneystr.h>
|
#include <utilmoneystr.h>
|
||||||
#include <wallet/fees.h>
|
#include <wallet/fees.h>
|
||||||
#include <wallet/walletutil.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <assert.h>
|
#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.
|
// bits of the unencrypted private key in slack space in the database file.
|
||||||
database->Rewrite();
|
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);
|
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:
|
// 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.
|
// 3. Path to a symlink to a directory.
|
||||||
// 4. For backwards compatibility, the name of a data file in -walletdir.
|
// 4. For backwards compatibility, the name of a data file in -walletdir.
|
||||||
LOCK(cs_wallets);
|
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();
|
fs::file_type path_type = fs::symlink_status(wallet_path).type();
|
||||||
if (!(path_type == fs::file_not_found || path_type == fs::directory_file ||
|
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::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(
|
error_string = strprintf(
|
||||||
"Invalid -wallet path '%s'. -wallet path should point to a directory where wallet.dat and "
|
"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, "
|
"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)",
|
"or (for backwards compatibility) the name of an existing data file in -walletdir (%s)",
|
||||||
wallet_file, GetWalletDir());
|
location.GetName(), GetWalletDir());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure that the wallet path doesn't clash with an existing wallet path
|
// Make sure that the wallet path doesn't clash with an existing wallet path
|
||||||
for (auto wallet : GetWallets()) {
|
if (IsWalletLoaded(wallet_path)) {
|
||||||
if (fs::absolute(wallet->GetName(), GetWalletDir()) == wallet_path) {
|
error_string = strprintf("Error loading wallet %s. Duplicate -wallet filename specified.", location.GetName());
|
||||||
error_string = strprintf("Error loading wallet %s. Duplicate -wallet filename specified.", wallet_file);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Keep same database environment instance across Verify/Recover calls below.
|
||||||
|
std::unique_ptr<WalletDatabase> database = WalletDatabase::Create(wallet_path);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!WalletBatch::VerifyEnvironment(wallet_path, error_string)) {
|
if (!WalletBatch::VerifyEnvironment(wallet_path, error_string)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (const fs::filesystem_error& e) {
|
} 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (salvage_wallet) {
|
if (salvage_wallet) {
|
||||||
// Recover readable keypairs:
|
// Recover readable keypairs:
|
||||||
CWallet dummyWallet("dummy", WalletDatabase::CreateDummy());
|
CWallet dummyWallet(WalletLocation(), WalletDatabase::CreateDummy());
|
||||||
std::string backup_filename;
|
std::string backup_filename;
|
||||||
if (!WalletBatch::Recover(wallet_path, (void *)&dummyWallet, WalletBatch::RecoverKeysOnlyFilter, backup_filename)) {
|
if (!WalletBatch::Recover(wallet_path, (void *)&dummyWallet, WalletBatch::RecoverKeysOnlyFilter, backup_filename)) {
|
||||||
return false;
|
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);
|
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
|
// needed to restore wallet transaction meta data after -zapwallettxes
|
||||||
std::vector<CWalletTx> vWtx;
|
std::vector<CWalletTx> vWtx;
|
||||||
|
@ -4066,7 +4071,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name,
|
||||||
if (gArgs.GetBoolArg("-zapwallettxes", false)) {
|
if (gArgs.GetBoolArg("-zapwallettxes", false)) {
|
||||||
uiInterface.InitMessage(_("Zapping all transactions from wallet..."));
|
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);
|
DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx);
|
||||||
if (nZapWalletRet != DBErrors::LOAD_OK) {
|
if (nZapWalletRet != DBErrors::LOAD_OK) {
|
||||||
InitError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile));
|
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;
|
bool fFirstRun = true;
|
||||||
// TODO: Can't use std::make_shared because we need a custom deleter but
|
// TODO: Can't use std::make_shared because we need a custom deleter but
|
||||||
// should be possible to use std::allocate_shared.
|
// 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);
|
DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun);
|
||||||
if (nLoadWalletRet != DBErrors::LOAD_OK)
|
if (nLoadWalletRet != DBErrors::LOAD_OK)
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
#include <wallet/crypter.h>
|
#include <wallet/crypter.h>
|
||||||
#include <wallet/coinselection.h>
|
#include <wallet/coinselection.h>
|
||||||
#include <wallet/walletdb.h>
|
#include <wallet/walletdb.h>
|
||||||
#include <wallet/rpcwallet.h>
|
#include <wallet/walletutil.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
@ -755,12 +755,8 @@ private:
|
||||||
*/
|
*/
|
||||||
bool AddWatchOnly(const CScript& dest) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
bool AddWatchOnly(const CScript& dest) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
|
|
||||||
/**
|
/** Wallet location which includes wallet name (see WalletLocation). */
|
||||||
* Wallet filename from wallet=<path> command line or config option.
|
WalletLocation m_location;
|
||||||
* Used in debug logs and to send RPCs to the right wallet instance when
|
|
||||||
* more than one wallet is loaded.
|
|
||||||
*/
|
|
||||||
std::string m_name;
|
|
||||||
|
|
||||||
/** Internal database handle. */
|
/** Internal database handle. */
|
||||||
std::unique_ptr<WalletDatabase> database;
|
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,
|
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 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.
|
/** 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 LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
void MarkPreSplitKeys();
|
void MarkPreSplitKeys();
|
||||||
|
@ -818,7 +816,7 @@ public:
|
||||||
unsigned int nMasterKeyMaxID = 0;
|
unsigned int nMasterKeyMaxID = 0;
|
||||||
|
|
||||||
/** Construct wallet with specified name and database implementation. */
|
/** 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);
|
bool MarkReplaced(const uint256& originalHash, const uint256& newHash);
|
||||||
|
|
||||||
//! Verify wallet naming and perform salvage on the wallet if required
|
//! 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 */
|
/* 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
|
* Wallet post-init setup
|
||||||
|
|
|
@ -25,3 +25,14 @@ fs::path GetWalletDir()
|
||||||
|
|
||||||
return path;
|
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.
|
//! Get the path of the wallet directory.
|
||||||
fs::path GetWalletDir();
|
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
|
#endif // BITCOIN_WALLET_WALLETUTIL_H
|
||||||
|
|
|
@ -478,10 +478,8 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||||
|
|
||||||
############################################################
|
############################################################
|
||||||
# locked wallet test
|
# locked wallet test
|
||||||
self.stop_node(0)
|
self.nodes[1].encryptwallet("test")
|
||||||
self.nodes[1].node_encrypt_wallet("test")
|
self.stop_nodes()
|
||||||
self.stop_node(2)
|
|
||||||
self.stop_node(3)
|
|
||||||
|
|
||||||
self.start_nodes()
|
self.start_nodes()
|
||||||
# This test is not meant to test fee estimation and we'd like
|
# 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
|
assert_msg = "bitcoind should have exited with expected error " + expected_msg
|
||||||
self._raise_assertion_error(assert_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):
|
def add_p2p_connection(self, p2p_conn, *, wait_for_verack=True, **kwargs):
|
||||||
"""Add a p2p connection to the node.
|
"""Add a p2p connection to the node.
|
||||||
|
|
||||||
|
|
|
@ -41,8 +41,7 @@ class BumpFeeTest(BitcoinTestFramework):
|
||||||
|
|
||||||
def run_test(self):
|
def run_test(self):
|
||||||
# Encrypt wallet for test_locked_wallet_fails test
|
# Encrypt wallet for test_locked_wallet_fails test
|
||||||
self.nodes[1].node_encrypt_wallet(WALLET_PASSPHRASE)
|
self.nodes[1].encryptwallet(WALLET_PASSPHRASE)
|
||||||
self.start_node(1)
|
|
||||||
self.nodes[1].walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT)
|
self.nodes[1].walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT)
|
||||||
|
|
||||||
connect_nodes_bi(self.nodes, 0, 1)
|
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
|
assert_equal(witness_addr_ret, witness_addr) # p2sh-p2wsh address added to the first key
|
||||||
|
|
||||||
#encrypt wallet, restart, unlock and dump
|
#encrypt wallet, restart, unlock and dump
|
||||||
self.nodes[0].node_encrypt_wallet('test')
|
self.nodes[0].encryptwallet('test')
|
||||||
self.start_node(0)
|
|
||||||
self.nodes[0].walletpassphrase('test', 10)
|
self.nodes[0].walletpassphrase('test', 10)
|
||||||
# Should be a no-op:
|
# Should be a no-op:
|
||||||
self.nodes[0].keypoolrefill()
|
self.nodes[0].keypoolrefill()
|
||||||
|
|
|
@ -33,8 +33,7 @@ class WalletEncryptionTest(BitcoinTestFramework):
|
||||||
assert_equal(len(privkey), 52)
|
assert_equal(len(privkey), 52)
|
||||||
|
|
||||||
# Encrypt the wallet
|
# Encrypt the wallet
|
||||||
self.nodes[0].node_encrypt_wallet(passphrase)
|
self.nodes[0].encryptwallet(passphrase)
|
||||||
self.start_node(0)
|
|
||||||
|
|
||||||
# Test that the wallet is encrypted
|
# Test that the wallet is encrypted
|
||||||
assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first", self.nodes[0].dumpprivkey, address)
|
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'])
|
assert(addr_before_encrypting_data['hdseedid'] == wallet_info_old['hdseedid'])
|
||||||
|
|
||||||
# Encrypt wallet and wait to terminate
|
# Encrypt wallet and wait to terminate
|
||||||
nodes[0].node_encrypt_wallet('test')
|
nodes[0].encryptwallet('test')
|
||||||
# Restart node 0
|
|
||||||
self.start_node(0)
|
|
||||||
# Keep creating keys
|
# Keep creating keys
|
||||||
addr = nodes[0].getnewaddress()
|
addr = nodes[0].getnewaddress()
|
||||||
addr_data = nodes[0].getaddressinfo(addr)
|
addr_data = nodes[0].getaddressinfo(addr)
|
||||||
|
|
|
@ -217,9 +217,16 @@ class MultiWalletTest(BitcoinTestFramework):
|
||||||
# Fail to load duplicate wallets
|
# 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])
|
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
|
# 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')
|
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
|
# 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')
|
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)
|
self.nodes[0].loadwallet(wallet_name)
|
||||||
assert_equal(rpc.getaddressinfo(addr)['ismine'], True)
|
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__':
|
if __name__ == '__main__':
|
||||||
MultiWalletTest().main()
|
MultiWalletTest().main()
|
||||||
|
|
|
@ -30,7 +30,6 @@ EXPECTED_CIRCULAR_DEPENDENCIES=(
|
||||||
"validation -> validationinterface -> validation"
|
"validation -> validationinterface -> validation"
|
||||||
"wallet/coincontrol -> wallet/wallet -> wallet/coincontrol"
|
"wallet/coincontrol -> wallet/wallet -> wallet/coincontrol"
|
||||||
"wallet/fees -> wallet/wallet -> wallet/fees"
|
"wallet/fees -> wallet/wallet -> wallet/fees"
|
||||||
"wallet/rpcwallet -> wallet/wallet -> wallet/rpcwallet"
|
|
||||||
"wallet/wallet -> wallet/walletdb -> wallet/wallet"
|
"wallet/wallet -> wallet/walletdb -> wallet/wallet"
|
||||||
"policy/fees -> policy/policy -> validation -> policy/fees"
|
"policy/fees -> policy/policy -> validation -> policy/fees"
|
||||||
"policy/rbf -> txmempool -> validation -> policy/rbf"
|
"policy/rbf -> txmempool -> validation -> policy/rbf"
|
||||||
|
|
Loading…
Reference in a new issue