mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-09 10:43:19 -05:00
Merge #13111: Add unloadwallet RPC
fe65bdec2
bugfix: Delete walletView in WalletFrame::removeWallet (João Barbosa)0b82bac76
bugfix: Remove dangling wallet env instance (João Barbosa)0ee77b207
ui: Support wallets unloaded dynamically (João Barbosa)9f9b50d5f
doc: Add release notes for unloadwallet RPC (João Barbosa)ccbf7ae74
test: Wallet methods are disabled when no wallet is loaded (João Barbosa)4940a20a4
test: Add functional tests for unloadwallet RPC (João Barbosa)6608c369b
rpc: Add unloadwallet RPC (João Barbosa)537efe19e
rpc: Extract GetWalletNameFromJSONRPCRequest from GetWalletForJSONRPCRequest (João Barbosa) Pull request description: This patch adds wallet unload feature via RPC. It also adds UI support for unloaded wallets. Tree-SHA512: 7c7f9f32f7a2266d2df574aa6b95f993c3dc82736f93304562122beb8756fb28cd22d03866b48f493c747441f22d30e196b098dec435cc25e035633f090351ea
This commit is contained in:
commit
000abbb6b0
16 changed files with 194 additions and 22 deletions
|
@ -1,9 +1,10 @@
|
||||||
Dynamic loading and creation of wallets
|
Dynamic loading and creation of wallets
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
|
||||||
Previously, wallets could only be loaded or created at startup, by specifying `-wallet` parameters on the command line or in the bitcoin.conf file. It is now possible to load and create wallets dynamically at runtime:
|
Previously, wallets could only be loaded or created at startup, by specifying `-wallet` parameters on the command line or in the bitcoin.conf file. It is now possible to load, create and unload wallets dynamically at runtime:
|
||||||
|
|
||||||
- Existing wallets can be loaded by calling the `loadwallet` RPC. The wallet can be specified as file/directory basename (which must be located in the `walletdir` directory), or as an absolute path to a file/directory.
|
- Existing wallets can be loaded by calling the `loadwallet` RPC. The wallet can be specified as file/directory basename (which must be located in the `walletdir` directory), or as an absolute path to a file/directory.
|
||||||
- New wallets can be created (and loaded) by calling the `createwallet` RPC. The provided name must not match a wallet file in the `walletdir` directory or the name of a wallet that is currently loaded.
|
- New wallets can be created (and loaded) by calling the `createwallet` RPC. The provided name must not match a wallet file in the `walletdir` directory or the name of a wallet that is currently loaded.
|
||||||
|
- Loaded wallets can be unloaded by calling the `unloadwallet` RPC.
|
||||||
|
|
||||||
This feature is currently only available through the RPC interface.
|
This feature is currently only available through the RPC interface.
|
||||||
|
|
|
@ -429,6 +429,10 @@ public:
|
||||||
bool hdEnabled() override { return m_wallet.IsHDEnabled(); }
|
bool hdEnabled() override { return m_wallet.IsHDEnabled(); }
|
||||||
OutputType getDefaultAddressType() override { return m_wallet.m_default_address_type; }
|
OutputType getDefaultAddressType() override { return m_wallet.m_default_address_type; }
|
||||||
OutputType getDefaultChangeType() override { return m_wallet.m_default_change_type; }
|
OutputType getDefaultChangeType() override { return m_wallet.m_default_change_type; }
|
||||||
|
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));
|
||||||
|
|
|
@ -242,6 +242,10 @@ public:
|
||||||
// Get default change type.
|
// Get default change type.
|
||||||
virtual OutputType getDefaultChangeType() = 0;
|
virtual OutputType getDefaultChangeType() = 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;
|
||||||
|
|
|
@ -238,6 +238,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();
|
||||||
|
@ -467,11 +468,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;
|
||||||
|
@ -491,8 +503,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, platformStyle, optionsModel, nullptr);
|
||||||
Q_ARG(WalletModel*, new WalletModel(std::move(wallet), m_node, platformStyle, 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()) {
|
||||||
|
|
|
@ -120,6 +120,7 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty
|
||||||
modalOverlay(0),
|
modalOverlay(0),
|
||||||
prevBlocks(0),
|
prevBlocks(0),
|
||||||
spinnerFrame(0),
|
spinnerFrame(0),
|
||||||
|
m_wallet_selector_label(nullptr),
|
||||||
platformStyle(_platformStyle)
|
platformStyle(_platformStyle)
|
||||||
{
|
{
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
|
@ -477,6 +478,16 @@ void BitcoinGUI::createToolBars()
|
||||||
|
|
||||||
m_wallet_selector = new QComboBox();
|
m_wallet_selector = new QComboBox();
|
||||||
connect(m_wallet_selector, SIGNAL(currentIndexChanged(int)), this, SLOT(setCurrentWalletBySelectorIndex(int)));
|
connect(m_wallet_selector, SIGNAL(currentIndexChanged(int)), this, SLOT(setCurrentWalletBySelectorIndex(int)));
|
||||||
|
|
||||||
|
m_wallet_selector_label = new QLabel();
|
||||||
|
m_wallet_selector_label->setText(tr("Wallet:") + " ");
|
||||||
|
m_wallet_selector_label->setBuddy(m_wallet_selector);
|
||||||
|
|
||||||
|
m_wallet_selector_label_action = appToolBar->addWidget(m_wallet_selector_label);
|
||||||
|
m_wallet_selector_action = appToolBar->addWidget(m_wallet_selector);
|
||||||
|
|
||||||
|
m_wallet_selector_label_action->setVisible(false);
|
||||||
|
m_wallet_selector_action->setVisible(false);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -556,16 +567,29 @@ bool BitcoinGUI::addWallet(WalletModel *walletModel)
|
||||||
setWalletActionsEnabled(true);
|
setWalletActionsEnabled(true);
|
||||||
m_wallet_selector->addItem(display_name, name);
|
m_wallet_selector->addItem(display_name, name);
|
||||||
if (m_wallet_selector->count() == 2) {
|
if (m_wallet_selector->count() == 2) {
|
||||||
m_wallet_selector_label = new QLabel();
|
m_wallet_selector_label_action->setVisible(true);
|
||||||
m_wallet_selector_label->setText(tr("Wallet:") + " ");
|
m_wallet_selector_action->setVisible(true);
|
||||||
m_wallet_selector_label->setBuddy(m_wallet_selector);
|
|
||||||
appToolBar->addWidget(m_wallet_selector_label);
|
|
||||||
appToolBar->addWidget(m_wallet_selector);
|
|
||||||
}
|
}
|
||||||
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_label_action->setVisible(false);
|
||||||
|
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)
|
||||||
|
|
|
@ -70,6 +70,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;
|
||||||
|
@ -122,8 +123,10 @@ private:
|
||||||
QAction *openRPCConsoleAction;
|
QAction *openRPCConsoleAction;
|
||||||
QAction *openAction;
|
QAction *openAction;
|
||||||
QAction *showHelpMessageAction;
|
QAction *showHelpMessageAction;
|
||||||
|
QAction *m_wallet_selector_label_action = nullptr;
|
||||||
|
QAction *m_wallet_selector_action = nullptr;
|
||||||
|
|
||||||
QLabel *m_wallet_selector_label;
|
QLabel *m_wallet_selector_label = nullptr;
|
||||||
QComboBox *m_wallet_selector;
|
QComboBox *m_wallet_selector;
|
||||||
|
|
||||||
QSystemTrayIcon *trayIcon;
|
QSystemTrayIcon *trayIcon;
|
||||||
|
|
|
@ -713,6 +713,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)
|
||||||
|
|
|
@ -48,6 +48,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,
|
||||||
|
|
|
@ -94,6 +94,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -364,6 +364,12 @@ bool WalletModel::changePassphrase(const SecureString &oldPass, const SecureStri
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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";
|
||||||
|
@ -411,6 +417,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));
|
||||||
|
@ -421,6 +428,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();
|
||||||
|
|
|
@ -208,6 +208,7 @@ public:
|
||||||
AddressTableModel* getAddressTableModel() const { return addressTableModel; }
|
AddressTableModel* getAddressTableModel() const { return addressTableModel; }
|
||||||
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;
|
||||||
|
@ -261,6 +262,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();
|
||||||
|
|
|
@ -694,8 +694,10 @@ 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");
|
||||||
|
}
|
||||||
|
g_dbenvs.erase(strPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -794,5 +796,6 @@ void BerkeleyDatabase::Flush(bool shutdown)
|
||||||
{
|
{
|
||||||
if (!IsDummy()) {
|
if (!IsDummy()) {
|
||||||
env->Flush(shutdown);
|
env->Flush(shutdown);
|
||||||
|
if (shutdown) env = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,12 +40,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;
|
||||||
}
|
}
|
||||||
|
@ -66,11 +75,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)");
|
||||||
}
|
}
|
||||||
|
@ -3053,7 +3057,7 @@ static UniValue listwallets(const JSONRPCRequest& request)
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
||||||
|
@ -3100,7 +3104,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(
|
||||||
|
@ -3147,6 +3151,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;
|
||||||
|
}
|
||||||
|
|
||||||
static UniValue resendwallettransactions(const JSONRPCRequest& request)
|
static UniValue resendwallettransactions(const JSONRPCRequest& request)
|
||||||
{
|
{
|
||||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
|
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
|
||||||
|
@ -4382,6 +4435,7 @@ static const CRPCCommand commands[] =
|
||||||
{ "wallet", "settxfee", &settxfee, {"amount"} },
|
{ "wallet", "settxfee", &settxfee, {"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", "walletlock", &walletlock, {} },
|
{ "wallet", "walletlock", &walletlock, {} },
|
||||||
{ "wallet", "walletpassphrasechange", &walletpassphrasechange, {"oldpassphrase","newpassphrase"} },
|
{ "wallet", "walletpassphrasechange", &walletpassphrasechange, {"oldpassphrase","newpassphrase"} },
|
||||||
{ "wallet", "walletpassphrase", &walletpassphrase, {"passphrase","timeout"} },
|
{ "wallet", "walletpassphrase", &walletpassphrase, {"passphrase","timeout"} },
|
||||||
|
|
|
@ -79,6 +79,15 @@ std::shared_ptr<CWallet> GetWallet(const std::string& name)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
|
const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
|
||||||
|
|
||||||
const uint256 CMerkleTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001"));
|
const uint256 CMerkleTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001"));
|
||||||
|
@ -1296,7 +1305,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4066,7 +4075,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(name, WalletDatabase::Create(path)), ReleaseWallet);
|
||||||
DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun);
|
DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun);
|
||||||
if (nLoadWalletRet != DBErrors::LOAD_OK)
|
if (nLoadWalletRet != DBErrors::LOAD_OK)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1100,6 +1100,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.
|
||||||
|
|
|
@ -234,5 +234,32 @@ class MultiWalletTest(BitcoinTestFramework):
|
||||||
|
|
||||||
assert new_wallet_name in self.nodes[0].listwallets()
|
assert new_wallet_name in self.nodes[0].listwallets()
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
Loading…
Add table
Reference in a new issue