/ src / qt / walletmodel.cpp
walletmodel.cpp
  1  // Copyright (c) 2011-present 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/walletmodel.h>
  6  
  7  #include <qt/addresstablemodel.h>
  8  #include <qt/clientmodel.h>
  9  #include <qt/guiconstants.h>
 10  #include <qt/guiutil.h>
 11  #include <qt/optionsmodel.h>
 12  #include <qt/paymentserver.h>
 13  #include <qt/recentrequeststablemodel.h>
 14  #include <qt/sendcoinsdialog.h>
 15  #include <qt/transactiontablemodel.h>
 16  
 17  #include <common/args.h>
 18  #include <interfaces/handler.h>
 19  #include <interfaces/node.h>
 20  #include <key_io.h>
 21  #include <node/interface_ui.h>
 22  #include <node/types.h>
 23  #include <psbt.h>
 24  #include <util/translation.h>
 25  #include <wallet/coincontrol.h>
 26  #include <wallet/wallet.h>
 27  
 28  #include <cstdint>
 29  #include <functional>
 30  #include <memory>
 31  #include <vector>
 32  
 33  #include <QDebug>
 34  #include <QMessageBox>
 35  #include <QSet>
 36  #include <QTimer>
 37  
 38  using wallet::CCoinControl;
 39  using wallet::CRecipient;
 40  using wallet::DEFAULT_DISABLE_WALLET;
 41  
 42  WalletModel::WalletModel(std::unique_ptr<interfaces::Wallet> wallet, ClientModel& client_model, const PlatformStyle *platformStyle, QObject *parent) :
 43      QObject(parent),
 44      m_wallet(std::move(wallet)),
 45      m_client_model(&client_model),
 46      m_node(client_model.node()),
 47      optionsModel(client_model.getOptionsModel()),
 48      timer(new QTimer(this))
 49  {
 50      addressTableModel = new AddressTableModel(this);
 51      transactionTableModel = new TransactionTableModel(platformStyle, this);
 52      recentRequestsTableModel = new RecentRequestsTableModel(this);
 53  
 54      subscribeToCoreSignals();
 55  }
 56  
 57  WalletModel::~WalletModel()
 58  {
 59      unsubscribeFromCoreSignals();
 60  }
 61  
 62  void WalletModel::startPollBalance()
 63  {
 64      // Update the cached balance right away, so every view can make use of it,
 65      // so them don't need to waste resources recalculating it.
 66      pollBalanceChanged();
 67  
 68      // This timer will be fired repeatedly to update the balance
 69      // Since the QTimer::timeout is a private signal, it cannot be used
 70      // in the GUIUtil::ExceptionSafeConnect directly.
 71      connect(timer, &QTimer::timeout, this, &WalletModel::timerTimeout);
 72      GUIUtil::ExceptionSafeConnect(this, &WalletModel::timerTimeout, this, &WalletModel::pollBalanceChanged);
 73      timer->start(MODEL_UPDATE_DELAY);
 74  }
 75  
 76  void WalletModel::setClientModel(ClientModel* client_model)
 77  {
 78      m_client_model = client_model;
 79      if (!m_client_model) timer->stop();
 80  }
 81  
 82  void WalletModel::updateStatus()
 83  {
 84      EncryptionStatus newEncryptionStatus = getEncryptionStatus();
 85  
 86      if(cachedEncryptionStatus != newEncryptionStatus) {
 87          Q_EMIT encryptionStatusChanged();
 88      }
 89  }
 90  
 91  void WalletModel::pollBalanceChanged()
 92  {
 93      // Avoid recomputing wallet balances unless a TransactionChanged or
 94      // BlockTip notification was received.
 95      if (!fForceCheckBalanceChanged && m_cached_last_update_tip == getLastBlockProcessed()) return;
 96  
 97      // Try to get balances and return early if locks can't be acquired. This
 98      // avoids the GUI from getting stuck on periodical polls if the core is
 99      // holding the locks for a longer time - for example, during a wallet
100      // rescan.
101      interfaces::WalletBalances new_balances;
102      uint256 block_hash;
103      if (!m_wallet->tryGetBalances(new_balances, block_hash)) {
104          return;
105      }
106  
107      if (fForceCheckBalanceChanged || block_hash != m_cached_last_update_tip) {
108          fForceCheckBalanceChanged = false;
109  
110          // Balance and number of transactions might have changed
111          m_cached_last_update_tip = block_hash;
112  
113          checkBalanceChanged(new_balances);
114          if(transactionTableModel)
115              transactionTableModel->updateConfirmations();
116      }
117  }
118  
119  void WalletModel::checkBalanceChanged(const interfaces::WalletBalances& new_balances)
120  {
121      if (new_balances.balanceChanged(m_cached_balances)) {
122          m_cached_balances = new_balances;
123          Q_EMIT balanceChanged(new_balances);
124      }
125  }
126  
127  interfaces::WalletBalances WalletModel::getCachedBalance() const
128  {
129      return m_cached_balances;
130  }
131  
132  void WalletModel::updateTransaction()
133  {
134      // Balance and number of transactions might have changed
135      fForceCheckBalanceChanged = true;
136  }
137  
138  void WalletModel::updateAddressBook(const QString &address, const QString &label,
139          bool isMine, wallet::AddressPurpose purpose, int status)
140  {
141      if(addressTableModel)
142          addressTableModel->updateEntry(address, label, isMine, purpose, status);
143  }
144  
145  bool WalletModel::validateAddress(const QString& address) const
146  {
147      return IsValidDestinationString(address.toStdString());
148  }
149  
150  WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl& coinControl)
151  {
152      CAmount total = 0;
153      bool fSubtractFeeFromAmount = false;
154      QList<SendCoinsRecipient> recipients = transaction.getRecipients();
155      std::vector<CRecipient> vecSend;
156  
157      if(recipients.empty())
158      {
159          return OK;
160      }
161  
162      QSet<QString> setAddress; // Used to detect duplicates
163      int nAddresses = 0;
164  
165      // Pre-check input data for validity
166      for (const SendCoinsRecipient &rcp : recipients)
167      {
168          if (rcp.fSubtractFeeFromAmount)
169              fSubtractFeeFromAmount = true;
170          {   // User-entered bitcoin address / amount:
171              if(!validateAddress(rcp.address))
172              {
173                  return InvalidAddress;
174              }
175              if(rcp.amount <= 0)
176              {
177                  return InvalidAmount;
178              }
179              setAddress.insert(rcp.address);
180              ++nAddresses;
181  
182              vecSend.emplace_back(CRecipient{DecodeDestination(rcp.address.toStdString()), rcp.amount, rcp.fSubtractFeeFromAmount});
183  
184              total += rcp.amount;
185          }
186      }
187      if(setAddress.size() != nAddresses)
188      {
189          return DuplicateAddress;
190      }
191  
192      // If no coin was manually selected, use the cached balance
193      // Future: can merge this call with 'createTransaction'.
194      CAmount nBalance = getAvailableBalance(&coinControl);
195  
196      if(total > nBalance)
197      {
198          return AmountExceedsBalance;
199      }
200  
201      try {
202          CAmount nFeeRequired = 0;
203          int nChangePosRet = -1;
204  
205          auto& newTx = transaction.getWtx();
206          const auto& res = m_wallet->createTransaction(vecSend, coinControl, /*sign=*/!wallet().privateKeysDisabled(), nChangePosRet, nFeeRequired);
207          newTx = res ? *res : nullptr;
208          transaction.setTransactionFee(nFeeRequired);
209          if (fSubtractFeeFromAmount && newTx)
210              transaction.reassignAmounts(nChangePosRet);
211  
212          if(!newTx)
213          {
214              if(!fSubtractFeeFromAmount && (total + nFeeRequired) > nBalance)
215              {
216                  return SendCoinsReturn(AmountWithFeeExceedsBalance);
217              }
218              Q_EMIT message(tr("Send Coins"), QString::fromStdString(util::ErrorString(res).translated),
219                  CClientUIInterface::MSG_ERROR);
220              return TransactionCreationFailed;
221          }
222  
223          // Reject absurdly high fee. (This can never happen because the
224          // wallet never creates transactions with fee greater than
225          // m_default_max_tx_fee. This merely a belt-and-suspenders check).
226          if (nFeeRequired > m_wallet->getDefaultMaxTxFee()) {
227              return AbsurdFee;
228          }
229      } catch (const std::runtime_error& err) {
230          // Something unexpected happened, instruct user to report this bug.
231          Q_EMIT message(tr("Send Coins"), QString::fromStdString(err.what()),
232                         CClientUIInterface::MSG_ERROR);
233          return TransactionCreationFailed;
234      }
235  
236      return SendCoinsReturn(OK);
237  }
238  
239  void WalletModel::sendCoins(WalletModelTransaction& transaction)
240  {
241      QByteArray transaction_array; /* store serialized transaction */
242  
243      {
244          std::vector<std::pair<std::string, std::string>> vOrderForm;
245          for (const SendCoinsRecipient &rcp : transaction.getRecipients())
246          {
247              if (!rcp.message.isEmpty()) // Message from normal bitcoin:URI (bitcoin:123...?message=example)
248                  vOrderForm.emplace_back("Message", rcp.message.toStdString());
249          }
250  
251          auto& newTx = transaction.getWtx();
252          wallet().commitTransaction(newTx, /*value_map=*/{}, std::move(vOrderForm));
253  
254          DataStream ssTx;
255          ssTx << TX_WITH_WITNESS(*newTx);
256          transaction_array.append((const char*)ssTx.data(), ssTx.size());
257      }
258  
259      // Add addresses / update labels that we've sent to the address book,
260      // and emit coinsSent signal for each recipient
261      for (const SendCoinsRecipient &rcp : transaction.getRecipients())
262      {
263          {
264              std::string strAddress = rcp.address.toStdString();
265              CTxDestination dest = DecodeDestination(strAddress);
266              std::string strLabel = rcp.label.toStdString();
267              {
268                  // Check if we have a new address or an updated label
269                  std::string name;
270                  if (!m_wallet->getAddress(
271                       dest, &name, /*purpose=*/nullptr))
272                  {
273                      m_wallet->setAddressBook(dest, strLabel, wallet::AddressPurpose::SEND);
274                  }
275                  else if (name != strLabel)
276                  {
277                      m_wallet->setAddressBook(dest, strLabel, {}); // {} means don't change purpose
278                  }
279              }
280          }
281          Q_EMIT coinsSent(this, rcp, transaction_array);
282      }
283  
284      checkBalanceChanged(m_wallet->getBalances()); // update balance immediately, otherwise there could be a short noticeable delay until pollBalanceChanged hits
285  }
286  
287  OptionsModel* WalletModel::getOptionsModel() const
288  {
289      return optionsModel;
290  }
291  
292  AddressTableModel* WalletModel::getAddressTableModel() const
293  {
294      return addressTableModel;
295  }
296  
297  TransactionTableModel* WalletModel::getTransactionTableModel() const
298  {
299      return transactionTableModel;
300  }
301  
302  RecentRequestsTableModel* WalletModel::getRecentRequestsTableModel() const
303  {
304      return recentRequestsTableModel;
305  }
306  
307  WalletModel::EncryptionStatus WalletModel::getEncryptionStatus() const
308  {
309      if(!m_wallet->isCrypted())
310      {
311          // A previous bug allowed for watchonly wallets to be encrypted (encryption keys set, but nothing is actually encrypted).
312          // To avoid misrepresenting the encryption status of such wallets, we only return NoKeys for watchonly wallets that are unencrypted.
313          if (m_wallet->privateKeysDisabled()) {
314              return NoKeys;
315          }
316          return Unencrypted;
317      }
318      else if(m_wallet->isLocked())
319      {
320          return Locked;
321      }
322      else
323      {
324          return Unlocked;
325      }
326  }
327  
328  bool WalletModel::setWalletEncrypted(const SecureString& passphrase)
329  {
330      return m_wallet->encryptWallet(passphrase);
331  }
332  
333  bool WalletModel::setWalletLocked(bool locked, const SecureString &passPhrase)
334  {
335      if(locked)
336      {
337          // Lock
338          return m_wallet->lock();
339      }
340      else
341      {
342          // Unlock
343          return m_wallet->unlock(passPhrase);
344      }
345  }
346  
347  bool WalletModel::changePassphrase(const SecureString &oldPass, const SecureString &newPass)
348  {
349      m_wallet->lock(); // Make sure wallet is locked before attempting pass change
350      return m_wallet->changeWalletPassphrase(oldPass, newPass);
351  }
352  
353  // Handlers for core signals
354  static void NotifyUnload(WalletModel* walletModel)
355  {
356      qDebug() << "NotifyUnload";
357      bool invoked = QMetaObject::invokeMethod(walletModel, "unload");
358      assert(invoked);
359  }
360  
361  static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel)
362  {
363      qDebug() << "NotifyKeyStoreStatusChanged";
364      bool invoked = QMetaObject::invokeMethod(walletmodel, "updateStatus", Qt::QueuedConnection);
365      assert(invoked);
366  }
367  
368  static void NotifyAddressBookChanged(WalletModel *walletmodel,
369          const CTxDestination &address, const std::string &label, bool isMine,
370          wallet::AddressPurpose purpose, ChangeType status)
371  {
372      QString strAddress = QString::fromStdString(EncodeDestination(address));
373      QString strLabel = QString::fromStdString(label);
374  
375      qDebug() << "NotifyAddressBookChanged: " + strAddress + " " + strLabel + " isMine=" + QString::number(isMine) + " purpose=" + QString::number(static_cast<uint8_t>(purpose)) + " status=" + QString::number(status);
376      bool invoked = QMetaObject::invokeMethod(walletmodel, "updateAddressBook",
377                                Q_ARG(QString, strAddress),
378                                Q_ARG(QString, strLabel),
379                                Q_ARG(bool, isMine),
380                                Q_ARG(wallet::AddressPurpose, purpose),
381                                Q_ARG(int, status));
382      assert(invoked);
383  }
384  
385  static void NotifyTransactionChanged(WalletModel *walletmodel, const Txid& hash, ChangeType status)
386  {
387      Q_UNUSED(hash);
388      Q_UNUSED(status);
389      bool invoked = QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection);
390      assert(invoked);
391  }
392  
393  static void ShowProgress(WalletModel *walletmodel, const std::string &title, int nProgress)
394  {
395      // emits signal "showProgress"
396      bool invoked = QMetaObject::invokeMethod(walletmodel, "showProgress", Qt::QueuedConnection,
397                                Q_ARG(QString, QString::fromStdString(title)),
398                                Q_ARG(int, nProgress));
399      assert(invoked);
400  }
401  
402  static void NotifyCanGetAddressesChanged(WalletModel* walletmodel)
403  {
404      bool invoked = QMetaObject::invokeMethod(walletmodel, "canGetAddressesChanged");
405      assert(invoked);
406  }
407  
408  void WalletModel::subscribeToCoreSignals()
409  {
410      // Connect signals to wallet
411      m_handler_unload = m_wallet->handleUnload(std::bind(&NotifyUnload, this));
412      m_handler_status_changed = m_wallet->handleStatusChanged(std::bind(&NotifyKeyStoreStatusChanged, this));
413      m_handler_address_book_changed = m_wallet->handleAddressBookChanged(std::bind(NotifyAddressBookChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
414      m_handler_transaction_changed = m_wallet->handleTransactionChanged(std::bind(NotifyTransactionChanged, this, std::placeholders::_1, std::placeholders::_2));
415      m_handler_show_progress = m_wallet->handleShowProgress(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2));
416      m_handler_can_get_addrs_changed = m_wallet->handleCanGetAddressesChanged(std::bind(NotifyCanGetAddressesChanged, this));
417  }
418  
419  void WalletModel::unsubscribeFromCoreSignals()
420  {
421      // Disconnect signals from wallet
422      m_handler_unload->disconnect();
423      m_handler_status_changed->disconnect();
424      m_handler_address_book_changed->disconnect();
425      m_handler_transaction_changed->disconnect();
426      m_handler_show_progress->disconnect();
427      m_handler_can_get_addrs_changed->disconnect();
428  }
429  
430  // WalletModel::UnlockContext implementation
431  WalletModel::UnlockContext WalletModel::requestUnlock()
432  {
433      // Bugs in earlier versions may have resulted in wallets with private keys disabled to become "encrypted"
434      // (encryption keys are present, but not actually doing anything).
435      // To avoid issues with such wallets, check if the wallet has private keys disabled, and if so, return a context
436      // that indicates the wallet is not encrypted.
437      if (m_wallet->privateKeysDisabled()) {
438          return UnlockContext(this, /*valid=*/true, /*relock=*/false);
439      }
440      bool was_locked = getEncryptionStatus() == Locked;
441      if(was_locked)
442      {
443          // Request UI to unlock wallet
444          Q_EMIT requireUnlock();
445      }
446      // If wallet is still locked, unlock was failed or cancelled, mark context as invalid
447      bool valid = getEncryptionStatus() != Locked;
448  
449      return UnlockContext(this, valid, was_locked);
450  }
451  
452  WalletModel::UnlockContext::UnlockContext(WalletModel *_wallet, bool _valid, bool _relock):
453          wallet(_wallet),
454          valid(_valid),
455          relock(_relock)
456  {
457  }
458  
459  WalletModel::UnlockContext::~UnlockContext()
460  {
461      if(valid && relock)
462      {
463          wallet->setWalletLocked(true);
464      }
465  }
466  
467  bool WalletModel::bumpFee(Txid hash, Txid& new_hash)
468  {
469      CCoinControl coin_control;
470      coin_control.m_signal_bip125_rbf = true;
471      std::vector<bilingual_str> errors;
472      CAmount old_fee;
473      CAmount new_fee;
474      CMutableTransaction mtx;
475      if (!m_wallet->createBumpTransaction(hash, coin_control, errors, old_fee, new_fee, mtx)) {
476          QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Increasing transaction fee failed") + "<br />(" +
477              (errors.size() ? QString::fromStdString(errors[0].translated) : "") +")");
478          return false;
479      }
480  
481      // allow a user based fee verification
482      /*: Asks a user if they would like to manually increase the fee of a transaction that has already been created. */
483      QString questionString = tr("Do you want to increase the fee?");
484      questionString.append("<br />");
485      questionString.append("<table style=\"text-align: left;\">");
486      questionString.append("<tr><td>");
487      questionString.append(tr("Current fee:"));
488      questionString.append("</td><td>");
489      questionString.append(BitcoinUnits::formatHtmlWithUnit(getOptionsModel()->getDisplayUnit(), old_fee));
490      questionString.append("</td></tr><tr><td>");
491      questionString.append(tr("Increase:"));
492      questionString.append("</td><td>");
493      questionString.append(BitcoinUnits::formatHtmlWithUnit(getOptionsModel()->getDisplayUnit(), new_fee - old_fee));
494      questionString.append("</td></tr><tr><td>");
495      questionString.append(tr("New fee:"));
496      questionString.append("</td><td>");
497      questionString.append(BitcoinUnits::formatHtmlWithUnit(getOptionsModel()->getDisplayUnit(), new_fee));
498      questionString.append("</td></tr></table>");
499  
500      // Display warning in the "Confirm fee bump" window if the "Coin Control Features" option is enabled
501      if (getOptionsModel()->getCoinControlFeatures()) {
502          questionString.append("<br><br>");
503          questionString.append(tr("Warning: This may pay the additional fee by reducing change outputs or adding inputs, when necessary. It may add a new change output if one does not already exist. These changes may potentially leak privacy."));
504      }
505  
506      const bool enable_send{!wallet().privateKeysDisabled() || wallet().hasExternalSigner()};
507      const bool always_show_unsigned{getOptionsModel()->getEnablePSBTControls()};
508      auto confirmationDialog = new SendConfirmationDialog(tr("Confirm fee bump"), questionString, "", "", SEND_CONFIRM_DELAY, enable_send, always_show_unsigned, nullptr);
509      confirmationDialog->setAttribute(Qt::WA_DeleteOnClose);
510      // TODO: Replace QDialog::exec() with safer QDialog::show().
511      const auto retval = static_cast<QMessageBox::StandardButton>(confirmationDialog->exec());
512  
513      // cancel sign&broadcast if user doesn't want to bump the fee
514      if (retval != QMessageBox::Yes && retval != QMessageBox::Save) {
515          return false;
516      }
517  
518      // Short-circuit if we are returning a bumped transaction PSBT to clipboard
519      if (retval == QMessageBox::Save) {
520          // "Create Unsigned" clicked
521          PartiallySignedTransaction psbtx(mtx);
522          bool complete = false;
523          const auto err{wallet().fillPSBT(std::nullopt, /*sign=*/false, /*bip32derivs=*/true, nullptr, psbtx, complete)};
524          if (err || complete) {
525              QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Can't draft transaction."));
526              return false;
527          }
528          // Serialize the PSBT
529          DataStream ssTx{};
530          ssTx << psbtx;
531          GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
532          Q_EMIT message(tr("PSBT copied"), tr("Fee-bump PSBT copied to clipboard"), CClientUIInterface::MSG_INFORMATION | CClientUIInterface::MODAL);
533          return true;
534      }
535  
536      WalletModel::UnlockContext ctx(requestUnlock());
537      if (!ctx.isValid()) {
538          return false;
539      }
540  
541      assert(!m_wallet->privateKeysDisabled() || wallet().hasExternalSigner());
542  
543      // sign bumped transaction
544      if (!m_wallet->signBumpTransaction(mtx)) {
545          QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Can't sign transaction."));
546          return false;
547      }
548      // commit the bumped transaction
549      if(!m_wallet->commitBumpTransaction(hash, std::move(mtx), errors, new_hash)) {
550          QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Could not commit transaction") + "<br />(" +
551              QString::fromStdString(errors[0].translated)+")");
552          return false;
553      }
554      return true;
555  }
556  
557  void WalletModel::displayAddress(std::string sAddress) const
558  {
559      CTxDestination dest = DecodeDestination(sAddress);
560      try {
561          util::Result<void> result = m_wallet->displayAddress(dest);
562          if (!result) {
563              QMessageBox::warning(nullptr, tr("Signer error"), QString::fromStdString(util::ErrorString(result).translated));
564          }
565      } catch (const std::runtime_error& e) {
566          QMessageBox::critical(nullptr, tr("Can't display address"), e.what());
567      }
568  }
569  
570  bool WalletModel::isWalletEnabled()
571  {
572     return !gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET);
573  }
574  
575  QString WalletModel::getWalletName() const
576  {
577      return QString::fromStdString(m_wallet->getWalletName());
578  }
579  
580  QString WalletModel::getDisplayName() const
581  {
582      return GUIUtil::WalletDisplayName(getWalletName());
583  }
584  
585  bool WalletModel::isMultiwallet() const
586  {
587      return m_node.walletLoader().getWallets().size() > 1;
588  }
589  
590  void WalletModel::refresh(bool pk_hash_only)
591  {
592      addressTableModel = new AddressTableModel(this, pk_hash_only);
593  }
594  
595  uint256 WalletModel::getLastBlockProcessed() const
596  {
597      return m_client_model ? m_client_model->getBestBlockHash() : uint256{};
598  }
599  
600  CAmount WalletModel::getAvailableBalance(const CCoinControl* control)
601  {
602      // No selected coins, return the cached balance
603      if (!control || !control->HasSelected()) {
604          const interfaces::WalletBalances& balances = getCachedBalance();
605          return balances.balance;
606      }
607      // Fetch balance from the wallet, taking into account the selected coins
608      return wallet().getAvailableBalance(*control);
609  }