Merge pull request #4081 from xdustinface/pr-fix-issue-3521

backport: Some wallet related PRs
This commit is contained in:
UdjinM6 2021-04-07 00:38:41 +03:00 committed by GitHub
commit 6ba3b2f3b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 446 additions and 170 deletions

View File

@ -33,7 +33,7 @@ static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector<CO
// (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());
std::vector<COutput> vCoins; std::vector<COutput> vCoins;
LOCK(wallet.cs_wallet); LOCK(wallet.cs_wallet);

View File

@ -484,6 +484,10 @@ public:
} }
bool hdEnabled() override { return m_wallet.IsHDEnabled(); } bool hdEnabled() override { return m_wallet.IsHDEnabled(); }
CoinJoin::Client& coinJoin() override { return m_coinjoin; } CoinJoin::Client& coinJoin() override { return m_coinjoin; }
std::unique_ptr<Handler> handleUnload(UnloadFn fn) override
{
return MakeHandler(m_wallet.NotifyUnload.connect(fn));
}
std::unique_ptr<Handler> handleShowProgress(ShowProgressFn fn) override std::unique_ptr<Handler> handleShowProgress(ShowProgressFn fn) override
{ {
return MakeHandler(m_wallet.ShowProgress.connect(fn)); return MakeHandler(m_wallet.ShowProgress.connect(fn));

View File

@ -249,6 +249,10 @@ public:
virtual CoinJoin::Client& coinJoin() = 0; virtual CoinJoin::Client& coinJoin() = 0;
//! Register handler for unload message.
using UnloadFn = std::function<void()>;
virtual std::unique_ptr<Handler> handleUnload(UnloadFn fn) = 0;
//! Register handler for show progress messages. //! Register handler for show progress messages.
using ShowProgressFn = std::function<void(const std::string& title, int progress)>; using ShowProgressFn = std::function<void(const std::string& title, int progress)>;
virtual std::unique_ptr<Handler> handleShowProgress(ShowProgressFn fn) = 0; virtual std::unique_ptr<Handler> handleShowProgress(ShowProgressFn fn) = 0;

View File

@ -140,9 +140,9 @@ void AskPassphraseDialog::accept()
if (model->wallet().hdEnabled()) { if (model->wallet().hdEnabled()) {
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 funds from being stolen by malware infecting your computer.").arg(tr(PACKAGE_NAME)) + "your funds 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. "
@ -152,9 +152,9 @@ void AskPassphraseDialog::accept()
} else { } else {
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 funds from being stolen by malware infecting your computer.").arg(tr(PACKAGE_NAME)) + "your funds 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. "
@ -162,7 +162,6 @@ void AskPassphraseDialog::accept()
"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
{ {

View File

@ -639,8 +639,19 @@ void BitcoinGUI::createToolBars()
#ifdef ENABLE_WALLET #ifdef ENABLE_WALLET
m_wallet_selector = new QComboBox(this); m_wallet_selector = new QComboBox(this);
m_wallet_selector->setHidden(true); connect(m_wallet_selector, SIGNAL(currentIndexChanged(int)), this, SLOT(setCurrentWalletBySelectorIndex(int)));
connect(m_wallet_selector, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(setCurrentWallet(const QString&)));
QVBoxLayout* walletSelectorLayout = new QVBoxLayout(this);
walletSelectorLayout->addWidget(m_wallet_selector);
walletSelectorLayout->setSpacing(0);
walletSelectorLayout->setMargin(0);
walletSelectorLayout->setContentsMargins(5, 0, 5, 0);
QWidget* walletSelector = new QWidget(this);
walletSelector->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
walletSelector->setObjectName("walletSelector");
walletSelector->setLayout(walletSelectorLayout);
m_wallet_selector_action = appToolBar->insertWidget(appToolBarLogoAction, walletSelector);
m_wallet_selector_action->setVisible(false);
#endif #endif
QLabel *logoLabel = new QLabel(); QLabel *logoLabel = new QLabel();
@ -769,25 +780,31 @@ bool BitcoinGUI::addWallet(WalletModel *walletModel)
if(!walletFrame) if(!walletFrame)
return false; return false;
const QString name = walletModel->getWalletName(); const QString name = walletModel->getWalletName();
QString display_name = name.isEmpty() ? "["+tr("default wallet")+"]" : name;
setWalletActionsEnabled(true); setWalletActionsEnabled(true);
m_wallet_selector->addItem(name); m_wallet_selector->addItem(display_name, name);
if (m_wallet_selector->count() == 2) { if (m_wallet_selector->count() == 2) {
m_wallet_selector->setHidden(false); m_wallet_selector_action->setVisible(true);
QVBoxLayout* layout = new QVBoxLayout(this);
layout->addWidget(m_wallet_selector);
layout->setSpacing(0);
layout->setMargin(0);
layout->setContentsMargins(5, 0, 5, 0);
QWidget* walletSelector = new QWidget(this);
walletSelector->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
walletSelector->setObjectName("walletSelector");
walletSelector->setLayout(layout);
appToolBar->insertWidget(appToolBarLogoAction, walletSelector);
} }
rpcConsole->addWallet(walletModel); rpcConsole->addWallet(walletModel);
return walletFrame->addWallet(walletModel); return walletFrame->addWallet(walletModel);
} }
bool BitcoinGUI::removeWallet(WalletModel* walletModel)
{
if (!walletFrame) return false;
QString name = walletModel->getWalletName();
int index = m_wallet_selector->findData(name);
m_wallet_selector->removeItem(index);
if (m_wallet_selector->count() == 0) {
setWalletActionsEnabled(false);
} else if (m_wallet_selector->count() == 1) {
m_wallet_selector_action->setVisible(false);
}
rpcConsole->removeWallet(walletModel);
return walletFrame->removeWallet(name);
}
bool BitcoinGUI::setCurrentWallet(const QString& name) bool BitcoinGUI::setCurrentWallet(const QString& name)
{ {
if(!walletFrame) if(!walletFrame)
@ -795,6 +812,12 @@ bool BitcoinGUI::setCurrentWallet(const QString& name)
return walletFrame->setCurrentWallet(name); return walletFrame->setCurrentWallet(name);
} }
bool BitcoinGUI::setCurrentWalletBySelectorIndex(int index)
{
QString internal_name = m_wallet_selector->itemData(index).toString();
return setCurrentWallet(internal_name);
}
void BitcoinGUI::removeAllWallets() void BitcoinGUI::removeAllWallets()
{ {
if(!walletFrame) if(!walletFrame)

View File

@ -76,6 +76,7 @@ public:
functionality. functionality.
*/ */
bool addWallet(WalletModel *walletModel); bool addWallet(WalletModel *walletModel);
bool removeWallet(WalletModel* walletModel);
void removeAllWallets(); void removeAllWallets();
#endif // ENABLE_WALLET #endif // ENABLE_WALLET
bool enableWallet; bool enableWallet;
@ -140,8 +141,8 @@ private:
QAction *openAction; QAction *openAction;
QAction *showHelpMessageAction; QAction *showHelpMessageAction;
QAction *showCoinJoinHelpAction; QAction *showCoinJoinHelpAction;
QAction *m_wallet_selector_action = nullptr;
QLabel *m_wallet_selector_label;
QComboBox *m_wallet_selector; QComboBox *m_wallet_selector;
QSystemTrayIcon *trayIcon; QSystemTrayIcon *trayIcon;
@ -238,6 +239,7 @@ public Q_SLOTS:
#ifdef ENABLE_WALLET #ifdef ENABLE_WALLET
bool setCurrentWallet(const QString& name); bool setCurrentWallet(const QString& name);
bool setCurrentWalletBySelectorIndex(int index);
/** Set the UI status indicators based on the currently selected wallet. /** Set the UI status indicators based on the currently selected wallet.
*/ */
void updateWalletStatus(); void updateWalletStatus();

View File

@ -212,6 +212,7 @@ public Q_SLOTS:
/// Handle runaway exceptions. Shows a message box with the problem and quits the program. /// Handle runaway exceptions. Shows a message box with the problem and quits the program.
void handleRunawayException(const QString &message); void handleRunawayException(const QString &message);
void addWallet(WalletModel* walletModel); void addWallet(WalletModel* walletModel);
void removeWallet();
Q_SIGNALS: Q_SIGNALS:
void requestedInitialize(); void requestedInitialize();
@ -454,11 +455,22 @@ void BitcoinApplication::addWallet(WalletModel* walletModel)
connect(walletModel, SIGNAL(coinsSent(WalletModel*, SendCoinsRecipient, QByteArray)), connect(walletModel, SIGNAL(coinsSent(WalletModel*, SendCoinsRecipient, QByteArray)),
paymentServer, SLOT(fetchPaymentACK(WalletModel*, const SendCoinsRecipient&, QByteArray))); paymentServer, SLOT(fetchPaymentACK(WalletModel*, const SendCoinsRecipient&, QByteArray)));
connect(walletModel, SIGNAL(unload()), this, SLOT(removeWallet()));
m_wallet_models.push_back(walletModel); m_wallet_models.push_back(walletModel);
#endif #endif
} }
void BitcoinApplication::removeWallet()
{
#ifdef ENABLE_WALLET
WalletModel* walletModel = static_cast<WalletModel*>(sender());
m_wallet_models.erase(std::find(m_wallet_models.begin(), m_wallet_models.end(), walletModel));
window->removeWallet(walletModel);
walletModel->deleteLater();
#endif
}
void BitcoinApplication::initializeResult(bool success) void BitcoinApplication::initializeResult(bool success)
{ {
qDebug() << __func__ << ": Initialization result: " << success; qDebug() << __func__ << ": Initialization result: " << success;
@ -478,8 +490,10 @@ void BitcoinApplication::initializeResult(bool success)
#ifdef ENABLE_WALLET #ifdef ENABLE_WALLET
m_handler_load_wallet = m_node.handleLoadWallet([this](std::unique_ptr<interfaces::Wallet> wallet) { m_handler_load_wallet = m_node.handleLoadWallet([this](std::unique_ptr<interfaces::Wallet> wallet) {
QMetaObject::invokeMethod(this, "addWallet", Qt::QueuedConnection, WalletModel* wallet_model = new WalletModel(std::move(wallet), m_node, optionsModel, nullptr);
Q_ARG(WalletModel*, new WalletModel(std::move(wallet), m_node, optionsModel))); // Fix wallet model thread affinity.
wallet_model->moveToThread(thread());
QMetaObject::invokeMethod(this, "addWallet", Qt::QueuedConnection, Q_ARG(WalletModel*, wallet_model));
}); });
for (auto& wallet : m_node.getWallets()) { for (auto& wallet : m_node.getWallets()) {

View File

@ -680,6 +680,10 @@ QToolBar > QToolButton:checked {
color: #c7c7c7; color: #c7c7c7;
} }
QToolBar > QToolButton:disabled {
color: #4a4a4b;
}
/****************************************************** /******************************************************
QToolTip QToolTip
******************************************************/ ******************************************************/

View File

@ -665,6 +665,10 @@ QToolBar > QToolButton:checked {
color: #555; color: #555;
} }
QToolBar > QToolButton:disabled {
color: #a7a7a7;
}
/****************************************************** /******************************************************
QToolTip QToolTip
******************************************************/ ******************************************************/

View File

@ -743,7 +743,8 @@ void RPCConsole::addWallet(WalletModel * const walletModel)
{ {
const QString name = walletModel->getWalletName(); const QString name = walletModel->getWalletName();
// use name for text and internal data object (to allow to move to a wallet id later) // use name for text and internal data object (to allow to move to a wallet id later)
ui->WalletSelector->addItem(name, name); QString display_name = name.isEmpty() ? "["+tr("default wallet")+"]" : name;
ui->WalletSelector->addItem(display_name, name);
if (ui->WalletSelector->count() == 2 && !isVisible()) { if (ui->WalletSelector->count() == 2 && !isVisible()) {
// First wallet added, set to default so long as the window isn't presently visible (and potentially in use) // First wallet added, set to default so long as the window isn't presently visible (and potentially in use)
ui->WalletSelector->setCurrentIndex(1); ui->WalletSelector->setCurrentIndex(1);
@ -753,6 +754,16 @@ void RPCConsole::addWallet(WalletModel * const walletModel)
ui->WalletSelectorLabel->setVisible(true); ui->WalletSelectorLabel->setVisible(true);
} }
} }
void RPCConsole::removeWallet(WalletModel * const walletModel)
{
const QString name = walletModel->getWalletName();
ui->WalletSelector->removeItem(ui->WalletSelector->findData(name));
if (ui->WalletSelector->count() == 2) {
ui->WalletSelector->setVisible(false);
ui->WalletSelectorLabel->setVisible(false);
}
}
#endif #endif
static QString categoryClass(int category) static QString categoryClass(int category)

View File

@ -49,6 +49,7 @@ public:
void setClientModel(ClientModel *model); void setClientModel(ClientModel *model);
void addWallet(WalletModel * const walletModel); void addWallet(WalletModel * const walletModel);
void removeWallet(WalletModel* const walletModel);
enum MessageClass { enum MessageClass {
MC_ERROR, MC_ERROR,

View File

@ -119,7 +119,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());
AddWallet(wallet); AddWallet(wallet);
bool firstRun; bool firstRun;
wallet->LoadWallet(firstRun); wallet->LoadWallet(firstRun);

View File

@ -93,6 +93,7 @@ bool WalletFrame::removeWallet(const QString &name)
WalletView *walletView = mapWalletViews.take(name); WalletView *walletView = mapWalletViews.take(name);
walletStack->removeWidget(walletView); walletStack->removeWidget(walletView);
delete walletView;
return true; return true;
} }

View File

@ -427,6 +427,12 @@ int64_t WalletModel::getKeysLeftSinceAutoBackup() const
} }
// Handlers for core signals // Handlers for core signals
static void NotifyUnload(WalletModel* walletModel)
{
qDebug() << "NotifyUnload";
QMetaObject::invokeMethod(walletModel, "unload", Qt::QueuedConnection);
}
static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel) static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel)
{ {
qDebug() << "NotifyKeyStoreStatusChanged"; qDebug() << "NotifyKeyStoreStatusChanged";
@ -485,6 +491,7 @@ static void NotifyWatchonlyChanged(WalletModel *walletmodel, bool fHaveWatchonly
void WalletModel::subscribeToCoreSignals() void WalletModel::subscribeToCoreSignals()
{ {
// Connect signals to wallet // Connect signals to wallet
m_handler_unload = m_wallet->handleUnload(boost::bind(&NotifyUnload, this));
m_handler_status_changed = m_wallet->handleStatusChanged(boost::bind(&NotifyKeyStoreStatusChanged, this)); m_handler_status_changed = m_wallet->handleStatusChanged(boost::bind(&NotifyKeyStoreStatusChanged, this));
m_handler_address_book_changed = m_wallet->handleAddressBookChanged(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5)); m_handler_address_book_changed = m_wallet->handleAddressBookChanged(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5));
m_handler_transaction_changed = m_wallet->handleTransactionChanged(boost::bind(NotifyTransactionChanged, this, _1, _2)); m_handler_transaction_changed = m_wallet->handleTransactionChanged(boost::bind(NotifyTransactionChanged, this, _1, _2));
@ -497,6 +504,7 @@ void WalletModel::subscribeToCoreSignals()
void WalletModel::unsubscribeFromCoreSignals() void WalletModel::unsubscribeFromCoreSignals()
{ {
// Disconnect signals from wallet // Disconnect signals from wallet
m_handler_unload->disconnect();
m_handler_status_changed->disconnect(); m_handler_status_changed->disconnect();
m_handler_address_book_changed->disconnect(); m_handler_address_book_changed->disconnect();
m_handler_transaction_changed->disconnect(); m_handler_transaction_changed->disconnect();

View File

@ -214,6 +214,7 @@ public:
bool isMultiwallet(); bool isMultiwallet();
private: private:
std::unique_ptr<interfaces::Wallet> m_wallet; std::unique_ptr<interfaces::Wallet> m_wallet;
std::unique_ptr<interfaces::Handler> m_handler_unload;
std::unique_ptr<interfaces::Handler> m_handler_status_changed; std::unique_ptr<interfaces::Handler> m_handler_status_changed;
std::unique_ptr<interfaces::Handler> m_handler_address_book_changed; std::unique_ptr<interfaces::Handler> m_handler_address_book_changed;
std::unique_ptr<interfaces::Handler> m_handler_transaction_changed; std::unique_ptr<interfaces::Handler> m_handler_transaction_changed;
@ -271,6 +272,9 @@ Q_SIGNALS:
// Watch-only address added // Watch-only address added
void notifyWatchonlyChanged(bool fHaveWatchonly); void notifyWatchonlyChanged(bool fHaveWatchonly);
// Signal that wallet is about to be removed
void unload();
public Q_SLOTS: public Q_SLOTS:
/* Wallet status might have changed */ /* Wallet status might have changed */
void updateStatus(); void updateStatus();

View File

@ -6,6 +6,7 @@
#ifdef ENABLE_WALLET #ifdef ENABLE_WALLET
#include <coinjoin/coinjoin-client.h> #include <coinjoin/coinjoin-client.h>
#include <coinjoin/coinjoin-client-options.h> #include <coinjoin/coinjoin-client-options.h>
#include <wallet/rpcwallet.h>
#endif // ENABLE_WALLET #endif // ENABLE_WALLET
#include <coinjoin/coinjoin-server.h> #include <coinjoin/coinjoin-server.h>
#include <rpc/server.h> #include <rpc/server.h>

View File

@ -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,25 +30,19 @@ 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)"));
} }
} }
} }
@ -56,9 +51,13 @@ 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, BerkeleyEnvironment> g_dbenvs GUARDED_BY(cs_db); //!< Map from directory name to open 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,6 +70,23 @@ 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);
auto env = g_dbenvs.find(env_directory.string());
if (env == g_dbenvs.end()) return false;
return env->second.IsDatabaseLoaded(database_filename);
}
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); LOCK(cs_db);
// Note: An ununsed temporary BerkeleyEnvironment object may be created inside the // Note: An ununsed temporary BerkeleyEnvironment object may be created inside the
// emplace function if the key already exists. This is a little inefficient, // emplace function if the key already exists. This is a little inefficient,
@ -90,13 +106,13 @@ 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();
} }
} }
@ -463,7 +479,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 +520,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, 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,22 +572,50 @@ 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()) {
@ -694,8 +738,9 @@ void BerkeleyEnvironment::Flush(bool fShutdown)
if (mapFileUseCount.empty()) { if (mapFileUseCount.empty()) {
dbenv->log_archive(&listp, DB_ARCH_REMOVE); dbenv->log_archive(&listp, DB_ARCH_REMOVE);
Close(); Close();
if (!fMockDb) if (!fMockDb) {
fs::remove_all(fs::path(strPath) / "database"); fs::remove_all(fs::path(strPath) / "database");
}
} }
} }
} }
@ -794,5 +839,24 @@ void BerkeleyDatabase::Flush(bool shutdown)
{ {
if (!IsDummy()) { if (!IsDummy()) {
env->Flush(shutdown); env->Flush(shutdown);
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();
} }
} }

View File

@ -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,7 +45,9 @@ 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();
@ -46,6 +56,7 @@ public:
void MakeMock(); 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,6 +98,9 @@ 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); BerkeleyEnvironment* GetWalletEnv(const fs::path& wallet_path, std::string& database_filename);
@ -106,6 +121,8 @@ public:
nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0) nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0)
{ {
env = GetWalletEnv(wallet_path, strFile); env = GetWalletEnv(wallet_path, strFile);
auto inserted = env->m_databases.emplace(strFile, std::ref(*this));
assert(inserted.second);
if (mock) { if (mock) {
env->Close(); env->Close();
env->Reset(); env->Reset();
@ -113,6 +130,13 @@ public:
} }
} }
~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)
{ {
@ -145,11 +169,16 @@ 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;
/** 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 */ /** BerkeleyDB specific */
BerkeleyEnvironment *env; BerkeleyEnvironment *env;

View File

@ -375,15 +375,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;
@ -400,7 +400,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;
} }

View File

@ -43,12 +43,21 @@
static const std::string WALLET_ENDPOINT_BASE = "/wallet/"; static const std::string WALLET_ENDPOINT_BASE = "/wallet/";
std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request) bool GetWalletNameFromJSONRPCRequest(const JSONRPCRequest& request, std::string& wallet_name)
{ {
if (request.URI.substr(0, WALLET_ENDPOINT_BASE.size()) == WALLET_ENDPOINT_BASE) { if (request.URI.substr(0, WALLET_ENDPOINT_BASE.size()) == WALLET_ENDPOINT_BASE) {
// wallet endpoint was used // wallet endpoint was used
std::string requestedWallet = urlDecode(request.URI.substr(WALLET_ENDPOINT_BASE.size())); wallet_name = urlDecode(request.URI.substr(WALLET_ENDPOINT_BASE.size()));
std::shared_ptr<CWallet> pwallet = GetWallet(requestedWallet); return true;
}
return false;
}
std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request)
{
std::string wallet_name;
if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
std::shared_ptr<CWallet> pwallet = GetWallet(wallet_name);
if (!pwallet) throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded"); if (!pwallet) throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
return pwallet; return pwallet;
} }
@ -69,11 +78,6 @@ bool EnsureWalletIsAvailable(CWallet * const pwallet, bool avoidException)
if (pwallet) return true; if (pwallet) return true;
if (avoidException) return false; if (avoidException) return false;
if (!HasWallets()) { if (!HasWallets()) {
// Note: It isn't currently possible to trigger this error because
// wallet RPC methods aren't registered unless a wallet is loaded. But
// this error is being kept as a precaution, because it's possible in
// the future that wallet RPC methods might get or remain registered
// when no wallets are loaded.
throw JSONRPCError( throw JSONRPCError(
RPC_METHOD_NOT_FOUND, "Method not found (wallet method is disabled because no wallet is loaded)"); RPC_METHOD_NOT_FOUND, "Method not found (wallet method is disabled because no wallet is loaded)");
} }
@ -2676,7 +2680,6 @@ 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"
@ -2714,11 +2717,7 @@ 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; Dash Core 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.";
} }
UniValue lockunspent(const JSONRPCRequest& request) UniValue lockunspent(const JSONRPCRequest& request)
@ -3227,14 +3226,6 @@ UniValue upgradetohd(const JSONRPCRequest& request)
pwallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, true); pwallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, true);
} }
if (!prev_encrypted && pwallet->IsCrypted()) {
// 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 successfully upgraded and encrypted, Dash Core server is stopping. Remember to make a backup before restarting.";
}
return true; return true;
} }
@ -3293,7 +3284,7 @@ UniValue keepass(const JSONRPCRequest& request)
return "Invalid command"; return "Invalid command";
} }
UniValue loadwallet(const JSONRPCRequest& request) static UniValue loadwallet(const JSONRPCRequest& request)
{ {
if (request.fHelp || request.params.size() != 1) if (request.fHelp || request.params.size() != 1)
throw std::runtime_error( throw std::runtime_error(
@ -3312,26 +3303,26 @@ 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.");
} }
@ -3345,7 +3336,7 @@ UniValue loadwallet(const JSONRPCRequest& request)
return obj; return obj;
} }
UniValue createwallet(const JSONRPCRequest& request) static UniValue createwallet(const JSONRPCRequest& request)
{ {
if (request.fHelp || request.params.size() != 1) { if (request.fHelp || request.params.size() != 1) {
throw std::runtime_error( throw std::runtime_error(
@ -3363,21 +3354,20 @@ 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;
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);
} }
const auto wallet = CWallet::CreateWalletFromFile(wallet_name, fs::absolute(wallet_name, GetWalletDir())); const auto wallet = CWallet::CreateWalletFromFile(location);
if (!wallet) { if (!wallet) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed."); throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed.");
} }
@ -3392,6 +3382,55 @@ UniValue createwallet(const JSONRPCRequest& request)
return obj; return obj;
} }
static UniValue unloadwallet(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() > 1) {
throw std::runtime_error(
"unloadwallet ( \"wallet_name\" )\n"
"Unloads the wallet referenced by the request endpoint otherwise unloads the wallet specified in the argument.\n"
"Specifying the wallet name on a wallet endpoint is invalid."
"\nArguments:\n"
"1. \"wallet_name\" (string, optional) The name of the wallet to unload.\n"
"\nExamples:\n"
+ HelpExampleCli("unloadwallet", "wallet_name")
+ HelpExampleRpc("unloadwallet", "wallet_name")
);
}
std::string wallet_name;
if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
if (!request.params[0].isNull()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot unload the requested wallet");
}
} else {
wallet_name = request.params[0].get_str();
}
std::shared_ptr<CWallet> wallet = GetWallet(wallet_name);
if (!wallet) {
throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
}
// Release the "main" shared pointer and prevent further notifications.
// Note that any attempt to load the same wallet would fail until the wallet
// is destroyed (see CheckUniqueFileid).
if (!RemoveWallet(wallet)) {
throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded");
}
UnregisterValidationInterface(wallet.get());
// The wallet can be in use so it's not possible to explicitly unload here.
// Just notify the unload intent so that all shared pointers are released.
// The wallet will be destroyed once the last shared pointer is released.
wallet->NotifyUnload();
// There's no point in waiting for the wallet to unload.
// At this point this method should never fail. The unloading could only
// fail due to an unexpected error which would cause a process termination.
return NullUniValue;
}
UniValue resendwallettransactions(const JSONRPCRequest& request) UniValue resendwallettransactions(const JSONRPCRequest& request)
{ {
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
@ -4356,6 +4395,7 @@ static const CRPCCommand commands[] =
{ "wallet", "setcoinjoinamount", &setcoinjoinamount, {"amount"} }, { "wallet", "setcoinjoinamount", &setcoinjoinamount, {"amount"} },
{ "wallet", "signmessage", &signmessage, {"address","message"} }, { "wallet", "signmessage", &signmessage, {"address","message"} },
{ "wallet", "signrawtransactionwithwallet", &signrawtransactionwithwallet, {"hexstring","prevtxs","sighashtype"} }, { "wallet", "signrawtransactionwithwallet", &signrawtransactionwithwallet, {"hexstring","prevtxs","sighashtype"} },
{ "wallet", "unloadwallet", &unloadwallet, {"wallet_name"} },
{ "wallet", "upgradetohd", &upgradetohd, {"mnemonic", "mnemonicpassphrase", "walletpassphrase"} }, { "wallet", "upgradetohd", &upgradetohd, {"mnemonic", "mnemonicpassphrase", "walletpassphrase"} },
{ "wallet", "walletlock", &walletlock, {} }, { "wallet", "walletlock", &walletlock, {} },
{ "wallet", "walletpassphrasechange", &walletpassphrasechange, {"oldpassphrase","newpassphrase"} }, { "wallet", "walletpassphrasechange", &walletpassphrasechange, {"oldpassphrase","newpassphrase"} },

View File

@ -36,7 +36,7 @@ public:
CTransactionBuilderTestSetup() CTransactionBuilderTestSetup()
{ {
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);
AddWallet(wallet); AddWallet(wallet);

View File

@ -27,7 +27,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 const CWallet testWallet("dummy", WalletDatabase::CreateDummy()); static const 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);

View File

@ -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);

View File

@ -49,7 +49,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();
@ -64,7 +64,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();
@ -76,7 +76,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();
@ -137,7 +137,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());
@ -153,7 +153,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();
@ -183,7 +183,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, MakeTransactionRef(coinbaseTxns.back())); CWalletTx wtx(&wallet, MakeTransactionRef(coinbaseTxns.back()));
LOCK2(cs_main, wallet.cs_wallet); LOCK2(cs_main, wallet.cs_wallet);
wtx.hashBlock = chainActive.Tip()->GetBlockHash(); wtx.hashBlock = chainActive.Tip()->GetBlockHash();
@ -276,7 +276,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);
@ -395,7 +395,7 @@ public:
CreateTransactionTestSetup() CreateTransactionTestSetup()
{ {
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);
AddWallet(wallet); AddWallet(wallet);

View File

@ -28,7 +28,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 <coinjoin/coinjoin-client.h> #include <coinjoin/coinjoin-client.h>
#include <coinjoin/coinjoin-client-options.h> #include <coinjoin/coinjoin-client-options.h>
@ -111,6 +110,15 @@ CFeeRate CWallet::fallbackFee = CFeeRate(DEFAULT_FALLBACK_FEE);
CFeeRate CWallet::m_discard_rate = CFeeRate(DEFAULT_DISCARD_FEE); CFeeRate CWallet::m_discard_rate = CFeeRate(DEFAULT_DISCARD_FEE);
// Custom deleter for shared_ptr<CWallet>.
static void ReleaseWallet(CWallet* wallet)
{
LogPrintf("Releasing wallet %s\n", wallet->GetName());
wallet->BlockUntilSyncedToCurrentChain();
wallet->Flush();
delete wallet;
}
const uint256 CMerkleTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001")); const uint256 CMerkleTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001"));
/** @defgroup mapWallet /** @defgroup mapWallet
@ -913,6 +921,11 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
} }
} }
// 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);
@ -1551,7 +1564,7 @@ void CWallet::BlockUntilSyncedToCurrentChain() {
LOCK(cs_main); LOCK(cs_main);
const CBlockIndex* initialChainTip = chainActive.Tip(); const CBlockIndex* initialChainTip = chainActive.Tip();
if (m_last_block_processed->GetAncestor(initialChainTip->nHeight) == initialChainTip) { if (m_last_block_processed && m_last_block_processed->GetAncestor(initialChainTip->nHeight) == initialChainTip) {
return; return;
} }
} }
@ -4913,7 +4926,7 @@ std::vector<std::string> CWallet::GetDestValues(const std::string& prefix) const
return values; return values;
} }
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:
// //
@ -4922,25 +4935,23 @@ 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;
}
} }
try { try {
@ -4948,18 +4959,18 @@ bool CWallet::Verify(std::string wallet_file, bool salvage_wallet, std::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;
} }
std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(wallet_file, WalletDatabase::Create(wallet_path)); std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(location, WalletDatabase::Create(wallet_path));
if (!tempWallet->AutoBackupWallet(wallet_path, warning_string, error_string) && !error_string.empty()) { if (!tempWallet->AutoBackupWallet(wallet_path, warning_string, error_string) && !error_string.empty()) {
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;
@ -4969,9 +4980,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) std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const WalletLocation& location)
{ {
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;
@ -4979,7 +4990,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));
@ -4991,7 +5002,9 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name,
int64_t nStart = GetTimeMillis(); int64_t nStart = GetTimeMillis();
bool fFirstRun = true; bool fFirstRun = true;
std::shared_ptr<CWallet> walletInstance = std::make_shared<CWallet>(name, WalletDatabase::Create(path)); // 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(location, WalletDatabase::Create(location.GetPath())), ReleaseWallet);
AddWallet(walletInstance); AddWallet(walletInstance);
auto error = [&](const std::string& strError) { auto error = [&](const std::string& strError) {
RemoveWallet(walletInstance); RemoveWallet(walletInstance);
@ -5028,8 +5041,6 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name,
} }
} }
uiInterface.LoadWallet(walletInstance);
if (gArgs.GetBoolArg("-upgradewallet", fFirstRun)) if (gArgs.GetBoolArg("-upgradewallet", fFirstRun))
{ {
int nMaxVersion = gArgs.GetArg("-upgradewallet", 0); int nMaxVersion = gArgs.GetArg("-upgradewallet", 0);
@ -5204,6 +5215,8 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name,
} }
} }
uiInterface.LoadWallet(walletInstance);
// Register with the validation interface. It's ok to do this after rescan since we're still holding cs_main. // Register with the validation interface. It's ok to do this after rescan since we're still holding cs_main.
RegisterValidationInterface(walletInstance.get()); RegisterValidationInterface(walletInstance.get());
@ -5249,7 +5262,10 @@ bool CWallet::BackupWallet(const std::string& strDest)
bool CWallet::AutoBackupWallet(const fs::path& wallet_path, std::string& strBackupWarningRet, std::string& strBackupErrorRet) bool CWallet::AutoBackupWallet(const fs::path& wallet_path, std::string& strBackupWarningRet, std::string& strBackupErrorRet)
{ {
strBackupWarningRet = strBackupErrorRet = ""; strBackupWarningRet = strBackupErrorRet = "";
std::string strWalletName = m_name.empty() ? "wallet.dat" : m_name; std::string strWalletName = GetName();
if (strWalletName.empty()) {
strWalletName = "wallet.dat";
}
if (nWalletBackups <= 0) { if (nWalletBackups <= 0) {
LogPrintf("Automatic wallet backups are disabled!\n"); LogPrintf("Automatic wallet backups are disabled!\n");

View File

@ -21,7 +21,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 <coinjoin/coinjoin.h> #include <coinjoin/coinjoin.h>
#include <governance/governance-object.h> #include <governance/governance-object.h>
@ -789,12 +789,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;
@ -844,9 +840,11 @@ public:
return *database; return *database;
} }
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);
@ -864,7 +862,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))
{ {
} }
@ -1164,6 +1162,9 @@ public:
//! Flush wallet (bitdb flush) //! Flush wallet (bitdb flush)
void Flush(bool shutdown=false); void Flush(bool shutdown=false);
/** Wallet is about to be unloaded */
boost::signals2::signal<void ()> NotifyUnload;
/** /**
* Address book entry changed. * Address book entry changed.
* @note called with lock cs_wallet held. * @note called with lock cs_wallet held.
@ -1204,10 +1205,10 @@ public:
bool AbandonTransaction(const uint256& hashTx); bool AbandonTransaction(const uint256& hashTx);
//! 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); static std::shared_ptr<CWallet> CreateWalletFromFile(const WalletLocation& location);
/** /**
* Wallet post-init setup * Wallet post-init setup

View File

@ -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;
}

View File

@ -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

View File

@ -463,10 +463,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

View File

@ -439,10 +439,8 @@ class RawTransactionsTest(BitcoinTestFramework):
############################################################ ############################################################
# locked wallet test # locked wallet test
self.stop_node(0) self.nodes[1].encryptwallet("test")
self.stop_node(2) self.stop_nodes()
self.stop_node(3)
self.nodes[1].node_encrypt_wallet("test")
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

View File

@ -264,14 +264,6 @@ class TestNode():
assert_msg = "dashd should have exited with expected error " + expected_msg assert_msg = "dashd 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 dashd 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, *args, **kwargs): def add_p2p_connection(self, p2p_conn, *args, **kwargs):
"""Add a p2p connection to the node. """Add a p2p connection to the node.

View File

@ -120,8 +120,7 @@ class WalletDumpTest(BitcoinTestFramework):
assert_equal(found_addr_rsv, 180) # keypool size (external+internal) assert_equal(found_addr_rsv, 180) # keypool size (external+internal)
#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', 30) self.nodes[0].walletpassphrase('test', 30)
# Should be a no-op: # Should be a no-op:
self.nodes[0].keypoolrefill() self.nodes[0].keypoolrefill()

View File

@ -30,8 +30,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)

View File

@ -16,9 +16,7 @@ class KeyPoolTest(BitcoinTestFramework):
nodes = self.nodes nodes = self.nodes
# 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()

View File

@ -28,9 +28,7 @@ class KeyPoolTest(BitcoinTestFramework):
assert(addr_before_encrypting_data['hdchainid'] == wallet_info_old['hdchainid']) assert(addr_before_encrypting_data['hdchainid'] == wallet_info_old['hdchainid'])
# 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)

View File

@ -199,9 +199,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')
@ -232,5 +239,32 @@ class MultiWalletTest(BitcoinTestFramework):
os.mkdir(wallet_dir('empty_wallet_dir')) os.mkdir(wallet_dir('empty_wallet_dir'))
assert_raises_rpc_error(-18, "Directory empty_wallet_dir does not contain a wallet.dat file", self.nodes[0].loadwallet, 'empty_wallet_dir') assert_raises_rpc_error(-18, "Directory empty_wallet_dir does not contain a wallet.dat file", self.nodes[0].loadwallet, 'empty_wallet_dir')
self.log.info("Test dynamic wallet unloading")
# Test `unloadwallet` errors
assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[0].unloadwallet)
assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", self.nodes[0].unloadwallet, "dummy")
assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", node.get_wallet_rpc("dummy").unloadwallet)
assert_raises_rpc_error(-8, "Cannot unload the requested wallet", w1.unloadwallet, "w2"),
# Successfully unload the specified wallet name
self.nodes[0].unloadwallet("w1")
assert 'w1' not in self.nodes[0].listwallets()
# Successfully unload the wallet referenced by the request endpoint
w2.unloadwallet()
assert 'w2' not in self.nodes[0].listwallets()
# Successfully unload all wallets
for wallet_name in self.nodes[0].listwallets():
self.nodes[0].unloadwallet(wallet_name)
assert_equal(self.nodes[0].listwallets(), [])
assert_raises_rpc_error(-32601, "Method not found (wallet method is disabled because no wallet is loaded)", self.nodes[0].getwalletinfo)
# Successfully load a previously unloaded wallet
self.nodes[0].loadwallet('w1')
assert_equal(self.nodes[0].listwallets(), ['w1'])
assert_equal(w1.getwalletinfo()['walletname'], 'w1')
if __name__ == '__main__': if __name__ == '__main__':
MultiWalletTest().main() MultiWalletTest().main()

View File

@ -139,8 +139,8 @@ class WalletUpgradeToHDTest(BitcoinTestFramework):
self.log.info("Same mnemonic, same mnemonic passphrase, encrypt wallet on upgrade, should recover all coins after rescan") self.log.info("Same mnemonic, same mnemonic passphrase, encrypt wallet on upgrade, should recover all coins after rescan")
walletpass = "111pass222" walletpass = "111pass222"
# Upgrading and encrypting at the saame time results in a warning # Upgrading and encrypting at the saame time results in a warning
assert_equal(node.upgradetohd(mnemonic, "", walletpass), "Wallet successfully upgraded and encrypted, Dash Core server is stopping. Remember to make a backup before restarting.") assert node.upgradetohd(mnemonic, "", walletpass)
# Wallet encryption results in node shutdown node.stop()
node.wait_until_stopped() node.wait_until_stopped()
self.start_node(0, extra_args=['-rescan']) self.start_node(0, extra_args=['-rescan'])
assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", node.dumphdinfo) assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", node.dumphdinfo)
@ -161,13 +161,12 @@ class WalletUpgradeToHDTest(BitcoinTestFramework):
self.log.info("Same mnemonic, same mnemonic passphrase, encrypt wallet first, should recover all coins on upgrade after rescan") self.log.info("Same mnemonic, same mnemonic passphrase, encrypt wallet first, should recover all coins on upgrade after rescan")
walletpass = "111pass222" walletpass = "111pass222"
node.encryptwallet(walletpass) node.encryptwallet(walletpass)
# Wallet encryption results in node shutdown node.stop()
node.wait_until_stopped() node.wait_until_stopped()
self.start_node(0, extra_args=['-rescan']) self.start_node(0, extra_args=['-rescan'])
assert_raises_rpc_error(-14, "Cannot upgrade encrypted wallet to HD without the wallet passphrase", node.upgradetohd, mnemonic) assert_raises_rpc_error(-14, "Cannot upgrade encrypted wallet to HD without the wallet passphrase", node.upgradetohd, mnemonic)
assert_raises_rpc_error(-14, "The wallet passphrase entered was incorrect", node.upgradetohd, mnemonic, "", "wrongpass") assert_raises_rpc_error(-14, "The wallet passphrase entered was incorrect", node.upgradetohd, mnemonic, "", "wrongpass")
assert(node.upgradetohd(mnemonic, "", walletpass)) assert(node.upgradetohd(mnemonic, "", walletpass))
# Note: upgrading an already encrypted wallet does not result in node shutdown
assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", node.dumphdinfo) assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", node.dumphdinfo)
node.walletpassphrase(walletpass, 100) node.walletpassphrase(walletpass, 100)
assert_equal(mnemonic, node.dumphdinfo()['mnemonic']) assert_equal(mnemonic, node.dumphdinfo()['mnemonic'])

View File

@ -28,7 +28,6 @@ EXPECTED_CIRCULAR_DEPENDENCIES=(
"txmempool -> validation -> txmempool" "txmempool -> validation -> txmempool"
"validation -> validationinterface -> validation" "validation -> validationinterface -> validation"
"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"
"qt/addressbookpage -> qt/bitcoingui -> qt/walletview -> qt/addressbookpage" "qt/addressbookpage -> qt/bitcoingui -> qt/walletview -> qt/addressbookpage"