35
35
#include < QAction>
36
36
#include < QApplication>
37
37
#include < QCheckBox>
38
+ #include < QClipboard>
39
+ #include < QObject>
38
40
#include < QPushButton>
39
41
#include < QTimer>
40
42
#include < QVBoxLayout>
@@ -47,21 +49,22 @@ using wallet::CWallet;
47
49
using wallet::CreateMockWalletDatabase;
48
50
using wallet::RemoveWallet;
49
51
using wallet::WALLET_FLAG_DESCRIPTORS;
52
+ using wallet::WALLET_FLAG_DISABLE_PRIVATE_KEYS;
50
53
using wallet::WalletContext;
51
54
using wallet::WalletDescriptor;
52
55
using wallet::WalletRescanReserver;
53
56
54
57
namespace
55
58
{
56
59
// ! 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 )
58
61
{
59
- QTimer::singleShot (0 , [text, cancel ]() {
62
+ QTimer::singleShot (0 , [text, confirm_type ]() {
60
63
for (QWidget* widget : QApplication::topLevelWidgets ()) {
61
64
if (widget->inherits (" SendConfirmationDialog" )) {
62
65
SendConfirmationDialog* dialog = qobject_cast<SendConfirmationDialog*>(widget);
63
66
if (text) *text = dialog->text ();
64
- QAbstractButton* button = dialog->button (cancel ? QMessageBox::Cancel : QMessageBox::Yes );
67
+ QAbstractButton* button = dialog->button (confirm_type );
65
68
button->setEnabled (true );
66
69
button->click ();
67
70
}
@@ -70,7 +73,8 @@ void ConfirmSend(QString* text = nullptr, bool cancel = false)
70
73
}
71
74
72
75
// ! 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)
74
78
{
75
79
QVBoxLayout* entries = sendCoinsDialog.findChild <QVBoxLayout*>(" entries" );
76
80
SendCoinsEntry* entry = qobject_cast<SendCoinsEntry*>(entries->itemAt (0 )->widget ());
@@ -84,7 +88,7 @@ uint256 SendCoins(CWallet& wallet, SendCoinsDialog& sendCoinsDialog, const CTxDe
84
88
boost::signals2::scoped_connection c (wallet.NotifyTransactionChanged .connect ([&txid](const uint256& hash, ChangeType status) {
85
89
if (status == CT_NEW) txid = hash;
86
90
}));
87
- ConfirmSend ();
91
+ ConfirmSend (/* text= */ nullptr , confirm_type );
88
92
bool invoked = QMetaObject::invokeMethod (&sendCoinsDialog, " sendButtonClicked" , Q_ARG (bool , false ));
89
93
assert (invoked);
90
94
return txid;
@@ -122,7 +126,7 @@ void BumpFee(TransactionView& view, const uint256& txid, bool expectDisabled, st
122
126
action->setEnabled (true );
123
127
QString text;
124
128
if (expectError.empty ()) {
125
- ConfirmSend (&text, cancel);
129
+ ConfirmSend (&text, cancel ? QMessageBox::Cancel : QMessageBox::Yes );
126
130
} else {
127
131
ConfirmMessage (&text, 0ms);
128
132
}
@@ -183,6 +187,24 @@ void SyncUpWallet(const std::shared_ptr<CWallet>& wallet, interfaces::Node& node
183
187
QVERIFY (result.last_failed_block .IsNull ());
184
188
}
185
189
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
+
186
208
std::shared_ptr<CWallet> SetupDescriptorsWallet (interfaces::Node& node, TestChain100Setup& test)
187
209
{
188
210
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)
369
391
QCOMPARE (walletModel.wallet ().getAddressReceiveRequests ().size (), size_t {0 });
370
392
}
371
393
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
+
372
444
void TestGUI (interfaces::Node& node)
373
445
{
374
446
// Set up wallet and chain with 105 blocks (5 mature blocks for spending).
@@ -383,6 +455,10 @@ void TestGUI(interfaces::Node& node)
383
455
// "Full" GUI tests, use descriptor wallet
384
456
const std::shared_ptr<CWallet>& desc_wallet = SetupDescriptorsWallet (node, test);
385
457
TestGUI (node, desc_wallet);
458
+
459
+ // Legacy watch-only wallet test
460
+ // Verify PSBT creation.
461
+ TestGUIWatchOnly (node, test);
386
462
}
387
463
388
464
} // namespace
0 commit comments