Skip to content
Bitcoin Roots

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

src/qt/test/wallettests.cpp

Before: src/qt/test/wallettests.cpp · After: src/qt/test/wallettests.cpp

File metadata
index cc49160ce8d221323ea384ea7bc63d194d581153..3b414a15813c59e02716b3e027de099b286edfe0 100644

Download original patch · Release notes

Diff rows 1–125 of 125. 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/test/wallettests.cpp CHANGED
@@ -10,9 +10,11 @@
10
10
  #include <interfaces/node.h>
11
11
  #include <key_io.h>
12
12
  #include <logging.h>
13
+ #include <policy/feerate.h>
13
14
  #include <qt/bitcoinamountfield.h>
14
15
  #include <qt/bitcoinunits.h>
15
16
  #include <qt/clientmodel.h>
17
+ #include <qt/coincontroldialog.h>
16
18
  #include <qt/optionsmodel.h>
17
19
  #include <qt/overviewpage.h>
18
20
  #include <qt/platformstyle.h>
@@ -49,9 +51,11 @@
49
51
  #include <QLineEdit>
50
52
  #include <QObject>
51
53
  #include <QPushButton>
54
+ #include <QRadioButton>
52
55
  #include <QSettings>
53
56
  #include <QTest>
54
57
  #include <QTimer>
58
+ #include <QTreeWidget>
55
59
  #include <QVBoxLayout>
56
60
  #include <QTextEdit>
57
61
  #include <QListView>
@@ -435,6 +439,82 @@ void TestGUI(interfaces::Node& node, const std::shared_ptr<CWallet>& wallet)
435
439
  // Check 'UseAvailableBalance' functionality
436
440
  VerifyUseAvailableBalance(sendCoinsDialog, walletModel);
437
441
 
442
+ // The coin control view presents wallet-owned facts and keeps filtering a
443
+ // display-only operation: it must not alter the selected outpoints.
444
+ CoinControlDialog coin_control_dialog(*sendCoinsDialog.getCoinControl(), &walletModel, platformStyle.get());
445
+ QTreeWidget* coin_tree = coin_control_dialog.findChild<QTreeWidget*>("treeWidget");
446
+ QLineEdit* coin_filter = coin_control_dialog.findChild<QLineEdit*>("lineEditFilter");
447
+ QLabel* available = coin_control_dialog.findChild<QLabel*>("labelAvailable");
448
+ QLabel* fee_rate = coin_control_dialog.findChild<QLabel*>("labelCoinControlFeeRate");
449
+ QLabel* rbf_summary = coin_control_dialog.findChild<QLabel*>("labelCoinControlRbf");
450
+ QLabel* selection_notice = coin_control_dialog.findChild<QLabel*>("labelSelectionNotice");
451
+ QVERIFY(coin_tree && coin_filter && available && fee_rate && rbf_summary && selection_notice);
452
+ QCOMPARE(coin_tree->headerItem()->text(6), "Status");
453
+ QCOMPARE(coin_tree->headerItem()->text(7), "Effective value");
454
+ QCOMPARE(coin_tree->headerItem()->text(8), "Input bytes");
455
+ QVERIFY(available->text().contains("eligible"));
456
+ QVERIFY(rbf_summary->text().contains("Replace-By-Fee"));
457
+ QCOMPARE(coin_filter->accessibleName(), "Filter coin selection");
458
+ QVERIFY(coin_tree->accessibleDescription().contains("Status column"));
459
+ const auto selected_before_filter = sendCoinsDialog.getCoinControl()->ListSelected();
460
+ coin_filter->setText("not-a-wallet-coin");
461
+ qApp->processEvents();
462
+ for (int row = 0; row < coin_tree->topLevelItemCount(); ++row) QVERIFY(coin_tree->topLevelItem(row)->isHidden());
463
+ QVERIFY(sendCoinsDialog.getCoinControl()->ListSelected() == selected_before_filter);
464
+ coin_filter->clear();
465
+ QVERIFY(coin_tree->focusPolicy() != Qt::NoFocus);
466
+ QVERIFY(QMetaObject::invokeMethod(&coin_control_dialog, "walletChanged"));
467
+ QVERIFY(!sendCoinsDialog.getCoinControl()->HasSelected());
468
+ QVERIFY(!selection_notice->isHidden());
469
+ QVERIFY(selection_notice->text().contains("cleared"));
470
+ coin_control_dialog.reject();
471
+
472
+ // Effective value must use the active coin-control fee rate, not fall back
473
+ // to face value. Check the formatted value, its numeric sorting key, and
474
+ // the item sorting implementation independently of localized labels.
475
+ const CFeeRate effective_fee_rate{1000};
476
+ const auto coin_groups = walletModel.wallet().listCoins();
477
+ QVERIFY(!coin_groups.empty());
478
+ const COutPoint effective_outpoint{std::get<0>(coin_groups.begin()->second.front())};
479
+ const auto effective_coins = walletModel.wallet().getCoins({effective_outpoint}, effective_fee_rate);
480
+ QCOMPARE(effective_coins.size(), 1U);
481
+ const auto& effective_coin = effective_coins.front();
482
+ QVERIFY(effective_coin.effective_value);
483
+ QVERIFY(*effective_coin.effective_value < effective_coin.txout.nValue);
484
+
485
+ wallet::CCoinControl effective_control;
486
+ effective_control.m_feerate = effective_fee_rate;
487
+ CoinControlDialog effective_dialog(effective_control, &walletModel, platformStyle.get());
488
+ QRadioButton* list_mode = effective_dialog.findChild<QRadioButton*>("radioListMode");
489
+ QTreeWidget* effective_tree = effective_dialog.findChild<QTreeWidget*>("treeWidget");
490
+ QVERIFY(list_mode && effective_tree);
491
+ list_mode->setChecked(true);
492
+ qApp->processEvents();
493
+ QTreeWidgetItem* effective_item{nullptr};
494
+ for (int row = 0; row < effective_tree->topLevelItemCount(); ++row) {
495
+ QTreeWidgetItem* item = effective_tree->topLevelItem(row);
496
+ if (item->data(3, Qt::UserRole).toString() == QString::fromStdString(effective_outpoint.hash.GetHex()) &&
497
+ item->data(3, Qt::UserRole + 1).toUInt() == effective_outpoint.n) {
498
+ effective_item = item;
499
+ break;
500
+ }
501
+ }
502
+ QVERIFY(effective_item);
503
+ QCOMPARE(effective_item->text(7), BitcoinUnits::format(walletModel.getOptionsModel()->getDisplayUnit(), *effective_coin.effective_value));
504
+ QCOMPARE(effective_item->data(7, Qt::UserRole).toLongLong(), qlonglong{*effective_coin.effective_value});
505
+
506
+ QTreeWidget effective_sort_tree;
507
+ effective_sort_tree.setColumnCount(9);
508
+ auto* larger_effective = new CCoinControlWidgetItem(&effective_sort_tree);
509
+ larger_effective->setText(7, "0.00000001");
510
+ larger_effective->setData(7, Qt::UserRole, qlonglong{*effective_coin.effective_value + 1});
511
+ auto* smaller_effective = new CCoinControlWidgetItem(&effective_sort_tree);
512
+ smaller_effective->setText(7, "9.99999999");
513
+ smaller_effective->setData(7, Qt::UserRole, qlonglong{*effective_coin.effective_value});
514
+ effective_sort_tree.sortItems(7, Qt::AscendingOrder);
515
+ QCOMPARE(effective_sort_tree.topLevelItem(0)->data(7, Qt::UserRole).toLongLong(), qlonglong{*effective_coin.effective_value});
516
+ effective_dialog.reject();
517
+
438
518
  // Send two transactions, and verify they are added to transaction list.
439
519
  TransactionTableModel* transactionTableModel = walletModel.getTransactionTableModel();
440
520
  QCOMPARE(transactionTableModel->rowCount({}), 105);
@@ -455,6 +535,10 @@ void TestGUI(interfaces::Node& node, const std::shared_ptr<CWallet>& wallet)
455
535
  }
456
536
  QVERIFY(no_rbf_confirmation.contains("Not signalling Replace-By-Fee, BIP-125."));
457
537
  QVERIFY(rbf_confirmation.contains("signals Replace-By-Fee (BIP-125)"));
538
+ QVERIFY(rbf_confirmation.contains("Effective coin control"));
539
+ QVERIFY(rbf_confirmation.contains("Inputs:"));
540
+ QVERIFY(rbf_confirmation.contains("Fee rate:"));
541
+ QVERIFY(rbf_confirmation.contains("Change:"));
458
542
 
459
543
  wallet->m_signal_rbf = true;
460
544
  SendCoinsDialog default_rbf_dialog(platformStyle.get());
@@ -608,7 +692,10 @@ void TestGUIWatchOnly(interfaces::Node& node, TestChain100Setup& test)
608
692
  timer.start(500);
609
693
 
610
694
  // Send tx and verify PSBT copied to the clipboard.
611
- SendCoins(*wallet.get(), sendCoinsDialog, PKHash(), 5 * COIN, /*rbf=*/false, QMessageBox::Save);
695
+ QString psbt_confirmation;
696
+ SendCoins(*wallet.get(), sendCoinsDialog, PKHash(), 5 * COIN, /*rbf=*/false, QMessageBox::Save, &psbt_confirmation);
697
+ QVERIFY(psbt_confirmation.contains("Effective coin control"));
698
+ QVERIFY(psbt_confirmation.contains("Inputs:"));
612
699
  const std::string& psbt_string = QApplication::clipboard()->text().toStdString();
613
700
  QVERIFY(!psbt_string.empty());
614
701