Skip to content
Bitcoin Roots

v31.1-roots.1 · Commit 6: [PATCH 06/17] feat(wallet): preserve Roots signing and spend controls on v31

src/qt/test/wallettests.cpp

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

File metadata
index 19b25650b7886ceb4e12dad37fe246107ab42c04..41b00b2313b920956b7434f1174ac9dadb1d87c9 100644

Download original patch · Release notes

Diff rows 401–588 of 588. Line numbers refer to the original before/after files.

+ Added− RemovedUnmarked lines are context.

Scroll the code area horizontally to read long lines. Before is on the left; after is on the right. Unified view fits narrow screens more easily.

src/qt/test/wallettests.cpp CHANGED
@@ -280,16 +492,157 @@ void TestGUI(interfaces::Node& node, const std::shared_ptr<CWallet>& wallet) (continued)
 
 
283
  // Send two transactions, and verify they are added to transaction list.
284
  TransactionTableModel* transactionTableModel = walletModel.getTransactionTableModel();
285
  QCOMPARE(transactionTableModel->rowCount({}), 105);
286
- Txid txid1 = SendCoins(*wallet.get(), sendCoinsDialog, PKHash(), 5 * COIN, /*rbf=*/false);
287
- Txid txid2 = SendCoins(*wallet.get(), sendCoinsDialog, PKHash(), 10 * COIN, /*rbf=*/true);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
  // Transaction table model updates on a QueuedConnection, so process events to ensure it's updated.
289
  qApp->processEvents();
290
- QCOMPARE(transactionTableModel->rowCount({}), 107);
291
  QVERIFY(FindTx(*transactionTableModel, txid1).isValid());
292
  QVERIFY(FindTx(*transactionTableModel, txid2).isValid());
 
293
 
294
  // Call bumpfee. Test canceled fullrbf bump, canceled bip-125-rbf bump, passing bump, and then failing bump.
295
  BumpFee(transactionView, txid1, /*expectDisabled=*/false, /*expectError=*/{}, /*cancel=*/true);
@@ -397,9 +750,23 @@ void TestGUIWatchOnly(interfaces::Node& node, TestChain100Setup& test)
397
  WalletModel& walletModel = *mini_gui.walletModel;
398
  SendCoinsDialog& sendCoinsDialog = mini_gui.sendCoinsDialog;
399
 
400
- // Update walletModel cached balance which will trigger an update for the 'labelBalance' QLabel.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
401
  walletModel.pollBalanceChanged();
402
- // Check balance in send dialog
403
  CompareBalance(walletModel, walletModel.wallet().getBalances().balance,
404
  sendCoinsDialog.findChild<QLabel*>("labelBalance"));
405
 
@@ -424,7 +791,10 @@ void TestGUIWatchOnly(interfaces::Node& node, TestChain100Setup& test)
424
  timer.start(500);
425
 
426
  // Send tx and verify PSBT copied to the clipboard.
427
- SendCoins(*wallet.get(), sendCoinsDialog, PKHash(), 5 * COIN, /*rbf=*/false, QMessageBox::Save);
 
 
 
428
  const std::string& psbt_string = QApplication::clipboard()->text().toStdString();
429
  QVERIFY(!psbt_string.empty());
430
 
@@ -434,6 +804,79 @@ void TestGUIWatchOnly(interfaces::Node& node, TestChain100Setup& test)
434
  PartiallySignedTransaction psbt;
435
  std::string err;
436
  QVERIFY(DecodeRawPSBT(psbt, MakeByteSpan(*decoded_psbt), err));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
437
  }
438
 
439
  void TestGUI(interfaces::Node& node)
@@ -454,6 +897,7 @@ void TestGUI(interfaces::Node& node)
454
  // Legacy watch-only wallet test
455
  // Verify PSBT creation.
456
  TestGUIWatchOnly(node, test);
 
457
  }
458
 
459
  } // namespace
@@ -471,5 +915,11 @@ void WalletTests::walletTests()
471
  return;
472
  }
473
  #endif
 
 
 
 
 
474
  TestGUI(m_node);
 
475
  }
 
597
+ effective_dialog.reject();
598
+
599
  // Send two transactions, and verify they are added to transaction list.
600
  TransactionTableModel* transactionTableModel = walletModel.getTransactionTableModel();
601
  QCOMPARE(transactionTableModel->rowCount({}), 105);
602
+ walletModel.getOptionsModel()->setData(walletModel.getOptionsModel()->index(OptionsModel::CoinControlFeatures), true);
603
+ QVERIFY(QMetaObject::invokeMethod(&sendCoinsDialog, "on_buttonChooseFee_clicked"));
604
+ QVERIFY(CaptureWalletWidget(sendCoinsDialog, "send-rbf"));
605
+ auto* custom_change{sendCoinsDialog.findChild<QCheckBox*>("checkBoxCoinControlChange")};
606
+ auto* change_address{sendCoinsDialog.findChild<QValidatedLineEdit*>("lineEditCoinControlChange")};
607
+ QVERIFY(custom_change && change_address);
608
+ change_address->setText("not-an-address");
609
+ custom_change->setChecked(true);
610
+ QVERIFY(QMetaObject::invokeMethod(&sendCoinsDialog, "coinControlChangeEdited", Q_ARG(QString, QString{"not-an-address"})));
611
+ QVERIFY(CaptureWalletWidget(sendCoinsDialog, "send-change-invalid"));
612
+ custom_change->setChecked(false);
613
+ change_address->clear();
614
+ auto* rbf_checkbox = sendCoinsDialog.findChild<QCheckBox*>("optInRBF");
615
+ QVERIFY(rbf_checkbox);
616
+ QVERIFY(!rbf_checkbox->isChecked());
617
+ QString no_rbf_confirmation, rbf_confirmation;
618
+ Txid txid1 = SendCoins(*wallet, sendCoinsDialog, PKHash(), 5 * COIN, false, QMessageBox::Yes, &no_rbf_confirmation);
619
+ Txid txid2 = SendCoins(*wallet, sendCoinsDialog, PKHash(), 10 * COIN, true, QMessageBox::Yes, &rbf_confirmation);
620
+ {
621
+ LOCK(wallet->cs_wallet);
622
+ QVERIFY(!SignalsOptInRBF(*wallet->mapWallet.at(txid1).tx));
623
+ QVERIFY(SignalsOptInRBF(*wallet->mapWallet.at(txid2).tx));
624
+ }
625
+ QVERIFY(no_rbf_confirmation.contains("Not signalling Replace-By-Fee, BIP-125."));
626
+ QVERIFY(rbf_confirmation.contains("signals Replace-By-Fee (BIP-125)"));
627
+ QVERIFY(rbf_confirmation.contains("Effective coin control"));
628
+ QVERIFY(rbf_confirmation.contains("Inputs:"));
629
+ QVERIFY(rbf_confirmation.contains("Fee rate:"));
630
+ QVERIFY(rbf_confirmation.contains("Change:"));
631
+ wallet->m_signal_rbf = true;
632
+ SendCoinsDialog default_rbf_dialog(platformStyle.get());
633
+ default_rbf_dialog.setModel(&walletModel);
634
+ QVERIFY(default_rbf_dialog.findChild<QCheckBox*>("optInRBF")->isChecked());
635
+ Txid txid3 = SendCoins(*wallet, default_rbf_dialog, PKHash(), COIN, false);
636
+ {
637
+ LOCK(wallet->cs_wallet);
638
+ QVERIFY(!SignalsOptInRBF(*wallet->mapWallet.at(txid3).tx));
639
+ }
640
  // Transaction table model updates on a QueuedConnection, so process events to ensure it's updated.
641
  qApp->processEvents();
642
+ QCOMPARE(transactionTableModel->rowCount({}), 108);
643
  QVERIFY(FindTx(*transactionTableModel, txid1).isValid());
644
  QVERIFY(FindTx(*transactionTableModel, txid2).isValid());
645
+ QVERIFY(FindTx(*transactionTableModel, txid3).isValid());
646
 
647
  // Call bumpfee. Test canceled fullrbf bump, canceled bip-125-rbf bump, passing bump, and then failing bump.
648
  BumpFee(transactionView, txid1, /*expectDisabled=*/false, /*expectError=*/{}, /*cancel=*/true);
 
750
  WalletModel& walletModel = *mini_gui.walletModel;
751
  SendCoinsDialog& sendCoinsDialog = mini_gui.sendCoinsDialog;
752
 
753
+ qApp->processEvents();
754
+ const auto groups{walletModel.wallet().listCoins()};
755
+ QVERIFY(!groups.empty());
756
+ const auto facts{walletModel.wallet().getCoins({std::get<0>(groups.begin()->second.front())})};
757
+ QVERIFY(facts.front().is_watch_only);
758
+ CoinControlDialog watch_control(*sendCoinsDialog.getCoinControl(), &walletModel, platformStyle.get());
759
+ auto* watch_tree{watch_control.findChild<QTreeWidget*>("treeWidget")};
760
+ QVERIFY(watch_tree);
761
+ bool shows_watch_only{false};
762
+ for (int i = 0; i < watch_tree->topLevelItemCount(); ++i) {
763
+ auto* row{watch_tree->topLevelItem(i)};
764
+ shows_watch_only |= row->text(6).contains("Watch-only");
765
+ for (int j = 0; j < row->childCount(); ++j) shows_watch_only |= row->child(j)->text(6).contains("Watch-only");
766
+ }
767
+ QVERIFY(shows_watch_only);
768
+ watch_control.reject();
769
  walletModel.pollBalanceChanged();
 
770
  CompareBalance(walletModel, walletModel.wallet().getBalances().balance,
771
  sendCoinsDialog.findChild<QLabel*>("labelBalance"));
772
 
 
791
  timer.start(500);
792
 
793
  // Send tx and verify PSBT copied to the clipboard.
794
+ QString psbt_confirmation;
795
+ SendCoins(*wallet.get(), sendCoinsDialog, PKHash(), 5 * COIN, /*rbf=*/false, QMessageBox::Save, &psbt_confirmation);
796
+ QVERIFY(psbt_confirmation.contains("Effective coin control"));
797
+ QVERIFY(psbt_confirmation.contains("Inputs:"));
798
  const std::string& psbt_string = QApplication::clipboard()->text().toStdString();
799
  QVERIFY(!psbt_string.empty());
800
 
 
804
  PartiallySignedTransaction psbt;
805
  std::string err;
806
  QVERIFY(DecodeRawPSBT(psbt, MakeByteSpan(*decoded_psbt), err));
807
+ QVERIFY(!SignalsOptInRBF(CTransaction{*psbt.tx}));
808
+ timer.start(500);
809
+ SendCoins(*wallet, sendCoinsDialog, PKHash(), 5 * COIN, true, QMessageBox::Save);
810
+ decoded_psbt = DecodeBase64(QApplication::clipboard()->text().toStdString());
811
+ QVERIFY(decoded_psbt);
812
+ psbt = {};
813
+ err.clear();
814
+ QVERIFY(DecodeRawPSBT(psbt, MakeByteSpan(*decoded_psbt), err));
815
+ QVERIFY(SignalsOptInRBF(CTransaction{*psbt.tx}));
816
+ }
817
+
818
+ //! Exercise a successful scan/preview and cancelled confirmation on real coins.
819
+ void TestSweepPreview(interfaces::Node& node, const std::shared_ptr<CWallet>& wallet, TestChain100Setup& test)
820
+ {
821
+ const CKey key{GenerateRandomKey()};
822
+ const CScript script{GetScriptForDestination(PKHash{key.GetPubKey()})};
823
+ test.CreateAndProcessBlock({}, script);
824
+ for (int i = 0; i < 100; ++i) test.CreateAndProcessBlock({}, CScript{});
825
+ std::unique_ptr<const PlatformStyle> style{PlatformStyle::instantiate("other")};
826
+ MiniGUI gui(node, style.get());
827
+ gui.initModelForWallet(node, wallet, style.get());
828
+ qApp->processEvents();
829
+ auto& model{*gui.walletModel};
830
+ SweepDialog dialog(&model);
831
+ auto* private_key{dialog.findChild<QLineEdit*>("sweepPrivateKey")};
832
+ auto* destination{dialog.findChild<QLineEdit*>("sweepDestination")};
833
+ auto* broadcast{dialog.findChild<QPushButton*>("sweepBroadcast")};
834
+ auto* result{dialog.findChild<QLabel*>("sweepResult")};
835
+ const auto dest{model.wallet().getNewDestination(OutputType::BECH32, "")};
836
+ QVERIFY(dest);
837
+ destination->setText(QString::fromStdString(EncodeDestination(*dest)));
838
+ // This key is freshly generated solely for the disposable test chain.
839
+ private_key->setText(QString::fromStdString(EncodeSecret(key)));
840
+ std::promise<void> locked, release;
841
+ auto released{release.get_future()};
842
+ std::thread blocker{[&] {
843
+ LOCK(cs_main);
844
+ locked.set_value();
845
+ released.wait();
846
+ }};
847
+ locked.get_future().wait();
848
+ const bool started{QMetaObject::invokeMethod(&dialog, "preview")};
849
+ const bool captured{CaptureWalletWidget(dialog, "sweep-scanning")};
850
+ // Cancellation wins before the worker can obtain a UTXO cursor. Ensure
851
+ // transient state disappears while the scan is still outstanding.
852
+ dialog.reject();
853
+ const bool disposed{SweepDialogTestAccess::Cache(dialog).empty() && private_key->text().isEmpty() && !broadcast->isEnabled()};
854
+ release.set_value();
855
+ blocker.join();
856
+ QVERIFY(started && captured && disposed);
857
+ private_key->setText(QString::fromStdString(EncodeSecret(key)));
858
+ QVERIFY(QMetaObject::invokeMethod(&dialog, "preview"));
859
+ QTRY_VERIFY_WITH_TIMEOUT(broadcast->isEnabled(), 10000);
860
+ QVERIFY(result->text().contains("net received"));
861
+ QVERIFY(private_key->text().isEmpty());
862
+ QVERIFY(CaptureWalletWidget(dialog, "sweep-preview"));
863
+ QTimer::singleShot(0, [] {
864
+ for (auto* widget : QApplication::topLevelWidgets()) {
865
+ auto* message{qobject_cast<QMessageBox*>(widget)};
866
+ if (message && message->windowTitle() == "Broadcast sweep") {
867
+ const bool captured{CaptureWalletWidget(*message, "sweep-confirmation")};
868
+ message->button(QMessageBox::Cancel)->click();
869
+ QVERIFY(captured);
870
+ }
871
+ }
872
+ });
873
+ QVERIFY(QMetaObject::invokeMethod(&dialog, "broadcast"));
874
+ QVERIFY(broadcast->isEnabled()); // Confirmation cancelled, no submission.
875
+ model.updateTransaction(); // Same balance, but state must invalidate preview.
876
+ QVERIFY(!broadcast->isEnabled());
877
+ QVERIFY(SweepDialogTestAccess::Cache(dialog).empty());
878
+ QVERIFY(result->text().contains("fresh preview"));
879
+ dialog.reject();
880
  }
881
 
882
  void TestGUI(interfaces::Node& node)
 
897
  // Legacy watch-only wallet test
898
  // Verify PSBT creation.
899
  TestGUIWatchOnly(node, test);
900
+ TestSweepPreview(node, desc_wallet, test);
901
  }
902
 
903
  } // namespace
 
915
  return;
916
  }
917
  #endif
918
+ const QFont original_font{qApp->font()};
919
+ const double scale{qEnvironmentVariable("ROOTS_WALLET_FONT_SCALE", "1.0").toDouble()};
920
+ QFont font{original_font};
921
+ font.setPointSizeF(original_font.pointSizeF() * scale);
922
+ qApp->setFont(font);
923
  TestGUI(m_node);
924
+ qApp->setFont(original_font);
925
  }