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. The two line-number columns refer to before and after.
src/qt/test/wallettests.cpp
CHANGED
|
@@ -280,16 +492,157 @@ void TestGUI(interfaces::Node& node, const std::shared_ptr<CWallet>& wallet) (continued)
|
|
|
597
|
+
effective_dialog.reject();
|
|
598
|
+
|
|
283
599
|
// Send two transactions, and verify they are added to transaction list.
|
|
284
600
|
TransactionTableModel* transactionTableModel = walletModel.getTransactionTableModel();
|
|
285
601
|
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);
|
|
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
|
+
}
|
|
288
640
|
// Transaction table model updates on a QueuedConnection, so process events to ensure it's updated.
|
|
289
641
|
qApp->processEvents();
|
|
290
|
-
QCOMPARE(transactionTableModel->rowCount({}), 107);
|
|
642
|
+
QCOMPARE(transactionTableModel->rowCount({}), 108);
|
|
291
643
|
QVERIFY(FindTx(*transactionTableModel, txid1).isValid());
|
|
292
644
|
QVERIFY(FindTx(*transactionTableModel, txid2).isValid());
|
|
645
|
+
QVERIFY(FindTx(*transactionTableModel, txid3).isValid());
|
|
293
646
|
|
|
294
647
|
// Call bumpfee. Test canceled fullrbf bump, canceled bip-125-rbf bump, passing bump, and then failing bump.
|
|
295
648
|
BumpFee(transactionView, txid1, /*expectDisabled=*/false, /*expectError=*/{}, /*cancel=*/true);
|
|
@@ -397,9 +750,23 @@ void TestGUIWatchOnly(interfaces::Node& node, TestChain100Setup& test)
|
|
|
397
750
|
WalletModel& walletModel = *mini_gui.walletModel;
|
|
398
751
|
SendCoinsDialog& sendCoinsDialog = mini_gui.sendCoinsDialog;
|
|
399
752
|
|
|
400
|
-
// Update walletModel cached balance which will trigger an update for the 'labelBalance' QLabel.
|
|
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();
|
|
401
769
|
walletModel.pollBalanceChanged();
|
|
402
|
-
// Check balance in send dialog
|
|
403
770
|
CompareBalance(walletModel, walletModel.wallet().getBalances().balance,
|
|
404
771
|
sendCoinsDialog.findChild<QLabel*>("labelBalance"));
|
|
405
772
|
|
|
@@ -424,7 +791,10 @@ void TestGUIWatchOnly(interfaces::Node& node, TestChain100Setup& test)
|
|
|
424
791
|
timer.start(500);
|
|
425
792
|
|
|
426
793
|
// Send tx and verify PSBT copied to the clipboard.
|
|
427
|
-
SendCoins(*wallet.get(), sendCoinsDialog, PKHash(), 5 * COIN, /*rbf=*/false, QMessageBox::Save);
|
|
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:"));
|
|
428
798
|
const std::string& psbt_string = QApplication::clipboard()->text().toStdString();
|
|
429
799
|
QVERIFY(!psbt_string.empty());
|
|
430
800
|
|
|
@@ -434,6 +804,79 @@ void TestGUIWatchOnly(interfaces::Node& node, TestChain100Setup& test)
|
|
|
434
804
|
PartiallySignedTransaction psbt;
|
|
435
805
|
std::string err;
|
|
436
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();
|
|
437
880
|
}
|
|
438
881
|
|
|
439
882
|
void TestGUI(interfaces::Node& node)
|
|
@@ -454,6 +897,7 @@ void TestGUI(interfaces::Node& node)
|
|
|
454
897
|
// Legacy watch-only wallet test
|
|
455
898
|
// Verify PSBT creation.
|
|
456
899
|
TestGUIWatchOnly(node, test);
|
|
900
|
+
TestSweepPreview(node, desc_wallet, test);
|
|
457
901
|
}
|
|
458
902
|
|
|
459
903
|
} // namespace
|
|
@@ -471,5 +915,11 @@ void WalletTests::walletTests()
|
|
|
471
915
|
return;
|
|
472
916
|
}
|
|
473
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);
|
|
474
923
|
TestGUI(m_node);
|
|
924
|
+
qApp->setFont(original_font);
|
|
475
925
|
}
|