Skip to content

Commit 68eed5d

Browse files
committed
test,gui: add coverage for PSBT creation on legacy watch-only wallets
1 parent 306aab5 commit 68eed5d

File tree

2 files changed

+83
-7
lines changed

2 files changed

+83
-7
lines changed

src/qt/sendcoinsdialog.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ void SendCoinsDialog::presentPSBT(PartiallySignedTransaction& psbtx)
403403
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
404404
ssTx << psbtx;
405405
GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
406-
QMessageBox msgBox;
406+
QMessageBox msgBox(this);
407407
//: Caption of "PSBT has been copied" messagebox
408408
msgBox.setText(tr("Unsigned Transaction", "PSBT copied"));
409409
msgBox.setInformativeText(tr("The PSBT has been copied to the clipboard. You can also save it."));

src/qt/test/wallettests.cpp

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
#include <QAction>
3636
#include <QApplication>
3737
#include <QCheckBox>
38+
#include <QClipboard>
39+
#include <QObject>
3840
#include <QPushButton>
3941
#include <QTimer>
4042
#include <QVBoxLayout>
@@ -47,21 +49,22 @@ using wallet::CWallet;
4749
using wallet::CreateMockWalletDatabase;
4850
using wallet::RemoveWallet;
4951
using wallet::WALLET_FLAG_DESCRIPTORS;
52+
using wallet::WALLET_FLAG_DISABLE_PRIVATE_KEYS;
5053
using wallet::WalletContext;
5154
using wallet::WalletDescriptor;
5255
using wallet::WalletRescanReserver;
5356

5457
namespace
5558
{
5659
//! Press "Yes" or "Cancel" buttons in modal send confirmation dialog.
57-
void ConfirmSend(QString* text = nullptr, bool cancel = false)
60+
void ConfirmSend(QString* text = nullptr, QMessageBox::StandardButton confirm_type = QMessageBox::Yes)
5861
{
59-
QTimer::singleShot(0, [text, cancel]() {
62+
QTimer::singleShot(0, [text, confirm_type]() {
6063
for (QWidget* widget : QApplication::topLevelWidgets()) {
6164
if (widget->inherits("SendConfirmationDialog")) {
6265
SendConfirmationDialog* dialog = qobject_cast<SendConfirmationDialog*>(widget);
6366
if (text) *text = dialog->text();
64-
QAbstractButton* button = dialog->button(cancel ? QMessageBox::Cancel : QMessageBox::Yes);
67+
QAbstractButton* button = dialog->button(confirm_type);
6568
button->setEnabled(true);
6669
button->click();
6770
}
@@ -70,7 +73,8 @@ void ConfirmSend(QString* text = nullptr, bool cancel = false)
7073
}
7174

7275
//! Send coins to address and return txid.
73-
uint256 SendCoins(CWallet& wallet, SendCoinsDialog& sendCoinsDialog, const CTxDestination& address, CAmount amount, bool rbf)
76+
uint256 SendCoins(CWallet& wallet, SendCoinsDialog& sendCoinsDialog, const CTxDestination& address, CAmount amount, bool rbf,
77+
QMessageBox::StandardButton confirm_type = QMessageBox::Yes)
7478
{
7579
QVBoxLayout* entries = sendCoinsDialog.findChild<QVBoxLayout*>("entries");
7680
SendCoinsEntry* entry = qobject_cast<SendCoinsEntry*>(entries->itemAt(0)->widget());
@@ -84,7 +88,7 @@ uint256 SendCoins(CWallet& wallet, SendCoinsDialog& sendCoinsDialog, const CTxDe
8488
boost::signals2::scoped_connection c(wallet.NotifyTransactionChanged.connect([&txid](const uint256& hash, ChangeType status) {
8589
if (status == CT_NEW) txid = hash;
8690
}));
87-
ConfirmSend();
91+
ConfirmSend(/*text=*/nullptr, confirm_type);
8892
bool invoked = QMetaObject::invokeMethod(&sendCoinsDialog, "sendButtonClicked", Q_ARG(bool, false));
8993
assert(invoked);
9094
return txid;
@@ -122,7 +126,7 @@ void BumpFee(TransactionView& view, const uint256& txid, bool expectDisabled, st
122126
action->setEnabled(true);
123127
QString text;
124128
if (expectError.empty()) {
125-
ConfirmSend(&text, cancel);
129+
ConfirmSend(&text, cancel ? QMessageBox::Cancel : QMessageBox::Yes);
126130
} else {
127131
ConfirmMessage(&text, 0ms);
128132
}
@@ -183,6 +187,24 @@ void SyncUpWallet(const std::shared_ptr<CWallet>& wallet, interfaces::Node& node
183187
QVERIFY(result.last_failed_block.IsNull());
184188
}
185189

190+
std::shared_ptr<CWallet> SetupLegacyWatchOnlyWallet(interfaces::Node& node, TestChain100Setup& test)
191+
{
192+
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(node.context()->chain.get(), "", CreateMockWalletDatabase());
193+
wallet->LoadWallet();
194+
{
195+
LOCK(wallet->cs_wallet);
196+
wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
197+
wallet->SetupLegacyScriptPubKeyMan();
198+
// Add watched key
199+
CPubKey pubKey = test.coinbaseKey.GetPubKey();
200+
bool import_keys = wallet->ImportPubKeys({pubKey.GetID()}, {{pubKey.GetID(), pubKey}} , /*key_origins=*/{}, /*add_keypool=*/false, /*internal=*/false, /*timestamp=*/1);
201+
assert(import_keys);
202+
wallet->SetLastBlockProcessed(105, WITH_LOCK(node.context()->chainman->GetMutex(), return node.context()->chainman->ActiveChain().Tip()->GetBlockHash()));
203+
}
204+
SyncUpWallet(wallet, node);
205+
return wallet;
206+
}
207+
186208
std::shared_ptr<CWallet> SetupDescriptorsWallet(interfaces::Node& node, TestChain100Setup& test)
187209
{
188210
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(node.context()->chain.get(), "", CreateMockWalletDatabase());
@@ -369,6 +391,56 @@ void TestGUI(interfaces::Node& node, const std::shared_ptr<CWallet>& wallet)
369391
QCOMPARE(walletModel.wallet().getAddressReceiveRequests().size(), size_t{0});
370392
}
371393

394+
void TestGUIWatchOnly(interfaces::Node& node, TestChain100Setup& test)
395+
{
396+
const std::shared_ptr<CWallet>& wallet = SetupLegacyWatchOnlyWallet(node, test);
397+
398+
// Create widgets and init models
399+
std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other"));
400+
MiniGUI mini_gui(node, platformStyle.get());
401+
mini_gui.initModelForWallet(node, wallet, platformStyle.get());
402+
WalletModel& walletModel = *mini_gui.walletModel;
403+
SendCoinsDialog& sendCoinsDialog = mini_gui.sendCoinsDialog;
404+
405+
// Update walletModel cached balance which will trigger an update for the 'labelBalance' QLabel.
406+
walletModel.pollBalanceChanged();
407+
// Check balance in send dialog
408+
CompareBalance(walletModel, walletModel.wallet().getBalances().watch_only_balance,
409+
sendCoinsDialog.findChild<QLabel*>("labelBalance"));
410+
411+
// Set change address
412+
sendCoinsDialog.getCoinControl()->destChange = GetDestinationForKey(test.coinbaseKey.GetPubKey(), OutputType::LEGACY);
413+
414+
// Time to reject "save" PSBT dialog ('SendCoins' locks the main thread until the dialog receives the event).
415+
QTimer timer;
416+
timer.setInterval(500);
417+
QObject::connect(&timer, &QTimer::timeout, [&](){
418+
for (QWidget* widget : QApplication::topLevelWidgets()) {
419+
if (widget->inherits("QMessageBox")) {
420+
QMessageBox* dialog = qobject_cast<QMessageBox*>(widget);
421+
QAbstractButton* button = dialog->button(QMessageBox::Discard);
422+
button->setEnabled(true);
423+
button->click();
424+
timer.stop();
425+
break;
426+
}
427+
}
428+
});
429+
timer.start(500);
430+
431+
// Send tx and verify PSBT copied to the clipboard.
432+
SendCoins(*wallet.get(), sendCoinsDialog, PKHash(), 5 * COIN, /*rbf=*/false, QMessageBox::Save);
433+
const std::string& psbt_string = QApplication::clipboard()->text().toStdString();
434+
QVERIFY(!psbt_string.empty());
435+
436+
// Decode psbt
437+
std::optional<std::vector<unsigned char>> decoded_psbt = DecodeBase64(psbt_string);
438+
QVERIFY(decoded_psbt);
439+
PartiallySignedTransaction psbt;
440+
std::string err;
441+
QVERIFY(DecodeRawPSBT(psbt, MakeByteSpan(*decoded_psbt), err));
442+
}
443+
372444
void TestGUI(interfaces::Node& node)
373445
{
374446
// Set up wallet and chain with 105 blocks (5 mature blocks for spending).
@@ -383,6 +455,10 @@ void TestGUI(interfaces::Node& node)
383455
// "Full" GUI tests, use descriptor wallet
384456
const std::shared_ptr<CWallet>& desc_wallet = SetupDescriptorsWallet(node, test);
385457
TestGUI(node, desc_wallet);
458+
459+
// Legacy watch-only wallet test
460+
// Verify PSBT creation.
461+
TestGUIWatchOnly(node, test);
386462
}
387463

388464
} // namespace

0 commit comments

Comments
 (0)