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