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:
Jonas Schnelli 2018-06-21 16:24:01 +02:00
commit 000abbb6b0
No known key found for this signature in database
GPG Key ID: 1EB776BB03C7922D
16 changed files with 194 additions and 22 deletions

View File

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

View File

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

View File

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

View File

@ -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()) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -694,9 +694,11 @@ 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;
} }
} }

View File

@ -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"} },

View File

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

View File

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

View File

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