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