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());
85 sendCoinsDialog.findChild<QFrame*>(
"frameFee")
86 ->findChild<QFrame*>(
"frameFeeSelection")
87 ->findChild<QCheckBox*>(
"optInRBF")
88 ->setCheckState(rbf ? Qt::Checked : Qt::Unchecked);
90 boost::signals2::scoped_connection c(
wallet.NotifyTransactionChanged.connect([&txid](
const Txid& hash,
ChangeType status) {
91 if (status == CT_NEW) txid = hash;
93 ConfirmSend(
nullptr, confirm_type);
94 bool invoked = QMetaObject::invokeMethod(&sendCoinsDialog,
"sendButtonClicked", Q_ARG(
bool,
false));
100QModelIndex FindTx(
const QAbstractItemModel& model,
const Txid& txid)
102 QString hash = QString::fromStdString(txid.
ToString());
103 int rows = model.rowCount({});
104 for (
int row = 0; row < rows; ++row) {
105 QModelIndex index = model.index(row, 0, {});
114void BumpFee(
TransactionView& view,
const Txid& txid,
bool expectDisabled, std::string expectError,
bool cancel)
116 QTableView* table = view.findChild<QTableView*>(
"transactionView");
117 QModelIndex index = FindTx(*table->selectionModel()->model(), txid);
118 QVERIFY2(index.isValid(),
"Could not find BumpFee txid");
122 QAction* action = view.findChild<QAction*>(
"bumpFeeAction");
123 table->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
124 action->setEnabled(expectDisabled);
125 table->customContextMenuRequested({});
126 QCOMPARE(action->isEnabled(), !expectDisabled);
128 action->setEnabled(
true);
130 if (expectError.empty()) {
131 ConfirmSend(&text, cancel ? QMessageBox::Cancel : QMessageBox::Yes);
136 QVERIFY(text.indexOf(QString::fromStdString(expectError)) != -1);
139void CompareBalance(
WalletModel& walletModel,
CAmount expected_balance, QLabel* balance_label_to_check)
143 QCOMPARE(balance_label_to_check->text().trimmed(), balanceComparison);
154 QVBoxLayout* entries = sendCoinsDialog.findChild<QVBoxLayout*>(
"entries");
155 QVERIFY(entries->count() == 1);
156 SendCoinsEntry* send_entry = qobject_cast<SendCoinsEntry*>(entries->itemAt(0)->widget());
164 int COINS_TO_SELECT = 2;
166 CAmount sum_selected_coins = 0;
168 QVERIFY(coins.size() == 1);
169 for (
const auto& [outpoint, tx_out] : coins.begin()->second) {
171 sum_selected_coins += tx_out.txout.nValue;
172 if (++selected == COINS_TO_SELECT)
break;
174 QVERIFY(selected == COINS_TO_SELECT);
186 CWallet::ScanResult result =
wallet->ScanForWalletTransactions(
Params().GetConsensus().hashGenesisBlock, 0, {}, reserver,
true,
false);
187 QCOMPARE(result.status, CWallet::ScanResult::SUCCESS);
188 QCOMPARE(result.last_scanned_block,
WITH_LOCK(
node.context()->chainman->GetMutex(),
return node.context()->chainman->ActiveChain().Tip()->GetBlockHash()));
189 QVERIFY(result.last_failed_block.IsNull());
201 wallet->SetupDescriptorScriptPubKeyMans();
213 auto descs =
Parse(
"combo(" + key_str +
")", provider, error,
false);
215 assert(descs.size() == 1);
216 auto& desc = descs.at(0);
218 Assert(
wallet->AddWalletDescriptor(w_desc, provider,
"",
false));
221 wallet->SetLastBlockProcessed(105,
WITH_LOCK(
node.context()->chainman->GetMutex(),
return node.context()->chainman->ActiveChain().Tip()->GetBlockHash()));
223 wallet->SetBroadcastTransactions(
true);
232 std::unique_ptr<ClientModel> clientModel;
233 std::unique_ptr<WalletModel> walletModel;
237 QVERIFY(optionsModel.
Init(error));
238 clientModel = std::make_unique<ClientModel>(
node, &optionsModel);
247 sendCoinsDialog.
setModel(walletModel.get());
248 transactionView.
setModel(walletModel.get());
270 MiniGUI mini_gui(
node, platformStyle.get());
271 mini_gui.initModelForWallet(
node,
wallet, platformStyle.get());
279 CompareBalance(walletModel, walletModel.
wallet().
getBalance(), sendCoinsDialog.findChild<QLabel*>(
"labelBalance"));
282 VerifyUseAvailableBalance(sendCoinsDialog, walletModel);
286 QCOMPARE(transactionTableModel->
rowCount({}), 105);
290 qApp->processEvents();
291 QCOMPARE(transactionTableModel->
rowCount({}), 107);
292 QVERIFY(FindTx(*transactionTableModel, txid1).isValid());
293 QVERIFY(FindTx(*transactionTableModel, txid2).isValid());
296 BumpFee(transactionView, txid1,
false, {},
true);
297 BumpFee(transactionView, txid2,
false, {},
true);
298 BumpFee(transactionView, txid2,
false, {},
false);
299 BumpFee(transactionView, txid2,
true,
"already bumped",
false);
303 overviewPage.setWalletModel(&walletModel);
305 CompareBalance(walletModel, walletModel.
wallet().
getBalance(), overviewPage.findChild<QLabel*>(
"labelBalance"));
309 receiveCoinsDialog.setModel(&walletModel);
313 QLineEdit* labelInput = receiveCoinsDialog.findChild<QLineEdit*>(
"reqLabel");
314 labelInput->setText(
"TEST_LABEL_1");
321 QLineEdit* messageInput = receiveCoinsDialog.findChild<QLineEdit*>(
"reqMessage");
322 messageInput->setText(
"TEST_MESSAGE_1");
323 int initialRowCount = requestTableModel->
rowCount({});
324 QPushButton* requestPaymentButton = receiveCoinsDialog.findChild<QPushButton*>(
"receiveButton");
325 requestPaymentButton->click();
327 for (QWidget* widget : QApplication::topLevelWidgets()) {
328 if (widget->inherits(
"ReceiveRequestDialog")) {
330 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"payment_header")->text(), QString(
"Payment information"));
331 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"uri_tag")->text(), QString(
"URI:"));
332 QString uri = receiveRequestDialog->QObject::findChild<QLabel*>(
"uri_content")->text();
333 QCOMPARE(uri.count(
"bitcoin:"), 2);
334 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"address_tag")->text(), QString(
"Address:"));
335 QVERIFY(address.isEmpty());
336 address = receiveRequestDialog->QObject::findChild<QLabel*>(
"address_content")->text();
337 QVERIFY(!address.isEmpty());
339 QCOMPARE(uri.count(
"amount=0.00000001"), 2);
340 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"amount_tag")->text(), QString(
"Amount:"));
341 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"amount_content")->text(), QString::fromStdString(
"0.00000001 " +
CURRENCY_UNIT));
343 QCOMPARE(uri.count(
"label=TEST_LABEL_1"), 2);
344 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"label_tag")->text(), QString(
"Label:"));
345 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"label_content")->text(), QString(
"TEST_LABEL_1"));
347 QCOMPARE(uri.count(
"message=TEST_MESSAGE_1"), 2);
348 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"message_tag")->text(), QString(
"Message:"));
349 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"message_content")->text(), QString(
"TEST_MESSAGE_1"));
354 QPushButton* clearButton = receiveCoinsDialog.findChild<QPushButton*>(
"clearButton");
355 clearButton->click();
356 QCOMPARE(labelInput->text(), QString(
""));
358 QCOMPARE(messageInput->text(), QString(
""));
361 int currentRowCount = requestTableModel->
rowCount({});
362 QCOMPARE(currentRowCount, initialRowCount+1);
366 QCOMPARE(requests.size(),
size_t{1});
370 QCOMPARE(entry.
id, int64_t{1});
371 QVERIFY(entry.
date.isValid());
380 QTableView* table = receiveCoinsDialog.findChild<QTableView*>(
"recentRequestsView");
381 table->selectRow(currentRowCount-1);
382 QPushButton* removeRequestButton = receiveCoinsDialog.findChild<QPushButton*>(
"removeRequestButton");
383 removeRequestButton->click();
384 QCOMPARE(requestTableModel->
rowCount({}), currentRowCount-1);
392 const std::shared_ptr<CWallet>&
wallet = SetupDescriptorsWallet(
node, test,
true);
396 MiniGUI mini_gui(
node, platformStyle.get());
397 mini_gui.initModelForWallet(
node,
wallet, platformStyle.get());
405 sendCoinsDialog.findChild<QLabel*>(
"labelBalance"));
412 timer.setInterval(500);
413 QObject::connect(&timer, &QTimer::timeout, [&](){
414 for (QWidget* widget : QApplication::topLevelWidgets()) {
415 if (widget->inherits(
"QMessageBox") && widget->objectName().compare(
"psbt_copied_message") == 0) {
416 QMessageBox* dialog = qobject_cast<QMessageBox*>(widget);
417 QAbstractButton* button = dialog->button(QMessageBox::Discard);
418 button->setEnabled(true);
428 SendCoins(*
wallet.get(), sendCoinsDialog,
PKHash(), 5 *
COIN,
false, QMessageBox::Save);
429 const std::string& psbt_string = QApplication::clipboard()->text().toStdString();
430 QVERIFY(!psbt_string.empty());
433 std::optional<std::vector<unsigned char>> decoded_psbt =
DecodeBase64(psbt_string);
434 QVERIFY(decoded_psbt);
444 for (
int i = 0; i < 5; ++i) {
452 const std::shared_ptr<CWallet>& desc_wallet = SetupDescriptorsWallet(
node, test);
453 TestGUI(
node, desc_wallet);
457 TestGUIWatchOnly(
node, test);
465 if (QApplication::platformName() ==
"minimal") {
470 qWarning() <<
"Skipping WalletTests on mac build with 'minimal' platform set due to Qt bugs. To run AppTests, invoke "
471 "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.
Double ended buffer combining vector and stream-like interfaces.
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
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)
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(MockableData records)
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)
bool DecodeRawPSBT(PartiallySignedTransaction &psbt, std::span< const std::byte > tx_data, std::string &error)
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
constexpr auto MakeUCharSpan(const V &v) -> decltype(UCharSpanCast(std::span{v}))
Like the std::span constructor, but for (const) unsigned char member types only.
A version of CTransaction with the PSBT format.
Testing fixture that pre-creates a 100-block REGTEST-mode block chain.
CBlock CreateAndProcessBlock(const std::vector< CMutableTransaction > &txns, const CScript &scriptPubKey, Chainstate *chainstate=nullptr)
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)