29#include <validation.h>
36#include <QAbstractButton>
38#include <QApplication>
47#include <QDialogButtonBox>
62void ConfirmSend(QString* text =
nullptr, QMessageBox::StandardButton confirm_type = QMessageBox::Yes)
64 QTimer::singleShot(0, [text, confirm_type]() {
65 for (QWidget* widget : QApplication::topLevelWidgets()) {
66 if (widget->inherits(
"SendConfirmationDialog")) {
67 SendConfirmationDialog* dialog = qobject_cast<SendConfirmationDialog*>(widget);
68 if (text) *text = dialog->text();
69 QAbstractButton* button = dialog->button(confirm_type);
70 button->setEnabled(true);
79 QMessageBox::StandardButton confirm_type = QMessageBox::Yes)
81 QVBoxLayout* entries = sendCoinsDialog.findChild<QVBoxLayout*>(
"entries");
82 SendCoinsEntry* entry = qobject_cast<SendCoinsEntry*>(entries->itemAt(0)->widget());
87 if (status == CT_NEW) txid = hash;
89 ConfirmSend(
nullptr, confirm_type);
90 bool invoked = QMetaObject::invokeMethod(&sendCoinsDialog,
"sendButtonClicked", Q_ARG(
bool,
false));
96QModelIndex FindTx(
const QAbstractItemModel& model,
const Txid& txid)
98 QString hash = QString::fromStdString(txid.
ToString());
99 int rows = model.rowCount({});
100 for (
int row = 0; row < rows; ++row) {
101 QModelIndex index = model.index(row, 0, {});
110void BumpFee(
TransactionView& view,
const Txid& txid,
bool expectDisabled, std::string expectError,
bool cancel)
112 QTableView* table = view.findChild<QTableView*>(
"transactionView");
113 QModelIndex index = FindTx(*table->selectionModel()->model(), txid);
114 QVERIFY2(index.isValid(),
"Could not find BumpFee txid");
118 QAction* action = view.findChild<QAction*>(
"bumpFeeAction");
119 table->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
120 action->setEnabled(expectDisabled);
121 table->customContextMenuRequested({});
122 QCOMPARE(action->isEnabled(), !expectDisabled);
124 action->setEnabled(
true);
126 if (expectError.empty()) {
127 ConfirmSend(&text, cancel ? QMessageBox::Cancel : QMessageBox::Yes);
132 QVERIFY(text.indexOf(QString::fromStdString(expectError)) != -1);
135void CompareBalance(
WalletModel& walletModel,
CAmount expected_balance, QLabel* balance_label_to_check)
139 QCOMPARE(balance_label_to_check->text().trimmed(), balanceComparison);
150 QVBoxLayout* entries = sendCoinsDialog.findChild<QVBoxLayout*>(
"entries");
151 QVERIFY(entries->count() == 1);
152 SendCoinsEntry* send_entry = qobject_cast<SendCoinsEntry*>(entries->itemAt(0)->widget());
160 int COINS_TO_SELECT = 2;
162 CAmount sum_selected_coins = 0;
164 QVERIFY(coins.size() == 1);
165 for (
const auto& [outpoint, tx_out] : coins.begin()->second) {
167 sum_selected_coins += tx_out.txout.nValue;
168 if (++selected == COINS_TO_SELECT)
break;
170 QVERIFY(selected == COINS_TO_SELECT);
182 CWallet::ScanResult result =
wallet->ScanForWalletTransactions(
Params().GetConsensus().hashGenesisBlock, 0, {}, reserver,
false);
183 QCOMPARE(result.status, CWallet::ScanResult::SUCCESS);
184 QCOMPARE(result.last_scanned_block,
WITH_LOCK(
node.context()->chainman->GetMutex(),
return node.context()->chainman->ActiveChain().Tip()->GetBlockHash()));
185 QVERIFY(result.last_failed_block.IsNull());
196 wallet->SetupDescriptorScriptPubKeyMans();
208 auto descs =
Parse(
"combo(" + key_str +
")", provider, error,
false);
210 assert(descs.size() == 1);
211 auto& desc = descs.at(0);
213 Assert(
wallet->AddWalletDescriptor(w_desc, provider,
"",
false));
216 wallet->SetLastBlockProcessed(105,
WITH_LOCK(
node.context()->chainman->GetMutex(),
return node.context()->chainman->ActiveChain().Tip()->GetBlockHash()));
218 wallet->SetBroadcastTransactions(
true);
227 std::unique_ptr<ClientModel> clientModel;
228 std::unique_ptr<WalletModel> walletModel;
232 QVERIFY(optionsModel.
Init(error));
233 clientModel = std::make_unique<ClientModel>(
node, &optionsModel);
242 sendCoinsDialog.
setModel(walletModel.get());
243 transactionView.
setModel(walletModel.get());
265 MiniGUI mini_gui(
node, platformStyle.get());
266 mini_gui.initModelForWallet(
node,
wallet, platformStyle.get());
274 CompareBalance(walletModel, walletModel.
wallet().
getBalance(), sendCoinsDialog.findChild<QLabel*>(
"labelBalance"));
277 VerifyUseAvailableBalance(sendCoinsDialog, walletModel);
281 QCOMPARE(transactionTableModel->
rowCount({}), 105);
285 qApp->processEvents();
286 QCOMPARE(transactionTableModel->
rowCount({}), 107);
287 QVERIFY(FindTx(*transactionTableModel, txid1).isValid());
288 QVERIFY(FindTx(*transactionTableModel, txid2).isValid());
291 BumpFee(transactionView, txid1,
false, {},
true);
292 BumpFee(transactionView, txid2,
false, {},
true);
293 BumpFee(transactionView, txid2,
false, {},
false);
294 BumpFee(transactionView, txid2,
true,
"already bumped",
false);
298 overviewPage.setWalletModel(&walletModel);
300 CompareBalance(walletModel, walletModel.
wallet().
getBalance(), overviewPage.findChild<QLabel*>(
"labelBalance"));
304 receiveCoinsDialog.setModel(&walletModel);
308 QLineEdit* labelInput = receiveCoinsDialog.findChild<QLineEdit*>(
"reqLabel");
309 labelInput->setText(
"TEST_LABEL_1");
316 QLineEdit* messageInput = receiveCoinsDialog.findChild<QLineEdit*>(
"reqMessage");
317 messageInput->setText(
"TEST_MESSAGE_1");
318 int initialRowCount = requestTableModel->
rowCount({});
319 QPushButton* requestPaymentButton = receiveCoinsDialog.findChild<QPushButton*>(
"receiveButton");
320 requestPaymentButton->click();
322 for (QWidget* widget : QApplication::topLevelWidgets()) {
323 if (widget->inherits(
"ReceiveRequestDialog")) {
325 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"payment_header")->text(), QString(
"Payment information"));
326 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"uri_tag")->text(), QString(
"URI:"));
327 QString uri = receiveRequestDialog->QObject::findChild<QLabel*>(
"uri_content")->text();
328 QCOMPARE(uri.count(
"bitcoin:"), 2);
329 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"address_tag")->text(), QString(
"Address:"));
330 QVERIFY(address.isEmpty());
331 address = receiveRequestDialog->QObject::findChild<QLabel*>(
"address_content")->text();
332 QVERIFY(!address.isEmpty());
334 QCOMPARE(uri.count(
"amount=0.00000001"), 2);
335 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"amount_tag")->text(), QString(
"Amount:"));
336 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"amount_content")->text(), QString::fromStdString(
"0.00000001 " +
CURRENCY_UNIT));
338 QCOMPARE(uri.count(
"label=TEST_LABEL_1"), 2);
339 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"label_tag")->text(), QString(
"Label:"));
340 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"label_content")->text(), QString(
"TEST_LABEL_1"));
342 QCOMPARE(uri.count(
"message=TEST_MESSAGE_1"), 2);
343 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"message_tag")->text(), QString(
"Message:"));
344 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"message_content")->text(), QString(
"TEST_MESSAGE_1"));
349 QPushButton* clearButton = receiveCoinsDialog.findChild<QPushButton*>(
"clearButton");
350 clearButton->click();
351 QCOMPARE(labelInput->text(), QString(
""));
353 QCOMPARE(messageInput->text(), QString(
""));
356 int currentRowCount = requestTableModel->
rowCount({});
357 QCOMPARE(currentRowCount, initialRowCount+1);
361 QCOMPARE(requests.size(),
size_t{1});
365 QCOMPARE(entry.
id, int64_t{1});
366 QVERIFY(entry.
date.isValid());
375 QTableView* table = receiveCoinsDialog.findChild<QTableView*>(
"recentRequestsView");
376 table->selectRow(currentRowCount-1);
377 QPushButton* removeRequestButton = receiveCoinsDialog.findChild<QPushButton*>(
"removeRequestButton");
378 removeRequestButton->click();
379 QCOMPARE(requestTableModel->
rowCount({}), currentRowCount-1);
387 const std::shared_ptr<CWallet>&
wallet = SetupDescriptorsWallet(
node, test,
true);
391 MiniGUI mini_gui(
node, platformStyle.get());
392 mini_gui.initModelForWallet(
node,
wallet, platformStyle.get());
400 sendCoinsDialog.findChild<QLabel*>(
"labelBalance"));
407 timer.setInterval(500);
408 QObject::connect(&timer, &QTimer::timeout, [&](){
409 for (QWidget* widget : QApplication::topLevelWidgets()) {
410 if (widget->inherits(
"QMessageBox") && widget->objectName().compare(
"psbt_copied_message") == 0) {
411 QMessageBox* dialog = qobject_cast<QMessageBox*>(widget);
412 QAbstractButton* button = dialog->button(QMessageBox::Discard);
413 button->setEnabled(true);
423 SendCoins(*
wallet.get(), sendCoinsDialog,
PKHash(), 5 *
COIN, QMessageBox::Save);
424 const std::string& psbt_string = QApplication::clipboard()->text().toStdString();
425 QVERIFY(!psbt_string.empty());
428 std::optional<std::vector<unsigned char>> decoded_psbt =
DecodeBase64(psbt_string);
429 QVERIFY(decoded_psbt);
438 for (
int i = 0; i < 5; ++i) {
446 const std::shared_ptr<CWallet>& desc_wallet = SetupDescriptorsWallet(
node, test);
447 TestGUI(
node, desc_wallet);
451 TestGUIWatchOnly(
node, test);
459 if (QApplication::platformName() ==
"minimal") {
464 qWarning() <<
"Skipping WalletTests on mac build with 'minimal' platform set due to Qt bugs. To run AppTests, invoke "
465 "with 'QT_QPA_PLATFORM=cocoa test_bitcoin-qt' on mac, or else use a linux or windows build.";
std::variant< CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, PayToAnchor, WitnessUnknown > CTxDestination
A txout script categorized into standard templates.
int64_t CAmount
Amount in satoshis (Can be negative)
static constexpr CAmount COIN
The amount of satoshis in one BTC.
const CChainParams & Params()
Return the currently selected parameters.
#define Assert(val)
Identity function.
Widget for entering bitcoin amounts.
void setValue(const CAmount &value)
static QString formatWithUnit(Unit unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as string (with unit)
CPubKey GetPubKey() const
Compute the public key from a private key.
Interface from Qt to configuration data structure for Bitcoin client.
bool Init(bilingual_str &error)
BitcoinUnit getDisplayUnit() const
Overview ("home") page widget.
Line edit that can be marked as "invalid" to show input validation feedback.
Dialog for requesting payment of bitcoins.
SendCoinsRecipient recipient
Model for list of recently generated payment requests / bitcoin: URIs.
int rowCount(const QModelIndex &parent) const override
Dialog for sending bitcoins.
void setModel(WalletModel *model)
wallet::CCoinControl * getCoinControl()
A single entry in the dialog for sending bitcoins.
void useAvailableBalance(SendCoinsEntry *entry)
SendCoinsRecipient getValue()
std::string sPaymentRequest
QString authenticatedMerchant
Minimal stream for reading from an existing byte array by std::span.
UI model for the transaction table of a wallet.
@ TxHashRole
Transaction hash.
int rowCount(const QModelIndex &parent) const override
Widget showing the transaction list for a wallet, including a filter row.
void setModel(WalletModel *model)
QTableView * transactionView
Interface to Bitcoin wallet from Qt view code.
RecentRequestsTableModel * getRecentRequestsTableModel() const
void pollBalanceChanged()
TransactionTableModel * getTransactionTableModel() const
interfaces::Wallet & wallet() const
OptionsModel * getOptionsModel() const
interfaces::WalletBalances getCachedBalance() const
interfaces::Node & m_node
Top-level interface for a bitcoin node (bitcoind process).
virtual CoinsList listCoins()=0
virtual CAmount getBalance()=0
Get balance.
virtual WalletBalances getBalances()=0
Get balances.
virtual std::vector< std::string > getAddressReceiveRequests()=0
Get receive requests.
std::string ToString() const
PreselectedInput & Select(const COutPoint &outpoint)
Lock-in the given output for spending.
CTxDestination destChange
Custom change destination, if not set an address is generated.
A CWallet maintains a set of transactions and balances, and provides the ability to create new transa...
Descriptor with some wallet metadata.
RAII object to check and reserve a wallet rescan.
static UniValue Parse(std::string_view raw, ParamFormat format=ParamFormat::JSON)
Parse string to UniValue or throw runtime_error if string contains invalid JSON.
const std::string CURRENCY_UNIT
std::string HexStr(const std::span< const uint8_t > s)
Convert a span of bytes to a lower-case hexadecimal string.
std::string EncodeSecret(const CKey &key)
std::string EncodeDestination(const CTxDestination &dest)
std::unique_ptr< WalletLoader > MakeWalletLoader(Chain &chain, ArgsManager &args)
Return implementation of ChainClient interface for a wallet loader.
std::unique_ptr< Wallet > MakeWallet(wallet::WalletContext &context, const std::shared_ptr< wallet::CWallet > &wallet)
Return implementation of Wallet interface.
std::unique_ptr< WalletDatabase > CreateMockableWalletDatabase()
bool AddWallet(WalletContext &context, const std::shared_ptr< CWallet > &wallet)
@ WALLET_FLAG_DESCRIPTORS
Indicate that this wallet supports DescriptorScriptPubKeyMan.
@ WALLET_FLAG_DISABLE_PRIVATE_KEYS
bool RemoveWallet(WalletContext &context, const std::shared_ptr< CWallet > &wallet, std::optional< bool > load_on_start, std::vector< bilingual_str > &warnings)
util::Result< PartiallySignedTransaction > DecodeRawPSBT(std::span< const std::byte > tx_data)
Decode a raw (binary blob) PSBT into a PartiallySignedTransaction.
void ConfirmMessage(QString *text, std::chrono::milliseconds msec)
Press "Ok" button in message box dialog.
CScript GetScriptForRawPubKey(const CPubKey &pubKey)
Generate a P2PK script for the given pubkey.
auto MakeByteSpan(const V &v) noexcept
Testing fixture that pre-creates a 100-block REGTEST-mode block chain.
CBlock CreateAndProcessBlock(const std::vector< CMutableTransaction > &txns, const CScript &scriptPubKey)
Create a new block with just given transactions, coinbase paying to scriptPubKey, and try to add it t...
interfaces::WalletLoader * wallet_loader
std::unique_ptr< interfaces::Chain > chain
WalletContext struct containing references to state shared between CWallet instances,...
#define WITH_LOCK(cs, code)
Run code while locking a mutex.
ChangeType
General change type (added, updated, removed).
std::optional< std::vector< unsigned char > > DecodeBase64(std::string_view str)