diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp index d207e49d07..3e52120005 100644 --- a/src/dummywallet.cpp +++ b/src/dummywallet.cpp @@ -10,6 +10,10 @@ class CWallet; +namespace interfaces { +class Chain; +} + class DummyWalletInit : public WalletInitInterface { public: @@ -89,6 +93,11 @@ std::vector> GetWallets() throw std::logic_error("Wallet function called in non-wallet build."); } +std::shared_ptr LoadWallet(interfaces::Chain& chain, const std::string& name, std::string& error, std::string& warning) +{ + throw std::logic_error("Wallet function called in non-wallet build."); +} + namespace interfaces { class Wallet; diff --git a/src/interfaces/node.cpp b/src/interfaces/node.cpp index d5fb759424..0064731861 100644 --- a/src/interfaces/node.cpp +++ b/src/interfaces/node.cpp @@ -49,6 +49,7 @@ class CWallet; fs::path GetWalletDir(); std::vector ListWalletDir(); std::vector> GetWallets(); +std::shared_ptr LoadWallet(interfaces::Chain& chain, const std::string& name, std::string& error, std::string& warning); namespace interfaces { @@ -378,6 +379,11 @@ public: } return wallets; } + std::unique_ptr loadWallet(const std::string& name, std::string& error, std::string& warning) override + { + return MakeWallet(LoadWallet(*m_interfaces.chain, name, error, warning)); + } + EVO& evo() override { return m_evo; } LLMQ& llmq() override { return m_llmq; } Masternode::Sync& masternodeSync() override { return m_masternodeSync; } diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 40f68e3052..c21c2d821f 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -253,6 +253,11 @@ public: //! Return interfaces for accessing wallets (if any). virtual std::vector> getWallets() = 0; + //! Attempts to load a wallet from file or directory. + //! The loaded wallet is also notified to handlers previously registered + //! with handleLoadWallet. + virtual std::unique_ptr loadWallet(const std::string& name, std::string& error, std::string& warning) = 0; + //! Return interface for accessing evo related handler. virtual EVO& evo() = 0; diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index 7841757740..622485aed9 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -544,6 +544,10 @@ public: bool hdEnabled() override { return m_wallet->IsHDEnabled(); } bool IsWalletFlagSet(uint64_t flag) override { return m_wallet->IsWalletFlagSet(flag); } CoinJoin::Client& coinJoin() override { return m_coinjoin; } + void remove() override + { + RemoveWallet(m_wallet); + } std::unique_ptr handleUnload(UnloadFn fn) override { return MakeHandler(m_wallet->NotifyUnload.connect(fn)); @@ -610,7 +614,7 @@ public: } // namespace -std::unique_ptr MakeWallet(const std::shared_ptr& wallet) { return MakeUnique(wallet); } +std::unique_ptr MakeWallet(const std::shared_ptr& wallet) { return wallet ? MakeUnique(wallet) : nullptr; } std::unique_ptr MakeWalletClient(Chain& chain, std::vector wallet_filenames) { diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index 7c88fd4ce4..6eff7991c1 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -266,6 +266,9 @@ public: virtual CoinJoin::Client& coinJoin() = 0; + // Remove wallet. + virtual void remove() = 0; + //! Register handler for unload message. using UnloadFn = std::function; virtual std::unique_ptr handleUnload(UnloadFn fn) = 0; diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index bb982547a3..a16b1cff81 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -427,6 +427,13 @@ void BitcoinGUI::createActions() openAction = new QAction(tr("Open &URI..."), this); openAction->setStatusTip(tr("Open a dash: URI or payment request")); + m_open_wallet_action = new QAction(tr("Open Wallet"), this); + m_open_wallet_action->setMenu(new QMenu(this)); + m_open_wallet_action->setStatusTip(tr("Open a wallet")); + + m_close_wallet_action = new QAction(tr("Close Wallet..."), this); + m_close_wallet_action->setStatusTip(tr("Close wallet")); + showHelpMessageAction = new QAction(tr("&Command-line options"), this); showHelpMessageAction->setMenuRole(QAction::NoRole); showHelpMessageAction->setStatusTip(tr("Show the %1 help message to get a list with possible Dash command-line options").arg(tr(PACKAGE_NAME))); @@ -475,6 +482,40 @@ void BitcoinGUI::createActions() connect(usedSendingAddressesAction, &QAction::triggered, walletFrame, &WalletFrame::usedSendingAddresses); connect(usedReceivingAddressesAction, &QAction::triggered, walletFrame, &WalletFrame::usedReceivingAddresses); connect(openAction, &QAction::triggered, this, &BitcoinGUI::openClicked); + connect(m_open_wallet_action->menu(), &QMenu::aboutToShow, [this] { + m_open_wallet_action->menu()->clear(); + for (std::string path : m_wallet_controller->getWalletsAvailableToOpen()) { + QString name = path.empty() ? QString("["+tr("default wallet")+"]") : QString::fromStdString(path); + QAction* action = m_open_wallet_action->menu()->addAction(name); + connect(action, &QAction::triggered, [this, name, path] { + OpenWalletActivity* activity = m_wallet_controller->openWallet(path); + + QProgressDialog* dialog = new QProgressDialog(this); + dialog->setLabelText(tr("Opening Wallet %1...").arg(name.toHtmlEscaped())); + dialog->setRange(0, 0); + dialog->setCancelButton(nullptr); + dialog->setWindowModality(Qt::ApplicationModal); + dialog->show(); + + connect(activity, &OpenWalletActivity::message, this, [this] (QMessageBox::Icon icon, QString text) { + QMessageBox box; + box.setIcon(icon); + box.setText(tr("Open Wallet Failed")); + box.setInformativeText(text); + box.setStandardButtons(QMessageBox::Ok); + box.setDefaultButton(QMessageBox::Ok); + connect(this, &QObject::destroyed, &box, &QDialog::accept); + box.exec(); + }); + connect(activity, &OpenWalletActivity::opened, this, &BitcoinGUI::setCurrentWallet); + connect(activity, &OpenWalletActivity::finished, activity, &QObject::deleteLater); + connect(activity, &OpenWalletActivity::finished, dialog, &QObject::deleteLater); + }); + } + }); + connect(m_close_wallet_action, &QAction::triggered, [this] { + m_wallet_controller->closeWallet(walletFrame->currentWalletModel(), this); + }); } #endif // ENABLE_WALLET @@ -499,6 +540,9 @@ void BitcoinGUI::createMenuBar() QMenu *file = appMenuBar->addMenu(tr("&File")); if(walletFrame) { + file->addAction(m_open_wallet_action); + file->addAction(m_close_wallet_action); + file->addSeparator(); file->addAction(openAction); file->addAction(backupWalletAction); file->addAction(signMessageAction); @@ -891,6 +935,7 @@ void BitcoinGUI::setWalletActionsEnabled(bool enabled) usedSendingAddressesAction->setEnabled(enabled); usedReceivingAddressesAction->setEnabled(enabled); openAction->setEnabled(enabled); + m_close_wallet_action->setEnabled(enabled); } void BitcoinGUI::createTrayIcon() diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index e946f58b44..47f58dc17f 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -159,6 +159,8 @@ private: QAction* showBackupsAction = nullptr; QAction* openAction = nullptr; QAction* showHelpMessageAction = nullptr; + QAction* m_open_wallet_action{nullptr}; + QAction* m_close_wallet_action{nullptr}; QAction* showCoinJoinHelpAction = nullptr; QAction* m_wallet_selector_action = nullptr; diff --git a/src/qt/dash.cpp b/src/qt/dash.cpp index 01471ea04e..24603e1c46 100644 --- a/src/qt/dash.cpp +++ b/src/qt/dash.cpp @@ -479,7 +479,7 @@ int GuiMain(int argc, char* argv[]) // IMPORTANT if it is no longer a typedef use the normal variant above qRegisterMetaType< CAmount >("CAmount"); qRegisterMetaType< std::function >("std::function"); - + qRegisterMetaType("QMessageBox::Icon"); /// 2. Parse command-line options. We do this after qt in order to show an error if there are problems parsing these // Command-line options take precedence: node->setupServerArgs(); diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index bcbb967df5..f389ec9826 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -26,11 +27,17 @@ WalletController::WalletController(interfaces::Node& node, OptionsModel* options for (std::unique_ptr& wallet : m_node.getWallets()) { getOrCreateWallet(std::move(wallet)); } + + m_activity_thread.start(); } // Not using the default destructor because not all member types definitions are // available in the header, just forward declared. -WalletController::~WalletController() {} +WalletController::~WalletController() +{ + m_activity_thread.quit(); + m_activity_thread.wait(); +} std::vector WalletController::getWallets() const { @@ -38,6 +45,41 @@ std::vector WalletController::getWallets() const return m_wallets; } +std::vector WalletController::getWalletsAvailableToOpen() const +{ + QMutexLocker locker(&m_mutex); + std::vector wallets = m_node.listWalletDir(); + for (WalletModel* wallet_model : m_wallets) { + auto it = std::remove(wallets.begin(), wallets.end(), wallet_model->wallet().getWalletName()); + if (it != wallets.end()) wallets.erase(it); + } + return wallets; +} + +OpenWalletActivity* WalletController::openWallet(const std::string& name, QWidget* parent) +{ + OpenWalletActivity* activity = new OpenWalletActivity(this, name); + activity->moveToThread(&m_activity_thread); + QMetaObject::invokeMethod(activity, "open", Qt::QueuedConnection); + return activity; +} + +void WalletController::closeWallet(WalletModel* wallet_model, QWidget* parent) +{ + QMessageBox box(parent); + box.setWindowTitle(tr("Close wallet")); + box.setText(tr("Are you sure you wish to close wallet %1?").arg(wallet_model->getDisplayName())); + box.setInformativeText(tr("Closing the wallet for too long can result in having to resync the entire chain if pruning is enabled.")); + box.setStandardButtons(QMessageBox::Yes|QMessageBox::Cancel); + box.setDefaultButton(QMessageBox::Yes); + if (box.exec() != QMessageBox::Yes) return; + + // First remove wallet from node. + wallet_model->wallet().remove(); + // Now release the model. + removeAndDeleteWallet(wallet_model); +} + WalletModel* WalletController::getOrCreateWallet(std::unique_ptr wallet) { QMutexLocker locker(&m_mutex); @@ -111,3 +153,24 @@ void WalletController::removeAndDeleteWallet(WalletModel* wallet_model) // CWallet shared pointer. delete wallet_model; } + + +OpenWalletActivity::OpenWalletActivity(WalletController* wallet_controller, const std::string& name) + : m_wallet_controller(wallet_controller) + , m_name(name) +{} + +void OpenWalletActivity::open() +{ + std::string error, warning; + std::unique_ptr wallet = m_wallet_controller->m_node.loadWallet(m_name, error, warning); + if (!warning.empty()) { + Q_EMIT message(QMessageBox::Warning, QString::fromStdString(warning)); + } + if (wallet) { + Q_EMIT opened(m_wallet_controller->getOrCreateWallet(std::move(wallet))); + } else { + Q_EMIT message(QMessageBox::Critical, QString::fromStdString(error)); + } + Q_EMIT finished(); +} diff --git a/src/qt/walletcontroller.h b/src/qt/walletcontroller.h index 9e19c4264f..3f0da1a87d 100644 --- a/src/qt/walletcontroller.h +++ b/src/qt/walletcontroller.h @@ -12,7 +12,9 @@ #include #include +#include #include +#include class OptionsModel; class PlatformStyle; @@ -22,6 +24,8 @@ class Handler; class Node; } // namespace interfaces +class OpenWalletActivity; + /** * Controller between interfaces::Node, WalletModel instances and the GUI. */ @@ -37,6 +41,10 @@ public: ~WalletController(); std::vector getWallets() const; + std::vector getWalletsAvailableToOpen() const; + + OpenWalletActivity* openWallet(const std::string& name, QWidget* parent = nullptr); + void closeWallet(WalletModel* wallet_model, QWidget* parent = nullptr); private Q_SLOTS: void addWallet(WalletModel* wallet_model); @@ -48,11 +56,34 @@ Q_SIGNALS: void coinsSent(WalletModel* wallet_model, SendCoinsRecipient recipient, QByteArray transaction); private: + QThread m_activity_thread; interfaces::Node& m_node; OptionsModel* const m_options_model; mutable QMutex m_mutex; std::vector m_wallets; std::unique_ptr m_handler_load_wallet; + + friend class OpenWalletActivity; +}; + +class OpenWalletActivity : public QObject +{ + Q_OBJECT + +public: + OpenWalletActivity(WalletController* wallet_controller, const std::string& name); + +public Q_SLOTS: + void open(); + +Q_SIGNALS: + void message(QMessageBox::Icon icon, const QString text); + void finished(); + void opened(WalletModel* wallet_model); + +private: + WalletController* const m_wallet_controller; + std::string const m_name; }; #endif // BITCOIN_QT_WALLETCONTROLLER_H diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 81bc0007ca..30092cba82 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2856,7 +2856,6 @@ static UniValue loadwallet(const JSONRPCRequest& request) ); WalletLocation location(request.params[0].get_str()); - std::string error; if (!location.Exists()) { throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Wallet " + location.GetName() + " not found."); @@ -2868,17 +2867,9 @@ static UniValue loadwallet(const JSONRPCRequest& request) } } - std::string warning; - if (!CWallet::Verify(*g_rpc_interfaces->chain, location, false, error, warning)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error); - } - - std::shared_ptr const wallet = CWallet::CreateWalletFromFile(*g_rpc_interfaces->chain, location); - if (!wallet) { - throw JSONRPCError(RPC_WALLET_ERROR, "Wallet loading failed."); - } - - wallet->postInitProcess(); + std::string error, warning; + std::shared_ptr const wallet = LoadWallet(*g_rpc_interfaces->chain, location, error, warning); + if (!wallet) throw JSONRPCError(RPC_WALLET_ERROR, error); UniValue obj(UniValue::VOBJ); obj.pushKV("name", wallet->GetName()); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 941f943ba1..0a1ceadcc4 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -149,6 +149,28 @@ void UnloadWallet(std::shared_ptr&& wallet) } } +std::shared_ptr LoadWallet(interfaces::Chain& chain, const WalletLocation& location, std::string& error, std::string& warning) +{ + if (!CWallet::Verify(chain, location, false, error, warning)) { + error = "Wallet file verification failed: " + error; + return nullptr; + } + + std::shared_ptr wallet = CWallet::CreateWalletFromFile(chain, location); + if (!wallet) { + error = "Wallet loading failed."; + return nullptr; + } + AddWallet(wallet); + wallet->postInitProcess(); + return wallet; +} + +std::shared_ptr LoadWallet(interfaces::Chain& chain, const std::string& name, std::string& error, std::string& warning) +{ + return LoadWallet(chain, WalletLocation(name), error, warning); +} + const uint256 CMerkleTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001")); /** @defgroup mapWallet diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 86d0590a53..d01d5c4512 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -70,6 +70,7 @@ bool RemoveWallet(const std::shared_ptr& wallet); bool HasWallets(); std::vector> GetWallets(); std::shared_ptr GetWallet(const std::string& name); +std::shared_ptr LoadWallet(interfaces::Chain& chain, const WalletLocation& location, std::string& error, std::string& warning); static const unsigned int DEFAULT_KEYPOOL_SIZE = 1000; //! -paytxfee default