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 |
}
|