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
|
||||
---------------------------------------
|
||||
|
||||
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.
|
||||
- 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.
|
||||
|
|
|
@ -429,6 +429,10 @@ public:
|
|||
bool hdEnabled() override { return m_wallet.IsHDEnabled(); }
|
||||
OutputType getDefaultAddressType() override { return m_wallet.m_default_address_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
|
||||
{
|
||||
return MakeHandler(m_wallet.ShowProgress.connect(fn));
|
||||
|
|
|
@ -242,6 +242,10 @@ public:
|
|||
// Get default change type.
|
||||
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.
|
||||
using ShowProgressFn = std::function<void(const std::string& title, int progress)>;
|
||||
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.
|
||||
void handleRunawayException(const QString &message);
|
||||
void addWallet(WalletModel* walletModel);
|
||||
void removeWallet();
|
||||
|
||||
Q_SIGNALS:
|
||||
void requestedInitialize();
|
||||
|
@ -467,11 +468,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;
|
||||
|
@ -491,8 +503,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, platformStyle, optionsModel)));
|
||||
WalletModel* wallet_model = new WalletModel(std::move(wallet), m_node, platformStyle, 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()) {
|
||||
|
|
|
@ -120,6 +120,7 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty
|
|||
modalOverlay(0),
|
||||
prevBlocks(0),
|
||||
spinnerFrame(0),
|
||||
m_wallet_selector_label(nullptr),
|
||||
platformStyle(_platformStyle)
|
||||
{
|
||||
QSettings settings;
|
||||
|
@ -477,6 +478,16 @@ void BitcoinGUI::createToolBars()
|
|||
|
||||
m_wallet_selector = new QComboBox();
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -556,16 +567,29 @@ bool BitcoinGUI::addWallet(WalletModel *walletModel)
|
|||
setWalletActionsEnabled(true);
|
||||
m_wallet_selector->addItem(display_name, name);
|
||||
if (m_wallet_selector->count() == 2) {
|
||||
m_wallet_selector_label = new QLabel();
|
||||
m_wallet_selector_label->setText(tr("Wallet:") + " ");
|
||||
m_wallet_selector_label->setBuddy(m_wallet_selector);
|
||||
appToolBar->addWidget(m_wallet_selector_label);
|
||||
appToolBar->addWidget(m_wallet_selector);
|
||||
m_wallet_selector_label_action->setVisible(true);
|
||||
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_label_action->setVisible(false);
|
||||
m_wallet_selector_action->setVisible(false);
|
||||
}
|
||||
rpcConsole->removeWallet(walletModel);
|
||||
return walletFrame->removeWallet(name);
|
||||
}
|
||||
|
||||
bool BitcoinGUI::setCurrentWallet(const QString& name)
|
||||
{
|
||||
if(!walletFrame)
|
||||
|
|
|
@ -70,6 +70,7 @@ public:
|
|||
functionality.
|
||||
*/
|
||||
bool addWallet(WalletModel *walletModel);
|
||||
bool removeWallet(WalletModel* walletModel);
|
||||
void removeAllWallets();
|
||||
#endif // ENABLE_WALLET
|
||||
bool enableWallet;
|
||||
|
@ -122,8 +123,10 @@ private:
|
|||
QAction *openRPCConsoleAction;
|
||||
QAction *openAction;
|
||||
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;
|
||||
|
||||
QSystemTrayIcon *trayIcon;
|
||||
|
|
|
@ -713,6 +713,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)
|
||||
|
|
|
@ -48,6 +48,7 @@ public:
|
|||
|
||||
void setClientModel(ClientModel *model);
|
||||
void addWallet(WalletModel * const walletModel);
|
||||
void removeWallet(WalletModel* const walletModel);
|
||||
|
||||
enum MessageClass {
|
||||
MC_ERROR,
|
||||
|
|
|
@ -94,6 +94,7 @@ bool WalletFrame::removeWallet(const QString &name)
|
|||
|
||||
WalletView *walletView = mapWalletViews.take(name);
|
||||
walletStack->removeWidget(walletView);
|
||||
delete walletView;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -364,6 +364,12 @@ bool WalletModel::changePassphrase(const SecureString &oldPass, const SecureStri
|
|||
}
|
||||
|
||||
// Handlers for core signals
|
||||
static void NotifyUnload(WalletModel* walletModel)
|
||||
{
|
||||
qDebug() << "NotifyUnload";
|
||||
QMetaObject::invokeMethod(walletModel, "unload", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel)
|
||||
{
|
||||
qDebug() << "NotifyKeyStoreStatusChanged";
|
||||
|
@ -411,6 +417,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));
|
||||
|
@ -421,6 +428,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();
|
||||
|
|
|
@ -208,6 +208,7 @@ public:
|
|||
AddressTableModel* getAddressTableModel() const { return addressTableModel; }
|
||||
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;
|
||||
|
@ -261,6 +262,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();
|
||||
|
|
|
@ -694,8 +694,10 @@ 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");
|
||||
}
|
||||
g_dbenvs.erase(strPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -794,5 +796,6 @@ void BerkeleyDatabase::Flush(bool shutdown)
|
|||
{
|
||||
if (!IsDummy()) {
|
||||
env->Flush(shutdown);
|
||||
if (shutdown) env = nullptr;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,12 +40,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;
|
||||
}
|
||||
|
@ -66,11 +75,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)");
|
||||
}
|
||||
|
@ -3053,7 +3057,7 @@ static UniValue listwallets(const JSONRPCRequest& request)
|
|||
return obj;
|
||||
}
|
||||
|
||||
UniValue loadwallet(const JSONRPCRequest& request)
|
||||
static UniValue loadwallet(const JSONRPCRequest& request)
|
||||
{
|
||||
if (request.fHelp || request.params.size() != 1)
|
||||
throw std::runtime_error(
|
||||
|
@ -3100,7 +3104,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(
|
||||
|
@ -3147,6 +3151,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;
|
||||
}
|
||||
|
||||
static UniValue resendwallettransactions(const JSONRPCRequest& request)
|
||||
{
|
||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
|
||||
|
@ -4382,6 +4435,7 @@ static const CRPCCommand commands[] =
|
|||
{ "wallet", "settxfee", &settxfee, {"amount"} },
|
||||
{ "wallet", "signmessage", &signmessage, {"address","message"} },
|
||||
{ "wallet", "signrawtransactionwithwallet", &signrawtransactionwithwallet, {"hexstring","prevtxs","sighashtype"} },
|
||||
{ "wallet", "unloadwallet", &unloadwallet, {"wallet_name"} },
|
||||
{ "wallet", "walletlock", &walletlock, {} },
|
||||
{ "wallet", "walletpassphrasechange", &walletpassphrasechange, {"oldpassphrase","newpassphrase"} },
|
||||
{ "wallet", "walletpassphrase", &walletpassphrase, {"passphrase","timeout"} },
|
||||
|
|
|
@ -79,6 +79,15 @@ std::shared_ptr<CWallet> GetWallet(const std::string& name)
|
|||
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 uint256 CMerkleTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001"));
|
||||
|
@ -1296,7 +1305,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;
|
||||
}
|
||||
}
|
||||
|
@ -4066,7 +4075,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(name, WalletDatabase::Create(path)), ReleaseWallet);
|
||||
DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun);
|
||||
if (nLoadWalletRet != DBErrors::LOAD_OK)
|
||||
{
|
||||
|
|
|
@ -1100,6 +1100,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.
|
||||
|
|
|
@ -234,5 +234,32 @@ class MultiWalletTest(BitcoinTestFramework):
|
|||
|
||||
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__':
|
||||
MultiWalletTest().main()
|
||||
|
|
Loading…
Reference in a new issue