Bitcoin Core 22.99.0
P2P Digital Currency
walletcontroller.cpp
Go to the documentation of this file.
1// Copyright (c) 2019-2020 The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
6
8#include <qt/clientmodel.h>
10#include <qt/guiconstants.h>
11#include <qt/guiutil.h>
12#include <qt/walletmodel.h>
13
14#include <external_signer.h>
15#include <interfaces/handler.h>
16#include <interfaces/node.h>
17#include <util/string.h>
18#include <util/threadnames.h>
19#include <util/translation.h>
20#include <wallet/wallet.h>
21
22#include <algorithm>
23
24#include <QApplication>
25#include <QMessageBox>
26#include <QMutexLocker>
27#include <QThread>
28#include <QTimer>
29#include <QWindow>
30
31WalletController::WalletController(ClientModel& client_model, const PlatformStyle* platform_style, QObject* parent)
32 : QObject(parent)
33 , m_activity_thread(new QThread(this))
34 , m_activity_worker(new QObject)
35 , m_client_model(client_model)
36 , m_node(client_model.node())
37 , m_platform_style(platform_style)
38 , m_options_model(client_model.getOptionsModel())
39{
40 m_handler_load_wallet = m_node.walletClient().handleLoadWallet([this](std::unique_ptr<interfaces::Wallet> wallet) {
41 getOrCreateWallet(std::move(wallet));
42 });
43
45 m_activity_thread->start();
46 QTimer::singleShot(0, m_activity_worker, []() {
47 util::ThreadRename("qt-walletctrl");
48 });
49}
50
51// Not using the default destructor because not all member types definitions are
52// available in the header, just forward declared.
54{
55 m_activity_thread->quit();
56 m_activity_thread->wait();
57 delete m_activity_worker;
58}
59
60std::map<std::string, bool> WalletController::listWalletDir() const
61{
62 QMutexLocker locker(&m_mutex);
63 std::map<std::string, bool> wallets;
64 for (const std::string& name : m_node.walletClient().listWalletDir()) {
65 wallets[name] = false;
66 }
67 for (WalletModel* wallet_model : m_wallets) {
68 auto it = wallets.find(wallet_model->wallet().getWalletName());
69 if (it != wallets.end()) it->second = true;
70 }
71 return wallets;
72}
73
74void WalletController::closeWallet(WalletModel* wallet_model, QWidget* parent)
75{
76 QMessageBox box(parent);
77 box.setWindowTitle(tr("Close wallet"));
78 box.setText(tr("Are you sure you wish to close the wallet <i>%1</i>?").arg(GUIUtil::HtmlEscape(wallet_model->getDisplayName())));
79 box.setInformativeText(tr("Closing the wallet for too long can result in having to resync the entire chain if pruning is enabled."));
80 box.setStandardButtons(QMessageBox::Yes|QMessageBox::Cancel);
81 box.setDefaultButton(QMessageBox::Yes);
82 if (box.exec() != QMessageBox::Yes) return;
83
84 // First remove wallet from node.
85 wallet_model->wallet().remove();
86 // Now release the model.
87 removeAndDeleteWallet(wallet_model);
88}
89
91{
92 QMessageBox::StandardButton button = QMessageBox::question(parent, tr("Close all wallets"),
93 tr("Are you sure you wish to close all wallets?"),
94 QMessageBox::Yes|QMessageBox::Cancel,
95 QMessageBox::Yes);
96 if (button != QMessageBox::Yes) return;
97
98 QMutexLocker locker(&m_mutex);
99 for (WalletModel* wallet_model : m_wallets) {
100 wallet_model->wallet().remove();
101 Q_EMIT walletRemoved(wallet_model);
102 delete wallet_model;
103 }
104 m_wallets.clear();
105}
106
107WalletModel* WalletController::getOrCreateWallet(std::unique_ptr<interfaces::Wallet> wallet)
108{
109 QMutexLocker locker(&m_mutex);
110
111 // Return model instance if exists.
112 if (!m_wallets.empty()) {
113 std::string name = wallet->getWalletName();
114 for (WalletModel* wallet_model : m_wallets) {
115 if (wallet_model->wallet().getWalletName() == name) {
116 return wallet_model;
117 }
118 }
119 }
120
121 // Instantiate model and register it.
122 WalletModel* wallet_model = new WalletModel(std::move(wallet), m_client_model, m_platform_style,
123 nullptr /* required for the following moveToThread() call */);
124
125 // Move WalletModel object to the thread that created the WalletController
126 // object (GUI main thread), instead of the current thread, which could be
127 // an outside wallet thread or RPC thread sending a LoadWallet notification.
128 // This ensures queued signals sent to the WalletModel object will be
129 // handled on the GUI event loop.
130 wallet_model->moveToThread(thread());
131 // setParent(parent) must be called in the thread which created the parent object. More details in #18948.
132 GUIUtil::ObjectInvoke(this, [wallet_model, this] {
133 wallet_model->setParent(this);
135
136 m_wallets.push_back(wallet_model);
137
138 // WalletModel::startPollBalance needs to be called in a thread managed by
139 // Qt because of startTimer. Considering the current thread can be a RPC
140 // thread, better delegate the calling to Qt with Qt::AutoConnection.
141 const bool called = QMetaObject::invokeMethod(wallet_model, "startPollBalance");
142 assert(called);
143
144 connect(wallet_model, &WalletModel::unload, this, [this, wallet_model] {
145 // Defer removeAndDeleteWallet when no modal widget is active.
146 // TODO: remove this workaround by removing usage of QDialog::exec.
147 if (QApplication::activeModalWidget()) {
148 connect(qApp, &QApplication::focusWindowChanged, wallet_model, [this, wallet_model]() {
149 if (!QApplication::activeModalWidget()) {
150 removeAndDeleteWallet(wallet_model);
151 }
152 }, Qt::QueuedConnection);
153 } else {
154 removeAndDeleteWallet(wallet_model);
155 }
156 }, Qt::QueuedConnection);
157
158 // Re-emit coinsSent signal from wallet model.
159 connect(wallet_model, &WalletModel::coinsSent, this, &WalletController::coinsSent);
160
161 Q_EMIT walletAdded(wallet_model);
162
163 return wallet_model;
164}
165
167{
168 // Unregister wallet model.
169 {
170 QMutexLocker locker(&m_mutex);
171 m_wallets.erase(std::remove(m_wallets.begin(), m_wallets.end(), wallet_model));
172 }
173 Q_EMIT walletRemoved(wallet_model);
174 // Currently this can trigger the unload since the model can hold the last
175 // CWallet shared pointer.
176 delete wallet_model;
177}
178
180 : QObject(wallet_controller)
181 , m_wallet_controller(wallet_controller)
182 , m_parent_widget(parent_widget)
183{
184 connect(this, &WalletControllerActivity::finished, this, &QObject::deleteLater);
185}
186
187void WalletControllerActivity::showProgressDialog(const QString& title_text, const QString& label_text)
188{
189 auto progress_dialog = new QProgressDialog(m_parent_widget);
190 progress_dialog->setAttribute(Qt::WA_DeleteOnClose);
191 connect(this, &WalletControllerActivity::finished, progress_dialog, &QWidget::close);
192
193 progress_dialog->setWindowTitle(title_text);
194 progress_dialog->setLabelText(label_text);
195 progress_dialog->setRange(0, 0);
196 progress_dialog->setCancelButton(nullptr);
197 progress_dialog->setWindowModality(Qt::ApplicationModal);
198 GUIUtil::PolishProgressDialog(progress_dialog);
199 // The setValue call forces QProgressDialog to start the internal duration estimation.
200 // See details in https://bugreports.qt.io/browse/QTBUG-47042.
201 progress_dialog->setValue(0);
202}
203
204CreateWalletActivity::CreateWalletActivity(WalletController* wallet_controller, QWidget* parent_widget)
205 : WalletControllerActivity(wallet_controller, parent_widget)
206{
208}
209
211{
213 delete m_passphrase_dialog;
214}
215
217{
219 m_passphrase_dialog->setWindowModality(Qt::ApplicationModal);
220 m_passphrase_dialog->show();
221
222 connect(m_passphrase_dialog, &QObject::destroyed, [this] {
223 m_passphrase_dialog = nullptr;
224 });
225 connect(m_passphrase_dialog, &QDialog::accepted, [this] {
226 createWallet();
227 });
228 connect(m_passphrase_dialog, &QDialog::rejected, [this] {
229 Q_EMIT finished();
230 });
231}
232
234{
236 //: Title of window indicating the progress of creation of a new wallet.
237 tr("Create Wallet"),
238 /*: Descriptive text of the create wallet progress window which indicates
239 to the user which wallet is currently being created. */
240 tr("Creating Wallet <b>%1</b>…").arg(m_create_wallet_dialog->walletName().toHtmlEscaped()));
241
242 std::string name = m_create_wallet_dialog->walletName().toStdString();
243 uint64_t flags = 0;
246 }
249 }
252 }
255 }
256
257 QTimer::singleShot(500, worker(), [this, name, flags] {
258 std::unique_ptr<interfaces::Wallet> wallet = node().walletClient().createWallet(name, m_passphrase, flags, m_error_message, m_warning_message);
259
261
262 QTimer::singleShot(500, this, &CreateWalletActivity::finish);
263 });
264}
265
267{
268 if (!m_error_message.empty()) {
269 QMessageBox::critical(m_parent_widget, tr("Create wallet failed"), QString::fromStdString(m_error_message.translated));
270 } else if (!m_warning_message.empty()) {
271 QMessageBox::warning(m_parent_widget, tr("Create wallet warning"), QString::fromStdString(Join(m_warning_message, Untranslated("\n")).translated));
272 }
273
275
276 Q_EMIT finished();
277}
278
280{
282
283 std::vector<ExternalSigner> signers;
284 try {
285 signers = node().externalSigners();
286 } catch (const std::runtime_error& e) {
287 QMessageBox::critical(nullptr, tr("Can't list signers"), e.what());
288 }
290
291 m_create_wallet_dialog->setWindowModality(Qt::ApplicationModal);
293
294 connect(m_create_wallet_dialog, &QObject::destroyed, [this] {
295 m_create_wallet_dialog = nullptr;
296 });
297 connect(m_create_wallet_dialog, &QDialog::rejected, [this] {
298 Q_EMIT finished();
299 });
300 connect(m_create_wallet_dialog, &QDialog::accepted, [this] {
302 askPassphrase();
303 } else {
304 createWallet();
305 }
306 });
307}
308
309OpenWalletActivity::OpenWalletActivity(WalletController* wallet_controller, QWidget* parent_widget)
310 : WalletControllerActivity(wallet_controller, parent_widget)
311{
312}
313
315{
316 if (!m_error_message.empty()) {
317 QMessageBox::critical(m_parent_widget, tr("Open wallet failed"), QString::fromStdString(m_error_message.translated));
318 } else if (!m_warning_message.empty()) {
319 QMessageBox::warning(m_parent_widget, tr("Open wallet warning"), QString::fromStdString(Join(m_warning_message, Untranslated("\n")).translated));
320 }
321
323
324 Q_EMIT finished();
325}
326
327void OpenWalletActivity::open(const std::string& path)
328{
329 QString name = path.empty() ? QString("["+tr("default wallet")+"]") : QString::fromStdString(path);
330
332 //: Title of window indicating the progress of opening of a wallet.
333 tr("Open Wallet"),
334 /*: Descriptive text of the open wallet progress window which indicates
335 to the user which wallet is currently being opened. */
336 tr("Opening Wallet <b>%1</b>…").arg(name.toHtmlEscaped()));
337
338 QTimer::singleShot(0, worker(), [this, path] {
339 std::unique_ptr<interfaces::Wallet> wallet = node().walletClient().loadWallet(path, m_error_message, m_warning_message);
340
342
343 QTimer::singleShot(0, this, &OpenWalletActivity::finish);
344 });
345}
346
347LoadWalletsActivity::LoadWalletsActivity(WalletController* wallet_controller, QWidget* parent_widget)
348 : WalletControllerActivity(wallet_controller, parent_widget)
349{
350}
351
353{
355 //: Title of progress window which is displayed when wallets are being loaded.
356 tr("Load Wallets"),
357 /*: Descriptive text of the load wallets progress window which indicates to
358 the user that wallets are currently being loaded.*/
359 tr("Loading wallets…"));
360
361 QTimer::singleShot(0, worker(), [this] {
362 for (auto& wallet : node().walletClient().getWallets()) {
364 }
365
366 QTimer::singleShot(0, this, [this] { Q_EMIT finished(); });
367 });
368}
NodeContext m_node
Definition: bitcoin-gui.cpp:36
int flags
Definition: bitcoin-tx.cpp:525
Multifunctional dialog to ask for passphrases.
@ Encrypt
Ask passphrase twice and encrypt.
Model for Bitcoin network client.
Definition: clientmodel.h:48
AskPassphraseDialog * m_passphrase_dialog
CreateWalletDialog * m_create_wallet_dialog
CreateWalletActivity(WalletController *wallet_controller, QWidget *parent_widget)
void created(WalletModel *wallet_model)
Dialog for creating wallets.
bool isMakeBlankWalletChecked() const
void setSigners(const std::vector< ExternalSigner > &signers)
QString walletName() const
bool isDisablePrivateKeysChecked() const
bool isEncryptWalletChecked() const
bool isDescriptorWalletChecked() const
bool isExternalSignerChecked() const
LoadWalletsActivity(WalletController *wallet_controller, QWidget *parent_widget)
void opened(WalletModel *wallet_model)
OpenWalletActivity(WalletController *wallet_controller, QWidget *parent_widget)
void open(const std::string &path)
std::vector< bilingual_str > m_warning_message
WalletController *const m_wallet_controller
interfaces::Node & node() const
QObject * worker() const
void showProgressDialog(const QString &title_text, const QString &label_text)
WalletControllerActivity(WalletController *wallet_controller, QWidget *parent_widget)
QWidget *const m_parent_widget
Controller between interfaces::Node, WalletModel instances and the GUI.
WalletController(ClientModel &client_model, const PlatformStyle *platform_style, QObject *parent)
WalletModel * getOrCreateWallet(std::unique_ptr< interfaces::Wallet > wallet)
ClientModel & m_client_model
void removeAndDeleteWallet(WalletModel *wallet_model)
void walletAdded(WalletModel *wallet_model)
void closeAllWallets(QWidget *parent=nullptr)
std::unique_ptr< interfaces::Handler > m_handler_load_wallet
QThread *const m_activity_thread
std::map< std::string, bool > listWalletDir() const
Returns all wallet names in the wallet dir mapped to whether the wallet is loaded.
void coinsSent(WalletModel *wallet_model, SendCoinsRecipient recipient, QByteArray transaction)
QObject *const m_activity_worker
void walletRemoved(WalletModel *wallet_model)
const PlatformStyle *const m_platform_style
interfaces::Node & m_node
void closeWallet(WalletModel *wallet_model, QWidget *parent=nullptr)
std::vector< WalletModel * > m_wallets
Interface to Bitcoin wallet from Qt view code.
Definition: walletmodel.h:52
interfaces::Wallet & wallet() const
Definition: walletmodel.h:144
void coinsSent(WalletModel *wallet, SendCoinsRecipient recipient, QByteArray transaction)
QString getDisplayName() const
virtual std::vector< ExternalSigner > externalSigners()=0
List external signers.
virtual WalletClient & walletClient()=0
Get wallet client.
virtual std::vector< std::string > listWalletDir()=0
Return available wallets in wallet directory.
virtual std::unique_ptr< Wallet > createWallet(const std::string &name, const SecureString &passphrase, uint64_t wallet_creation_flags, bilingual_str &error, std::vector< bilingual_str > &warnings)=0
Create new wallet.
virtual std::unique_ptr< Handler > handleLoadWallet(LoadWalletFn fn)=0
virtual std::unique_ptr< Wallet > loadWallet(const std::string &name, bilingual_str &error, std::vector< bilingual_str > &warnings)=0
Load existing wallet.
virtual void remove()=0
static const int MAX_PASSPHRASE_SIZE
Definition: guiconstants.h:14
Qt::ConnectionType blockingGUIThreadConnection()
Get connection type to call object slot in GUI thread with invokeMethod.
Definition: guiutil.cpp:363
QString HtmlEscape(const QString &str, bool fMultiLine)
Definition: guiutil.cpp:233
void PolishProgressDialog(QProgressDialog *dialog)
Definition: guiutil.cpp:868
void ObjectInvoke(QObject *object, Fn &&function, Qt::ConnectionType connection=Qt::QueuedConnection)
Queue a function to run in an object's event loop.
Definition: guiutil.h:368
void ThreadRename(std::string &&)
Rename a thread both in terms of an internal (in-memory) name as well as its system thread name.
Definition: threadnames.cpp:57
const char * name
Definition: rest.cpp:43
auto Join(const std::vector< T > &list, const BaseType &separator, UnaryOp unary_op) -> decltype(unary_op(list.at(0)))
Join a list of items.
Definition: string.h:44
bool empty() const
Definition: translation.h:27
std::string translated
Definition: translation.h:18
bilingual_str Untranslated(std::string original)
Mark a bilingual_str as untranslated.
Definition: translation.h:46
assert(!tx.IsCoinBase())
@ WALLET_FLAG_EXTERNAL_SIGNER
Indicates that the wallet needs an external signer.
Definition: walletutil.h:68
@ WALLET_FLAG_DISABLE_PRIVATE_KEYS
Definition: walletutil.h:50
@ WALLET_FLAG_DESCRIPTORS
Indicate that this wallet supports DescriptorScriptPubKeyMan.
Definition: walletutil.h:65
@ WALLET_FLAG_BLANK_WALLET
Flag set when a wallet contains no HD seed and no private keys, scripts, addresses,...
Definition: walletutil.h:62