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)
static void CoinSelection(benchmark::State& state)
{
const CWallet wallet("dummy", WalletDatabase::CreateDummy());
const CWallet wallet(WalletLocation(), WalletDatabase::CreateDummy());
std::vector<COutput> vCoins;
LOCK(wallet.cs_wallet);

View File

@ -484,6 +484,10 @@ public:
}
bool hdEnabled() override { return m_wallet.IsHDEnabled(); }
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
{
return MakeHandler(m_wallet.ShowProgress.connect(fn));

View File

@ -249,6 +249,10 @@ public:
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.
using ShowProgressFn = std::function<void(const std::string& title, int progress)>;
virtual std::unique_ptr<Handler> handleShowProgress(ShowProgressFn fn) = 0;

View File

@ -140,9 +140,9 @@ void AskPassphraseDialog::accept()
if (model->wallet().hdEnabled()) {
QMessageBox::warning(this, tr("Wallet encrypted"),
"<qt>" +
tr("%1 will close now to finish the encryption process. "
tr("Your wallet is now encrypted. "
"Remember that encrypting your wallet cannot fully protect "
"your 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>" +
tr("IMPORTANT: Any previous backups you have made of your wallet file "
"should be replaced with the newly generated, encrypted wallet file. "
@ -152,9 +152,9 @@ void AskPassphraseDialog::accept()
} else {
QMessageBox::warning(this, tr("Wallet encrypted"),
"<qt>" +
tr("%1 will close now to finish the encryption process. "
tr("Your wallet is now encrypted. "
"Remember that encrypting your wallet cannot fully protect "
"your 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>" +
tr("IMPORTANT: Any previous backups you have made of your 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.") +
"</b></qt>");
}
QApplication::quit();
}
else
{

View File

@ -639,8 +639,19 @@ void BitcoinGUI::createToolBars()
#ifdef ENABLE_WALLET
m_wallet_selector = new QComboBox(this);
m_wallet_selector->setHidden(true);
connect(m_wallet_selector, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(setCurrentWallet(const QString&)));
connect(m_wallet_selector, SIGNAL(currentIndexChanged(int)), this, SLOT(setCurrentWalletBySelectorIndex(int)));
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
QLabel *logoLabel = new QLabel();
@ -769,25 +780,31 @@ bool BitcoinGUI::addWallet(WalletModel *walletModel)
if(!walletFrame)
return false;
const QString name = walletModel->getWalletName();
QString display_name = name.isEmpty() ? "["+tr("default wallet")+"]" : name;
setWalletActionsEnabled(true);
m_wallet_selector->addItem(name);
m_wallet_selector->addItem(display_name, name);
if (m_wallet_selector->count() == 2) {
m_wallet_selector->setHidden(false);
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);
m_wallet_selector_action->setVisible(true);
}
rpcConsole->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)
{
if(!walletFrame)
@ -795,6 +812,12 @@ bool BitcoinGUI::setCurrentWallet(const QString& 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()
{
if(!walletFrame)

View File

@ -76,6 +76,7 @@ public:
functionality.
*/
bool addWallet(WalletModel *walletModel);
bool removeWallet(WalletModel* walletModel);
void removeAllWallets();
#endif // ENABLE_WALLET
bool enableWallet;
@ -140,8 +141,8 @@ private:
QAction *openAction;
QAction *showHelpMessageAction;
QAction *showCoinJoinHelpAction;
QAction *m_wallet_selector_action = nullptr;
QLabel *m_wallet_selector_label;
QComboBox *m_wallet_selector;
QSystemTrayIcon *trayIcon;
@ -238,6 +239,7 @@ public Q_SLOTS:
#ifdef ENABLE_WALLET
bool setCurrentWallet(const QString& name);
bool setCurrentWalletBySelectorIndex(int index);
/** Set the UI status indicators based on the currently selected wallet.
*/
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.
void handleRunawayException(const QString &message);
void addWallet(WalletModel* walletModel);
void removeWallet();
Q_SIGNALS:
void requestedInitialize();
@ -454,11 +455,22 @@ void BitcoinApplication::addWallet(WalletModel* walletModel)
connect(walletModel, SIGNAL(coinsSent(WalletModel*, SendCoinsRecipient, QByteArray)),
paymentServer, SLOT(fetchPaymentACK(WalletModel*, const SendCoinsRecipient&, QByteArray)));
connect(walletModel, SIGNAL(unload()), this, SLOT(removeWallet()));
m_wallet_models.push_back(walletModel);
#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)
{
qDebug() << __func__ << ": Initialization result: " << success;
@ -478,8 +490,10 @@ void BitcoinApplication::initializeResult(bool success)
#ifdef ENABLE_WALLET
m_handler_load_wallet = m_node.handleLoadWallet([this](std::unique_ptr<interfaces::Wallet> wallet) {
QMetaObject::invokeMethod(this, "addWallet", Qt::QueuedConnection,
Q_ARG(WalletModel*, new WalletModel(std::move(wallet), m_node, optionsModel)));
WalletModel* wallet_model = new WalletModel(std::move(wallet), m_node, optionsModel, nullptr);
// 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()) {

View File

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

View File

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

View File

@ -743,7 +743,8 @@ void RPCConsole::addWallet(WalletModel * const walletModel)
{
const QString name = walletModel->getWalletName();
// 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()) {
// First wallet added, set to default so long as the window isn't presently visible (and potentially in use)
ui->WalletSelector->setCurrentIndex(1);
@ -753,6 +754,16 @@ void RPCConsole::addWallet(WalletModel * const walletModel)
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
static QString categoryClass(int category)

View File

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

View File

@ -119,7 +119,7 @@ void TestGUI()
for (int i = 0; i < 5; ++i) {
test.CreateAndProcessBlock({}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey()));
}
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("mock", WalletDatabase::CreateMock());
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(WalletLocation(), WalletDatabase::CreateMock());
AddWallet(wallet);
bool firstRun;
wallet->LoadWallet(firstRun);

View File

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

View File

@ -427,6 +427,12 @@ int64_t WalletModel::getKeysLeftSinceAutoBackup() const
}
// Handlers for core signals
static void NotifyUnload(WalletModel* walletModel)
{
qDebug() << "NotifyUnload";
QMetaObject::invokeMethod(walletModel, "unload", Qt::QueuedConnection);
}
static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel)
{
qDebug() << "NotifyKeyStoreStatusChanged";
@ -485,6 +491,7 @@ static void NotifyWatchonlyChanged(WalletModel *walletmodel, bool fHaveWatchonly
void WalletModel::subscribeToCoreSignals()
{
// 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_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));
@ -497,6 +504,7 @@ void WalletModel::subscribeToCoreSignals()
void WalletModel::unsubscribeFromCoreSignals()
{
// Disconnect signals from wallet
m_handler_unload->disconnect();
m_handler_status_changed->disconnect();
m_handler_address_book_changed->disconnect();
m_handler_transaction_changed->disconnect();

View File

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

View File

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

View File

@ -20,6 +20,7 @@
#include <boost/thread.hpp>
namespace {
//! Make sure database has a unique fileid within the environment. If it
//! doesn't, throw an error. BDB caches do not work properly when more than one
//! open database has the same fileid (values written to one database may show
@ -29,25 +30,19 @@ namespace {
//! (https://docs.oracle.com/cd/E17275_01/html/programmer_reference/program_copy.html),
//! so bitcoin should never create different databases with the same fileid, but
//! this error can be triggered if users manually copy database files.
void CheckUniqueFileid(const BerkeleyEnvironment& env, const std::string& filename, Db& db)
void CheckUniqueFileid(const BerkeleyEnvironment& env, const std::string& filename, Db& db, WalletDatabaseFileId& fileid)
{
if (env.IsMock()) return;
u_int8_t fileid[DB_FILE_ID_LEN];
int ret = db.get_mpf()->get_fileid(fileid);
int ret = db.get_mpf()->get_fileid(fileid.value);
if (ret != 0) {
throw std::runtime_error(strprintf("BerkeleyBatch: Can't open database %s (get_fileid failed with %d)", filename, ret));
}
for (const auto& item : env.mapDb) {
u_int8_t item_fileid[DB_FILE_ID_LEN];
if (item.second && item.second->get_mpf()->get_fileid(item_fileid) == 0 &&
memcmp(fileid, item_fileid, sizeof(fileid)) == 0) {
const char* item_filename = nullptr;
item.second->get_dbname(&item_filename, nullptr);
for (const auto& item : env.m_fileids) {
if (fileid == item.second && &fileid != &item.second) {
throw std::runtime_error(strprintf("BerkeleyBatch: Can't open database %s (duplicates fileid %s from %s)", filename,
HexStr(std::begin(item_fileid), std::end(item_fileid)),
item_filename ? item_filename : "(unknown database)"));
HexStr(std::begin(item.second.value), std::end(item.second.value)), item.first));
}
}
}
@ -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.
} // namespace
BerkeleyEnvironment* GetWalletEnv(const fs::path& wallet_path, std::string& database_filename)
bool WalletDatabaseFileId::operator==(const WalletDatabaseFileId& rhs) const
{
return memcmp(value, &rhs.value, sizeof(value)) == 0;
}
static void SplitWalletPath(const fs::path& wallet_path, fs::path& env_directory, std::string& database_filename)
{
fs::path env_directory;
if (fs::is_regular_file(wallet_path)) {
// Special case for backwards compatibility: if wallet path points to an
// existing file, treat it as the path to a BDB data file in a parent
@ -71,6 +70,23 @@ BerkeleyEnvironment* GetWalletEnv(const fs::path& wallet_path, std::string& data
env_directory = wallet_path;
database_filename = "wallet.dat";
}
}
bool IsWalletLoaded(const fs::path& wallet_path)
{
fs::path env_directory;
std::string database_filename;
SplitWalletPath(wallet_path, env_directory, database_filename);
LOCK(cs_db);
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);
// Note: An ununsed temporary BerkeleyEnvironment object may be created inside the
// emplace function if the key already exists. This is a little inefficient,
@ -90,13 +106,13 @@ void BerkeleyEnvironment::Close()
fDbEnvInit = false;
for (auto& db : mapDb) {
for (auto& db : m_databases) {
auto count = mapFileUseCount.find(db.first);
assert(count == mapFileUseCount.end() || count->second == 0);
if (db.second) {
db.second->close(0);
delete db.second;
db.second = nullptr;
BerkeleyDatabase& database = db.second.get();
if (database.m_db) {
database.m_db->close(0);
database.m_db.reset();
}
}
@ -463,7 +479,7 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo
if (!env->Open(false /* retry */))
throw std::runtime_error("BerkeleyBatch: Failed to open database environment.");
pdb = env->mapDb[strFilename];
pdb = database.m_db.get();
if (pdb == nullptr) {
int ret;
std::unique_ptr<Db> pdb_temp = MakeUnique<Db>(env->dbenv.get(), 0);
@ -504,11 +520,11 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo
// versions of BDB have an set_lk_exclusive method for this
// purpose, but the older version we use does not.)
for (auto& env : g_dbenvs) {
CheckUniqueFileid(env.second, strFilename, *pdb_temp);
CheckUniqueFileid(env.second, strFilename, *pdb_temp, this->env->m_fileids[strFilename]);
}
pdb = pdb_temp.release();
env->mapDb[strFilename] = pdb;
database.m_db.reset(pdb);
if (fCreate && !Exists(std::string("version"))) {
bool fTmp = fReadOnly;
@ -556,22 +572,50 @@ void BerkeleyBatch::Close()
LOCK(cs_db);
--env->mapFileUseCount[strFile];
}
env->m_db_in_use.notify_all();
}
void BerkeleyEnvironment::CloseDb(const std::string& strFile)
{
{
LOCK(cs_db);
if (mapDb[strFile] != nullptr) {
auto it = m_databases.find(strFile);
assert(it != m_databases.end());
BerkeleyDatabase& database = it->second.get();
if (database.m_db) {
// Close the database handle
Db* pdb = mapDb[strFile];
pdb->close(0);
delete pdb;
mapDb[strFile] = nullptr;
database.m_db->close(0);
database.m_db.reset();
}
}
}
void BerkeleyEnvironment::ReloadDbEnv()
{
// Make sure that no Db's are in use
AssertLockNotHeld(cs_db);
std::unique_lock<CCriticalSection> lock(cs_db);
m_db_in_use.wait(lock, [this](){
for (auto& count : mapFileUseCount) {
if (count.second > 0) return false;
}
return true;
});
std::vector<std::string> filenames;
for (auto it : m_databases) {
filenames.push_back(it.first);
}
// Close the individual Db's
for (const std::string& filename : filenames) {
CloseDb(filename);
}
// Reset the environment
Flush(true); // This will flush and close the environment
Reset();
Open(true);
}
bool BerkeleyBatch::Rewrite(BerkeleyDatabase& database, const char* pszSkip)
{
if (database.IsDummy()) {
@ -694,8 +738,9 @@ void BerkeleyEnvironment::Flush(bool fShutdown)
if (mapFileUseCount.empty()) {
dbenv->log_archive(&listp, DB_ARCH_REMOVE);
Close();
if (!fMockDb)
if (!fMockDb) {
fs::remove_all(fs::path(strPath) / "database");
}
}
}
}
@ -794,5 +839,24 @@ void BerkeleyDatabase::Flush(bool shutdown)
{
if (!IsDummy()) {
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 <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include <db_cxx.h>
@ -25,6 +26,13 @@
static const unsigned int DEFAULT_WALLET_DBLOGSIZE = 100;
static const bool DEFAULT_WALLET_PRIVDB = true;
struct WalletDatabaseFileId {
u_int8_t value[DB_FILE_ID_LEN];
bool operator==(const WalletDatabaseFileId& rhs) const;
};
class BerkeleyDatabase;
class BerkeleyEnvironment
{
private:
@ -37,7 +45,9 @@ private:
public:
std::unique_ptr<DbEnv> dbenv;
std::map<std::string, int> mapFileUseCount;
std::map<std::string, Db*> mapDb;
std::map<std::string, std::reference_wrapper<BerkeleyDatabase>> m_databases;
std::unordered_map<std::string, WalletDatabaseFileId> m_fileids;
std::condition_variable_any m_db_in_use;
BerkeleyEnvironment(const fs::path& env_directory);
~BerkeleyEnvironment();
@ -46,6 +56,7 @@ public:
void MakeMock();
bool IsMock() const { return fMockDb; }
bool IsInitialized() const { return fDbEnvInit; }
bool IsDatabaseLoaded(const std::string& db_filename) const { return m_databases.find(db_filename) != m_databases.end(); }
fs::path Directory() const { return strPath; }
/**
@ -75,6 +86,7 @@ public:
void CheckpointLSN(const std::string& strFile);
void CloseDb(const std::string& strFile);
void ReloadDbEnv();
DbTxn* TxnBegin(int flags = DB_TXN_WRITE_NOSYNC)
{
@ -86,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. */
BerkeleyEnvironment* GetWalletEnv(const fs::path& wallet_path, std::string& database_filename);
@ -106,6 +121,8 @@ public:
nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0)
{
env = GetWalletEnv(wallet_path, strFile);
auto inserted = env->m_databases.emplace(strFile, std::ref(*this));
assert(inserted.second);
if (mock) {
env->Close();
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. */
static std::unique_ptr<BerkeleyDatabase> Create(const fs::path& path)
{
@ -145,11 +169,16 @@ public:
void IncrementUpdateCounter();
void ReloadDbEnv();
std::atomic<unsigned int> nUpdateCounter;
unsigned int nLastSeen;
unsigned int nLastFlushed;
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:
/** BerkeleyDB specific */
BerkeleyEnvironment *env;

View File

@ -375,15 +375,15 @@ bool WalletInit::Verify() const
std::set<fs::path> wallet_paths;
for (const auto& wallet_file : wallet_files) {
fs::path wallet_path = fs::absolute(wallet_file, GetWalletDir());
WalletLocation location(wallet_file);
if (!wallet_paths.insert(wallet_path).second) {
if (!wallet_paths.insert(location.GetPath()).second) {
return InitError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), wallet_file));
}
std::string error_string;
std::string warning_string;
bool verify_success = CWallet::Verify(wallet_file, salvage_wallet, error_string, warning_string);
bool verify_success = CWallet::Verify(location, salvage_wallet, error_string, warning_string);
if (!error_string.empty()) InitError(error_string);
if (!warning_string.empty()) InitWarning(warning_string);
if (!verify_success) return false;
@ -400,7 +400,7 @@ bool WalletInit::Open() const
}
for (const std::string& walletFile : gArgs.GetArgs("-wallet")) {
std::shared_ptr<CWallet> pwallet = CWallet::CreateWalletFromFile(walletFile, fs::absolute(walletFile, GetWalletDir()));
std::shared_ptr<CWallet> pwallet = CWallet::CreateWalletFromFile(WalletLocation(walletFile));
if (!pwallet) {
return false;
}

View File

@ -43,12 +43,21 @@
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) {
// wallet endpoint was used
std::string requestedWallet = urlDecode(request.URI.substr(WALLET_ENDPOINT_BASE.size()));
std::shared_ptr<CWallet> pwallet = GetWallet(requestedWallet);
wallet_name = urlDecode(request.URI.substr(WALLET_ENDPOINT_BASE.size()));
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");
return pwallet;
}
@ -69,11 +78,6 @@ bool EnsureWalletIsAvailable(CWallet * const pwallet, bool avoidException)
if (pwallet) return true;
if (avoidException) return false;
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(
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"
"Use the walletpassphrase call for this, and then walletlock call.\n"
"If the wallet is already encrypted, use the walletpassphrasechange call.\n"
"Note that this will shutdown the server.\n"
"\nArguments:\n"
"1. \"passphrase\" (string) The pass phrase to encrypt the wallet with. It must be at least 1 character, but should be long.\n"
"\nExamples:\n"
@ -2714,11 +2717,7 @@ UniValue encryptwallet(const JSONRPCRequest& request)
throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Failed to encrypt the wallet.");
}
// BDB seems to have a bad habit of writing old data into
// slack space in .dat files; that is bad if the old data is
// unencrypted private keys. So:
StartShutdown();
return "Wallet encrypted; 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.";
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.";
}
UniValue lockunspent(const JSONRPCRequest& request)
@ -3227,14 +3226,6 @@ UniValue upgradetohd(const JSONRPCRequest& request)
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;
}
@ -3293,7 +3284,7 @@ UniValue keepass(const JSONRPCRequest& request)
return "Invalid command";
}
UniValue loadwallet(const JSONRPCRequest& request)
static UniValue loadwallet(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 1)
throw std::runtime_error(
@ -3312,26 +3303,26 @@ UniValue loadwallet(const JSONRPCRequest& request)
+ HelpExampleCli("loadwallet", "\"test.dat\"")
+ HelpExampleRpc("loadwallet", "\"test.dat\"")
);
std::string wallet_file = request.params[0].get_str();
WalletLocation location(request.params[0].get_str());
std::string error;
fs::path wallet_path = fs::absolute(wallet_file, GetWalletDir());
if (fs::symlink_status(wallet_path).type() == fs::file_not_found) {
throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Wallet " + wallet_file + " not found.");
} else if (fs::is_directory(wallet_path)) {
if (!location.Exists()) {
throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Wallet " + location.GetName() + " not found.");
} else if (fs::is_directory(location.GetPath())) {
// The given filename is a directory. Check that there's a wallet.dat file.
fs::path wallet_dat_file = wallet_path / "wallet.dat";
fs::path wallet_dat_file = location.GetPath() / "wallet.dat";
if (fs::symlink_status(wallet_dat_file).type() == fs::file_not_found) {
throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Directory " + wallet_file + " does not contain a wallet.dat file.");
throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Directory " + location.GetName() + " does not contain a wallet.dat file.");
}
}
std::string warning;
if (!CWallet::Verify(wallet_file, false, error, warning)) {
if (!CWallet::Verify(location, false, error, warning)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error);
}
std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(wallet_file, fs::absolute(wallet_file, GetWalletDir()));
std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(location);
if (!wallet) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet loading failed.");
}
@ -3345,7 +3336,7 @@ UniValue loadwallet(const JSONRPCRequest& request)
return obj;
}
UniValue createwallet(const JSONRPCRequest& request)
static UniValue createwallet(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 1) {
throw std::runtime_error(
@ -3363,21 +3354,20 @@ UniValue createwallet(const JSONRPCRequest& request)
+ HelpExampleRpc("createwallet", "\"testwallet\"")
);
}
std::string wallet_name = request.params[0].get_str();
std::string error;
std::string warning;
fs::path wallet_path = fs::absolute(wallet_name, GetWalletDir());
if (fs::symlink_status(wallet_path).type() != fs::file_not_found) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet " + wallet_name + " already exists.");
WalletLocation location(request.params[0].get_str());
if (location.Exists()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet " + location.GetName() + " already exists.");
}
// Wallet::Verify will check if we're trying to create a wallet with a duplication name.
if (!CWallet::Verify(wallet_name, false, error, warning)) {
if (!CWallet::Verify(location, false, error, warning)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error);
}
const auto wallet = CWallet::CreateWalletFromFile(wallet_name, fs::absolute(wallet_name, GetWalletDir()));
const auto wallet = CWallet::CreateWalletFromFile(location);
if (!wallet) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed.");
}
@ -3392,6 +3382,55 @@ UniValue createwallet(const JSONRPCRequest& request)
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)
{
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
@ -4356,6 +4395,7 @@ static const CRPCCommand commands[] =
{ "wallet", "setcoinjoinamount", &setcoinjoinamount, {"amount"} },
{ "wallet", "signmessage", &signmessage, {"address","message"} },
{ "wallet", "signrawtransactionwithwallet", &signrawtransactionwithwallet, {"hexstring","prevtxs","sighashtype"} },
{ "wallet", "unloadwallet", &unloadwallet, {"wallet_name"} },
{ "wallet", "upgradetohd", &upgradetohd, {"mnemonic", "mnemonicpassphrase", "walletpassphrase"} },
{ "wallet", "walletlock", &walletlock, {} },
{ "wallet", "walletpassphrasechange", &walletpassphrasechange, {"oldpassphrase","newpassphrase"} },

View File

@ -36,7 +36,7 @@ public:
CTransactionBuilderTestSetup()
{
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
wallet = MakeUnique<CWallet>("mock", WalletDatabase::CreateMock());
wallet = MakeUnique<CWallet>(WalletLocation(), WalletDatabase::CreateMock());
bool firstRun;
wallet->LoadWallet(firstRun);
AddWallet(wallet);

View File

@ -27,7 +27,7 @@ std::vector<std::unique_ptr<CWalletTx>> wtxn;
typedef std::set<CInputCoin> CoinSet;
static std::vector<COutput> vCoins;
static const CWallet testWallet("dummy", WalletDatabase::CreateDummy());
static const CWallet testWallet(WalletLocation(), WalletDatabase::CreateDummy());
static CAmount balance = 0;
CoinEligibilityFilter filter_standard(1, 6, 0);

View File

@ -6,9 +6,10 @@
#include <rpc/server.h>
#include <wallet/db.h>
#include <wallet/rpcwallet.h>
WalletTestingSetup::WalletTestingSetup(const std::string& chainName):
TestingSetup(chainName), m_wallet("mock", WalletDatabase::CreateMock())
TestingSetup(chainName), m_wallet(WalletLocation(), WalletDatabase::CreateMock())
{
bool fFirstRun;
m_wallet.LoadWallet(fFirstRun);

View File

@ -49,7 +49,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
// Verify ScanForWalletTransactions picks up transactions in both the old
// and new block files.
{
CWallet wallet("dummy", WalletDatabase::CreateDummy());
CWallet wallet(WalletLocation(), WalletDatabase::CreateDummy());
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet);
reserver.reserve();
@ -64,7 +64,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
// Verify ScanForWalletTransactions only picks transactions in the new block
// file.
{
CWallet wallet("dummy", WalletDatabase::CreateDummy());
CWallet wallet(WalletLocation(), WalletDatabase::CreateDummy());
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet);
reserver.reserve();
@ -76,7 +76,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
// before the missing block, and success for a key whose creation time is
// after.
{
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("dummy", WalletDatabase::CreateDummy());
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(WalletLocation(), WalletDatabase::CreateDummy());
AddWallet(wallet);
UniValue keys;
keys.setArray();
@ -137,7 +137,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
// Import key into wallet and call dumpwallet to create backup file.
{
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("dummy", WalletDatabase::CreateDummy());
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(WalletLocation(), WalletDatabase::CreateDummy());
LOCK(wallet->cs_wallet);
wallet->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME;
wallet->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
@ -153,7 +153,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
// Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME
// were scanned, and no prior blocks were scanned.
{
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("dummy", WalletDatabase::CreateDummy());
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(WalletLocation(), WalletDatabase::CreateDummy());
JSONRPCRequest request;
request.params.setArray();
@ -183,7 +183,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
// debit functions.
BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup)
{
CWallet wallet("dummy", WalletDatabase::CreateDummy());
CWallet wallet(WalletLocation(), WalletDatabase::CreateDummy());
CWalletTx wtx(&wallet, MakeTransactionRef(coinbaseTxns.back()));
LOCK2(cs_main, wallet.cs_wallet);
wtx.hashBlock = chainActive.Tip()->GetBlockHash();
@ -276,7 +276,7 @@ public:
ListCoinsTestingSetup()
{
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
wallet = MakeUnique<CWallet>("mock", WalletDatabase::CreateMock());
wallet = MakeUnique<CWallet>(WalletLocation(), WalletDatabase::CreateMock());
bool firstRun;
wallet->LoadWallet(firstRun);
AddKey(*wallet, coinbaseKey);
@ -395,7 +395,7 @@ public:
CreateTransactionTestSetup()
{
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
wallet = MakeUnique<CWallet>("mock", WalletDatabase::CreateMock());
wallet = MakeUnique<CWallet>(WalletLocation(), WalletDatabase::CreateMock());
bool firstRun;
wallet->LoadWallet(firstRun);
AddWallet(wallet);

View File

@ -28,7 +28,6 @@
#include <txmempool.h>
#include <utilmoneystr.h>
#include <wallet/fees.h>
#include <wallet/walletutil.h>
#include <coinjoin/coinjoin-client.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);
// 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"));
/** @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);
@ -1551,7 +1564,7 @@ void CWallet::BlockUntilSyncedToCurrentChain() {
LOCK(cs_main);
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;
}
}
@ -4913,7 +4926,7 @@ std::vector<std::string> CWallet::GetDestValues(const std::string& prefix) const
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:
//
@ -4922,25 +4935,23 @@ bool CWallet::Verify(std::string wallet_file, bool salvage_wallet, std::string&
// 3. Path to a symlink to a directory.
// 4. For backwards compatibility, the name of a data file in -walletdir.
LOCK(cs_wallets);
fs::path wallet_path = fs::absolute(wallet_file, GetWalletDir());
const fs::path& wallet_path = location.GetPath();
fs::file_type path_type = fs::symlink_status(wallet_path).type();
if (!(path_type == fs::file_not_found || path_type == fs::directory_file ||
(path_type == fs::symlink_file && fs::is_directory(wallet_path)) ||
(path_type == fs::regular_file && fs::path(wallet_file).filename() == wallet_file))) {
(path_type == fs::regular_file && fs::path(location.GetName()).filename() == location.GetName()))) {
error_string =strprintf(
"Invalid -wallet path '%s'. -wallet path should point to a directory where wallet.dat and "
"database/log.?????????? files can be stored, a location where such a directory could be created, "
"or (for backwards compatibility) the name of an existing data file in -walletdir (%s)",
wallet_file, GetWalletDir());
location.GetName(), GetWalletDir());
return false;
}
// Make sure that the wallet path doesn't clash with an existing wallet path
for (auto wallet : GetWallets()) {
if (fs::absolute(wallet->GetName(), GetWalletDir()) == wallet_path) {
error_string = strprintf("Error loading wallet %s. Duplicate -wallet filename specified.", wallet_file);
return false;
}
if (IsWalletLoaded(wallet_path)) {
error_string = strprintf("Error loading wallet %s. Duplicate -wallet filename specified.", location.GetName());
return false;
}
try {
@ -4948,18 +4959,18 @@ bool CWallet::Verify(std::string wallet_file, bool salvage_wallet, std::string&
return false;
}
} catch (const fs::filesystem_error& e) {
error_string = strprintf("Error loading wallet %s. %s", wallet_file, e.what());
error_string = strprintf("Error loading wallet %s. %s", location.GetName(), e.what());
return false;
}
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()) {
return false;
}
if (salvage_wallet) {
// Recover readable keypairs:
CWallet dummyWallet("dummy", WalletDatabase::CreateDummy());
CWallet dummyWallet(WalletLocation(), WalletDatabase::CreateDummy());
std::string backup_filename;
if (!WalletBatch::Recover(wallet_path, (void *)&dummyWallet, WalletBatch::RecoverKeysOnlyFilter, backup_filename)) {
return false;
@ -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);
}
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
std::vector<CWalletTx> vWtx;
@ -4979,7 +4990,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name,
if (gArgs.GetBoolArg("-zapwallettxes", false)) {
uiInterface.InitMessage(_("Zapping all transactions from wallet..."));
std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(name, WalletDatabase::Create(path));
std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(location, WalletDatabase::Create(location.GetPath()));
DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx);
if (nZapWalletRet != DBErrors::LOAD_OK) {
InitError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile));
@ -4991,7 +5002,9 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name,
int64_t nStart = GetTimeMillis();
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);
auto error = [&](const std::string& strError) {
RemoveWallet(walletInstance);
@ -5028,8 +5041,6 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name,
}
}
uiInterface.LoadWallet(walletInstance);
if (gArgs.GetBoolArg("-upgradewallet", fFirstRun))
{
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.
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)
{
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) {
LogPrintf("Automatic wallet backups are disabled!\n");

View File

@ -21,7 +21,7 @@
#include <wallet/crypter.h>
#include <wallet/coinselection.h>
#include <wallet/walletdb.h>
#include <wallet/rpcwallet.h>
#include <wallet/walletutil.h>
#include <coinjoin/coinjoin.h>
#include <governance/governance-object.h>
@ -789,12 +789,8 @@ private:
*/
bool AddWatchOnly(const CScript& dest) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/**
* Wallet filename from wallet=<path> command line or config option.
* Used in debug logs and to send RPCs to the right wallet instance when
* more than one wallet is loaded.
*/
std::string m_name;
/** Wallet location which includes wallet name (see WalletLocation). */
WalletLocation m_location;
/** Internal database handle. */
std::unique_ptr<WalletDatabase> database;
@ -844,9 +840,11 @@ public:
return *database;
}
const WalletLocation& GetLocation() const { return m_location; }
/** Get a name for this wallet for logging/debugging purposes.
*/
const std::string& GetName() const { return m_name; }
const std::string& GetName() const { return m_location.GetName(); }
void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
@ -864,7 +862,7 @@ public:
unsigned int nMasterKeyMaxID = 0;
/** Construct wallet with specified name and database implementation. */
CWallet(std::string name, std::unique_ptr<WalletDatabase> database) : m_name(std::move(name)), database(std::move(database))
CWallet(const WalletLocation& location, std::unique_ptr<WalletDatabase> database) : m_location(location), database(std::move(database))
{
}
@ -1164,6 +1162,9 @@ public:
//! Flush wallet (bitdb flush)
void Flush(bool shutdown=false);
/** Wallet is about to be unloaded */
boost::signals2::signal<void ()> NotifyUnload;
/**
* Address book entry changed.
* @note called with lock cs_wallet held.
@ -1204,10 +1205,10 @@ public:
bool AbandonTransaction(const uint256& hashTx);
//! Verify wallet naming and perform salvage on the wallet if required
static bool Verify(std::string wallet_file, bool salvage_wallet, std::string& error_string, std::string& warning_string);
static bool Verify(const WalletLocation& location, bool salvage_wallet, std::string& error_string, std::string& warning_string);
/* Initializes the wallet, returns a new CWallet instance or a null pointer in case of an error */
static std::shared_ptr<CWallet> CreateWalletFromFile(const std::string& name, const fs::path& path);
static std::shared_ptr<CWallet> CreateWalletFromFile(const WalletLocation& location);
/**
* Wallet post-init setup

View File

@ -25,3 +25,14 @@ fs::path GetWalletDir()
return path;
}
WalletLocation::WalletLocation(const std::string& name)
: m_name(name)
, m_path(fs::absolute(name, GetWalletDir()))
{
}
bool WalletLocation::Exists() const
{
return fs::symlink_status(m_path).type() != fs::file_not_found;
}

View File

@ -11,4 +11,24 @@
//! Get the path of the wallet directory.
fs::path GetWalletDir();
//! The WalletLocation class provides wallet information.
class WalletLocation final
{
std::string m_name;
fs::path m_path;
public:
explicit WalletLocation() {}
explicit WalletLocation(const std::string& name);
//! Get wallet name.
const std::string& GetName() const { return m_name; }
//! Get wallet absolute path.
const fs::path& GetPath() const { return m_path; }
//! Return whether the wallet exists.
bool Exists() const;
};
#endif // BITCOIN_WALLET_WALLETUTIL_H

View File

@ -463,10 +463,8 @@ class RawTransactionsTest(BitcoinTestFramework):
############################################################
# locked wallet test
self.stop_node(0)
self.nodes[1].node_encrypt_wallet("test")
self.stop_node(2)
self.stop_node(3)
self.nodes[1].encryptwallet("test")
self.stop_nodes()
self.start_nodes()
# This test is not meant to test fee estimation and we'd like

View File

@ -439,10 +439,8 @@ class RawTransactionsTest(BitcoinTestFramework):
############################################################
# locked wallet test
self.stop_node(0)
self.stop_node(2)
self.stop_node(3)
self.nodes[1].node_encrypt_wallet("test")
self.nodes[1].encryptwallet("test")
self.stop_nodes()
self.start_nodes()
# 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
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):
"""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)
#encrypt wallet, restart, unlock and dump
self.nodes[0].node_encrypt_wallet('test')
self.start_node(0)
self.nodes[0].encryptwallet('test')
self.nodes[0].walletpassphrase('test', 30)
# Should be a no-op:
self.nodes[0].keypoolrefill()

View File

@ -30,8 +30,7 @@ class WalletEncryptionTest(BitcoinTestFramework):
assert_equal(len(privkey), 52)
# Encrypt the wallet
self.nodes[0].node_encrypt_wallet(passphrase)
self.start_node(0)
self.nodes[0].encryptwallet(passphrase)
# Test that the wallet is encrypted
assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first", self.nodes[0].dumpprivkey, address)

View File

@ -16,9 +16,7 @@ class KeyPoolTest(BitcoinTestFramework):
nodes = self.nodes
# Encrypt wallet and wait to terminate
nodes[0].node_encrypt_wallet('test')
# Restart node 0
self.start_node(0)
nodes[0].encryptwallet('test')
# Keep creating keys
addr = nodes[0].getnewaddress()

View File

@ -28,9 +28,7 @@ class KeyPoolTest(BitcoinTestFramework):
assert(addr_before_encrypting_data['hdchainid'] == wallet_info_old['hdchainid'])
# Encrypt wallet and wait to terminate
nodes[0].node_encrypt_wallet('test')
# Restart node 0
self.start_node(0)
nodes[0].encryptwallet('test')
# Keep creating keys
addr = nodes[0].getnewaddress()
addr_data = nodes[0].getaddressinfo(addr)

View File

@ -199,9 +199,16 @@ class MultiWalletTest(BitcoinTestFramework):
# Fail to load duplicate wallets
assert_raises_rpc_error(-4, 'Wallet file verification failed: Error loading wallet w1. Duplicate -wallet filename specified.', self.nodes[0].loadwallet, wallet_names[0])
# Fail to load duplicate wallets by different ways (directory and filepath)
assert_raises_rpc_error(-4, "Wallet file verification failed: Error loading wallet wallet.dat. Duplicate -wallet filename specified.", self.nodes[0].loadwallet, 'wallet.dat')
# Fail to load if one wallet is a copy of another
assert_raises_rpc_error(-1, "BerkeleyBatch: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
# Fail to load if one wallet is a copy of another, test this twice to make sure that we don't re-introduce #14304
assert_raises_rpc_error(-1, "BerkeleyBatch: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
# Fail to load if wallet file is a symlink
assert_raises_rpc_error(-4, "Wallet file verification failed: Invalid -wallet path 'w8_symlink'", self.nodes[0].loadwallet, 'w8_symlink')
@ -232,5 +239,32 @@ class MultiWalletTest(BitcoinTestFramework):
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')
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__':
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")
walletpass = "111pass222"
# 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.")
# Wallet encryption results in node shutdown
assert node.upgradetohd(mnemonic, "", walletpass)
node.stop()
node.wait_until_stopped()
self.start_node(0, extra_args=['-rescan'])
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")
walletpass = "111pass222"
node.encryptwallet(walletpass)
# Wallet encryption results in node shutdown
node.stop()
node.wait_until_stopped()
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, "The wallet passphrase entered was incorrect", node.upgradetohd, mnemonic, "", "wrongpass")
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)
node.walletpassphrase(walletpass, 100)
assert_equal(mnemonic, node.dumphdinfo()['mnemonic'])

View File

@ -28,7 +28,6 @@ EXPECTED_CIRCULAR_DEPENDENCIES=(
"txmempool -> validation -> txmempool"
"validation -> validationinterface -> validation"
"wallet/fees -> wallet/wallet -> wallet/fees"
"wallet/rpcwallet -> wallet/wallet -> wallet/rpcwallet"
"wallet/wallet -> wallet/walletdb -> wallet/wallet"
"policy/fees -> policy/policy -> validation -> policy/fees"
"qt/addressbookpage -> qt/bitcoingui -> qt/walletview -> qt/addressbookpage"