From 68eed5df8656bed1be6526b014e58d3123102b03 Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 16 Dec 2022 23:25:39 -0300 Subject: test,gui: add coverage for PSBT creation on legacy watch-only wallets --- src/qt/test/wallettests.cpp | 88 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 82 insertions(+), 6 deletions(-) (limited to 'src/qt/test/wallettests.cpp') diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index d3d67969a5..eb7bf33a32 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -35,6 +35,8 @@ #include #include #include +#include +#include #include #include #include @@ -47,6 +49,7 @@ using wallet::CWallet; using wallet::CreateMockWalletDatabase; using wallet::RemoveWallet; using wallet::WALLET_FLAG_DESCRIPTORS; +using wallet::WALLET_FLAG_DISABLE_PRIVATE_KEYS; using wallet::WalletContext; using wallet::WalletDescriptor; using wallet::WalletRescanReserver; @@ -54,14 +57,14 @@ using wallet::WalletRescanReserver; namespace { //! Press "Yes" or "Cancel" buttons in modal send confirmation dialog. -void ConfirmSend(QString* text = nullptr, bool cancel = false) +void ConfirmSend(QString* text = nullptr, QMessageBox::StandardButton confirm_type = QMessageBox::Yes) { - QTimer::singleShot(0, [text, cancel]() { + QTimer::singleShot(0, [text, confirm_type]() { for (QWidget* widget : QApplication::topLevelWidgets()) { if (widget->inherits("SendConfirmationDialog")) { SendConfirmationDialog* dialog = qobject_cast(widget); if (text) *text = dialog->text(); - QAbstractButton* button = dialog->button(cancel ? QMessageBox::Cancel : QMessageBox::Yes); + QAbstractButton* button = dialog->button(confirm_type); button->setEnabled(true); button->click(); } @@ -70,7 +73,8 @@ void ConfirmSend(QString* text = nullptr, bool cancel = false) } //! Send coins to address and return txid. -uint256 SendCoins(CWallet& wallet, SendCoinsDialog& sendCoinsDialog, const CTxDestination& address, CAmount amount, bool rbf) +uint256 SendCoins(CWallet& wallet, SendCoinsDialog& sendCoinsDialog, const CTxDestination& address, CAmount amount, bool rbf, + QMessageBox::StandardButton confirm_type = QMessageBox::Yes) { QVBoxLayout* entries = sendCoinsDialog.findChild("entries"); SendCoinsEntry* entry = qobject_cast(entries->itemAt(0)->widget()); @@ -84,7 +88,7 @@ uint256 SendCoins(CWallet& wallet, SendCoinsDialog& sendCoinsDialog, const CTxDe boost::signals2::scoped_connection c(wallet.NotifyTransactionChanged.connect([&txid](const uint256& hash, ChangeType status) { if (status == CT_NEW) txid = hash; })); - ConfirmSend(); + ConfirmSend(/*text=*/nullptr, confirm_type); bool invoked = QMetaObject::invokeMethod(&sendCoinsDialog, "sendButtonClicked", Q_ARG(bool, false)); assert(invoked); return txid; @@ -122,7 +126,7 @@ void BumpFee(TransactionView& view, const uint256& txid, bool expectDisabled, st action->setEnabled(true); QString text; if (expectError.empty()) { - ConfirmSend(&text, cancel); + ConfirmSend(&text, cancel ? QMessageBox::Cancel : QMessageBox::Yes); } else { ConfirmMessage(&text, 0ms); } @@ -183,6 +187,24 @@ void SyncUpWallet(const std::shared_ptr& wallet, interfaces::Node& node QVERIFY(result.last_failed_block.IsNull()); } +std::shared_ptr SetupLegacyWatchOnlyWallet(interfaces::Node& node, TestChain100Setup& test) +{ + std::shared_ptr wallet = std::make_shared(node.context()->chain.get(), "", CreateMockWalletDatabase()); + wallet->LoadWallet(); + { + LOCK(wallet->cs_wallet); + wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS); + wallet->SetupLegacyScriptPubKeyMan(); + // Add watched key + CPubKey pubKey = test.coinbaseKey.GetPubKey(); + bool import_keys = wallet->ImportPubKeys({pubKey.GetID()}, {{pubKey.GetID(), pubKey}} , /*key_origins=*/{}, /*add_keypool=*/false, /*internal=*/false, /*timestamp=*/1); + assert(import_keys); + wallet->SetLastBlockProcessed(105, WITH_LOCK(node.context()->chainman->GetMutex(), return node.context()->chainman->ActiveChain().Tip()->GetBlockHash())); + } + SyncUpWallet(wallet, node); + return wallet; +} + std::shared_ptr SetupDescriptorsWallet(interfaces::Node& node, TestChain100Setup& test) { std::shared_ptr wallet = std::make_shared(node.context()->chain.get(), "", CreateMockWalletDatabase()); @@ -369,6 +391,56 @@ void TestGUI(interfaces::Node& node, const std::shared_ptr& wallet) QCOMPARE(walletModel.wallet().getAddressReceiveRequests().size(), size_t{0}); } +void TestGUIWatchOnly(interfaces::Node& node, TestChain100Setup& test) +{ + const std::shared_ptr& wallet = SetupLegacyWatchOnlyWallet(node, test); + + // Create widgets and init models + std::unique_ptr platformStyle(PlatformStyle::instantiate("other")); + MiniGUI mini_gui(node, platformStyle.get()); + mini_gui.initModelForWallet(node, wallet, platformStyle.get()); + WalletModel& walletModel = *mini_gui.walletModel; + SendCoinsDialog& sendCoinsDialog = mini_gui.sendCoinsDialog; + + // Update walletModel cached balance which will trigger an update for the 'labelBalance' QLabel. + walletModel.pollBalanceChanged(); + // Check balance in send dialog + CompareBalance(walletModel, walletModel.wallet().getBalances().watch_only_balance, + sendCoinsDialog.findChild("labelBalance")); + + // Set change address + sendCoinsDialog.getCoinControl()->destChange = GetDestinationForKey(test.coinbaseKey.GetPubKey(), OutputType::LEGACY); + + // Time to reject "save" PSBT dialog ('SendCoins' locks the main thread until the dialog receives the event). + QTimer timer; + timer.setInterval(500); + QObject::connect(&timer, &QTimer::timeout, [&](){ + for (QWidget* widget : QApplication::topLevelWidgets()) { + if (widget->inherits("QMessageBox")) { + QMessageBox* dialog = qobject_cast(widget); + QAbstractButton* button = dialog->button(QMessageBox::Discard); + button->setEnabled(true); + button->click(); + timer.stop(); + break; + } + } + }); + timer.start(500); + + // Send tx and verify PSBT copied to the clipboard. + SendCoins(*wallet.get(), sendCoinsDialog, PKHash(), 5 * COIN, /*rbf=*/false, QMessageBox::Save); + const std::string& psbt_string = QApplication::clipboard()->text().toStdString(); + QVERIFY(!psbt_string.empty()); + + // Decode psbt + std::optional> decoded_psbt = DecodeBase64(psbt_string); + QVERIFY(decoded_psbt); + PartiallySignedTransaction psbt; + std::string err; + QVERIFY(DecodeRawPSBT(psbt, MakeByteSpan(*decoded_psbt), err)); +} + void TestGUI(interfaces::Node& node) { // Set up wallet and chain with 105 blocks (5 mature blocks for spending). @@ -383,6 +455,10 @@ void TestGUI(interfaces::Node& node) // "Full" GUI tests, use descriptor wallet const std::shared_ptr& desc_wallet = SetupDescriptorsWallet(node, test); TestGUI(node, desc_wallet); + + // Legacy watch-only wallet test + // Verify PSBT creation. + TestGUIWatchOnly(node, test); } } // namespace -- cgit v1.2.3