test,gui: add coverage for PSBT creation on legacy watch-only wallets
This commit is contained in:
2 files changed
+83
-7
No files matched your search
@@ -403,7 +403,7 @@ void SendCoinsDialog::presentPSBT(PartiallySignedTransaction& psbtx)
|
|||||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
||||||
ssTx << psbtx;
|
ssTx << psbtx;
|
||||||
GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
|
GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
|
||||||
QMessageBox msgBox;
|
QMessageBox msgBox(this);
|
||||||
//: Caption of "PSBT has been copied" messagebox
|
//: Caption of "PSBT has been copied" messagebox
|
||||||
msgBox.setText(tr("Unsigned Transaction", "PSBT copied"));
|
msgBox.setText(tr("Unsigned Transaction", "PSBT copied"));
|
||||||
msgBox.setInformativeText(tr("The PSBT has been copied to the clipboard. You can also save it."));
|
msgBox.setInformativeText(tr("The PSBT has been copied to the clipboard. You can also save it."));
|
||||||
|
|||||||
@@ -35,6 +35,8 @@
|
|||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
|
#include <QClipboard>
|
||||||
|
#include <QObject>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
@@ -47,6 +49,7 @@ using wallet::CWallet;
|
|||||||
using wallet::CreateMockWalletDatabase;
|
using wallet::CreateMockWalletDatabase;
|
||||||
using wallet::RemoveWallet;
|
using wallet::RemoveWallet;
|
||||||
using wallet::WALLET_FLAG_DESCRIPTORS;
|
using wallet::WALLET_FLAG_DESCRIPTORS;
|
||||||
|
using wallet::WALLET_FLAG_DISABLE_PRIVATE_KEYS;
|
||||||
using wallet::WalletContext;
|
using wallet::WalletContext;
|
||||||
using wallet::WalletDescriptor;
|
using wallet::WalletDescriptor;
|
||||||
using wallet::WalletRescanReserver;
|
using wallet::WalletRescanReserver;
|
||||||
@@ -54,14 +57,14 @@ using wallet::WalletRescanReserver;
|
|||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
//! Press "Yes" or "Cancel" buttons in modal send confirmation dialog.
|
//! 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()) {
|
for (QWidget* widget : QApplication::topLevelWidgets()) {
|
||||||
if (widget->inherits("SendConfirmationDialog")) {
|
if (widget->inherits("SendConfirmationDialog")) {
|
||||||
SendConfirmationDialog* dialog = qobject_cast<SendConfirmationDialog*>(widget);
|
SendConfirmationDialog* dialog = qobject_cast<SendConfirmationDialog*>(widget);
|
||||||
if (text) *text = dialog->text();
|
if (text) *text = dialog->text();
|
||||||
QAbstractButton* button = dialog->button(cancel ? QMessageBox::Cancel : QMessageBox::Yes);
|
QAbstractButton* button = dialog->button(confirm_type);
|
||||||
button->setEnabled(true);
|
button->setEnabled(true);
|
||||||
button->click();
|
button->click();
|
||||||
}
|
}
|
||||||
@@ -70,7 +73,8 @@ void ConfirmSend(QString* text = nullptr, bool cancel = false)
|
|||||||
}
|
}
|
||||||
|
|
||||||
//! Send coins to address and return txid.
|
//! 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<QVBoxLayout*>("entries");
|
QVBoxLayout* entries = sendCoinsDialog.findChild<QVBoxLayout*>("entries");
|
||||||
SendCoinsEntry* entry = qobject_cast<SendCoinsEntry*>(entries->itemAt(0)->widget());
|
SendCoinsEntry* entry = qobject_cast<SendCoinsEntry*>(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) {
|
boost::signals2::scoped_connection c(wallet.NotifyTransactionChanged.connect([&txid](const uint256& hash, ChangeType status) {
|
||||||
if (status == CT_NEW) txid = hash;
|
if (status == CT_NEW) txid = hash;
|
||||||
}));
|
}));
|
||||||
ConfirmSend();
|
ConfirmSend(/*text=*/nullptr, confirm_type);
|
||||||
bool invoked = QMetaObject::invokeMethod(&sendCoinsDialog, "sendButtonClicked", Q_ARG(bool, false));
|
bool invoked = QMetaObject::invokeMethod(&sendCoinsDialog, "sendButtonClicked", Q_ARG(bool, false));
|
||||||
assert(invoked);
|
assert(invoked);
|
||||||
return txid;
|
return txid;
|
||||||
@@ -122,7 +126,7 @@ void BumpFee(TransactionView& view, const uint256& txid, bool expectDisabled, st
|
|||||||
action->setEnabled(true);
|
action->setEnabled(true);
|
||||||
QString text;
|
QString text;
|
||||||
if (expectError.empty()) {
|
if (expectError.empty()) {
|
||||||
ConfirmSend(&text, cancel);
|
ConfirmSend(&text, cancel ? QMessageBox::Cancel : QMessageBox::Yes);
|
||||||
} else {
|
} else {
|
||||||
ConfirmMessage(&text, 0ms);
|
ConfirmMessage(&text, 0ms);
|
||||||
}
|
}
|
||||||
@@ -183,6 +187,24 @@ void SyncUpWallet(const std::shared_ptr<CWallet>& wallet, interfaces::Node& node
|
|||||||
QVERIFY(result.last_failed_block.IsNull());
|
QVERIFY(result.last_failed_block.IsNull());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<CWallet> SetupLegacyWatchOnlyWallet(interfaces::Node& node, TestChain100Setup& test)
|
||||||
|
{
|
||||||
|
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(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<CWallet> SetupDescriptorsWallet(interfaces::Node& node, TestChain100Setup& test)
|
std::shared_ptr<CWallet> SetupDescriptorsWallet(interfaces::Node& node, TestChain100Setup& test)
|
||||||
{
|
{
|
||||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(node.context()->chain.get(), "", CreateMockWalletDatabase());
|
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)
|
|||||||
QCOMPARE(walletModel.wallet().getAddressReceiveRequests().size(), size_t{0});
|
QCOMPARE(walletModel.wallet().getAddressReceiveRequests().size(), size_t{0});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestGUIWatchOnly(interfaces::Node& node, TestChain100Setup& test)
|
||||||
|
{
|
||||||
|
const std::shared_ptr<CWallet>& wallet = SetupLegacyWatchOnlyWallet(node, test);
|
||||||
|
|
||||||
|
// Create widgets and init models
|
||||||
|
std::unique_ptr<const PlatformStyle> 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<QLabel*>("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<QMessageBox*>(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<std::vector<unsigned char>> 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)
|
void TestGUI(interfaces::Node& node)
|
||||||
{
|
{
|
||||||
// Set up wallet and chain with 105 blocks (5 mature blocks for spending).
|
// 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
|
// "Full" GUI tests, use descriptor wallet
|
||||||
const std::shared_ptr<CWallet>& desc_wallet = SetupDescriptorsWallet(node, test);
|
const std::shared_ptr<CWallet>& desc_wallet = SetupDescriptorsWallet(node, test);
|
||||||
TestGUI(node, desc_wallet);
|
TestGUI(node, desc_wallet);
|
||||||
|
|
||||||
|
// Legacy watch-only wallet test
|
||||||
|
// Verify PSBT creation.
|
||||||
|
TestGUIWatchOnly(node, test);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
Reference in new issue
Block a user