Skip to content
Bitcoin Roots

v29.4-roots.3 · Commit 26: [PATCH 26/30] feat(qt): enhance privacy-aware coin control

src/qt/sendcoinsdialog.cpp

Before: src/qt/sendcoinsdialog.cpp · After: src/qt/sendcoinsdialog.cpp

File metadata
index ae6d58eba31493ff89a4887472a432cfff7ee320..115c65e821d70743dbe60f685fcd1554cb085d92 100644

Download original patch · Release notes

Diff rows 1–93 of 93. Line numbers refer to the original before/after files.

+ Added− RemovedUnmarked lines are context.

Scroll the code area horizontally to read long lines. The two line-number columns refer to before and after.

src/qt/sendcoinsdialog.cpp CHANGED
@@ -160,6 +160,8 @@ void SendCoinsDialog::setModel(WalletModel *_model)
160
160
  }
161
161
 
162
162
  connect(_model, &WalletModel::balanceChanged, this, &SendCoinsDialog::setBalance);
163
+ connect(_model, &WalletModel::balanceChanged, this, &SendCoinsDialog::walletChanged);
164
+ connect(_model, &WalletModel::unload, this, &SendCoinsDialog::reject);
163
165
  connect(_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &SendCoinsDialog::refreshBalance);
164
166
  refreshBalance();
165
167
 
@@ -366,6 +368,8 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
366
368
 
367
369
  }
368
370
 
371
+ appendCoinControlSummary(question_string);
372
+
369
373
  // Show the signaling in the prepared transaction, rather than the
370
374
  // requested value, so the confirmation states the effective result even
371
375
  // when no fee is required.
@@ -480,6 +484,45 @@ bool SendCoinsDialog::signWithExternalSigner(PartiallySignedTransaction& psbtx,
480
484
  return true;
481
485
  }
482
486
 
487
+ void SendCoinsDialog::appendCoinControlSummary(QString& text) const
488
+ {
489
+ if (!m_current_transaction || !m_current_transaction->getWtx()) return;
490
+
491
+ const CTransaction& tx{*m_current_transaction->getWtx()};
492
+ std::vector<COutPoint> outpoints;
493
+ outpoints.reserve(tx.vin.size());
494
+ for (const CTxIn& input : tx.vin) outpoints.push_back(input.prevout);
495
+
496
+ CAmount input_amount{0};
497
+ for (const auto& coin : model->wallet().getCoins(outpoints)) input_amount += coin.txout.nValue;
498
+ const CAmount fee{m_current_transaction->getTransactionFee()};
499
+ const CAmount change{input_amount - m_current_transaction->getTotalTransactionAmount() - fee};
500
+ const BitcoinUnit unit{model->getOptionsModel()->getDisplayUnit()};
501
+
502
+ text.append("<hr /><b>" + tr("Effective coin control") + "</b><br />");
503
+ text.append(tr("Inputs: %1 (%2)")
504
+ .arg(tx.vin.size())
505
+ .arg(BitcoinUnits::formatHtmlWithUnit(unit, input_amount)) + "<br />");
506
+ text.append(tr("Estimated virtual size: %1 vB").arg(m_current_transaction->getTransactionSize()) + "<br />");
507
+ const unsigned int vsize{m_current_transaction->getTransactionSize()};
508
+ text.append(tr("Fee rate: %1 sat/vB")
509
+ .arg(QString::number(vsize > 0 ? static_cast<double>(fee) / vsize : 0, 'f', 1)) + "<br />");
510
+ text.append(tr("Change: %1").arg(BitcoinUnits::formatHtmlWithUnit(unit, std::max<CAmount>(change, 0))) + "<br />");
511
+ }
512
+
513
+ void SendCoinsDialog::invalidateSendPreview()
514
+ {
515
+ if (m_sending) return;
516
+ m_current_transaction.reset();
517
+ m_coin_control->UnSelectAll();
518
+ coinControlUpdateLabels();
519
+ }
520
+
521
+ void SendCoinsDialog::walletChanged([[maybe_unused]] const interfaces::WalletBalances& balances)
522
+ {
523
+ invalidateSendPreview();
524
+ }
525
+
483
526
  void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
484
527
  {
485
528
  if(!model || !model->getOptionsModel())
@@ -502,6 +545,11 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
502
545
  fNewRecipientAllowed = true;
503
546
  return;
504
547
  }
548
+ if (!m_current_transaction) {
549
+ Q_EMIT message(tr("Send Coins"), tr("Wallet state changed. Review the current inputs and create a new preview."), CClientUIInterface::MSG_WARNING);
550
+ fNewRecipientAllowed = true;
551
+ return;
552
+ }
505
553
 
506
554
  bool send_failure = false;
507
555
  if (retval == QMessageBox::Save) {
@@ -548,9 +596,18 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
548
596
  // Broadcast the transaction, unless an external signer was used and it
549
597
  // failed, or more signatures are needed.
550
598
  if (broadcast) {
551
- // now send the prepared transaction
552
- model->sendCoins(*m_current_transaction);
553
- Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash());
599
+ // Keep the prepared choice valid through the synchronous commit.
600
+ // Wallet balance notifications from the successful commit must not
601
+ // invalidate its own transaction before it is announced.
602
+ m_sending = true;
603
+ try {
604
+ model->sendCoins(*m_current_transaction);
605
+ Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash());
606
+ } catch (const std::runtime_error& e) {
607
+ Q_EMIT message(tr("Send Coins"), QString::fromStdString(e.what()), CClientUIInterface::MSG_ERROR);
608
+ send_failure = true;
609
+ }
610
+ m_sending = false;
554
611
  }
555
612
  }
556
613
  if (!send_failure) {