29 #include <validation.h>
36 #include <QAbstractButton>
38 #include <QApplication>
42 #include <QPushButton>
44 #include <QVBoxLayout>
47 #include <QDialogButtonBox>
62 void 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 uint256& 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));
100 QModelIndex FindTx(
const QAbstractItemModel& model,
const uint256& 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, {});
114 void BumpFee(
TransactionView& view,
const uint256& 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);
139 void 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());
199 wallet->SetupLegacyScriptPubKeyMan();
202 bool import_keys =
wallet->ImportPubKeys({pubKey.
GetID()}, {{pubKey.
GetID(), pubKey}} , {},
false,
false, 1);
204 wallet->SetLastBlockProcessed(105,
WITH_LOCK(
node.context()->chainman->GetMutex(),
return node.context()->chainman->ActiveChain().Tip()->GetBlockHash()));
216 wallet->SetupDescriptorScriptPubKeyMans();
224 if (!
wallet->AddWalletDescriptor(w_desc, provider,
"",
false))
assert(
false);
227 wallet->SetLastBlockProcessed(105,
WITH_LOCK(
node.context()->chainman->GetMutex(),
return node.context()->chainman->ActiveChain().Tip()->GetBlockHash()));
229 wallet->SetBroadcastTransactions(
true);
238 std::unique_ptr<ClientModel> clientModel;
239 std::unique_ptr<WalletModel> walletModel;
243 QVERIFY(optionsModel.
Init(error));
244 clientModel = std::make_unique<ClientModel>(
node, &optionsModel);
253 sendCoinsDialog.
setModel(walletModel.get());
254 transactionView.
setModel(walletModel.get());
276 MiniGUI mini_gui(
node, platformStyle.get());
277 mini_gui.initModelForWallet(
node,
wallet, platformStyle.get());
285 CompareBalance(walletModel, walletModel.
wallet().
getBalance(), sendCoinsDialog.findChild<QLabel*>(
"labelBalance"));
288 VerifyUseAvailableBalance(sendCoinsDialog, walletModel);
292 QCOMPARE(transactionTableModel->
rowCount({}), 105);
296 qApp->processEvents();
297 QCOMPARE(transactionTableModel->
rowCount({}), 107);
298 QVERIFY(FindTx(*transactionTableModel, txid1).isValid());
299 QVERIFY(FindTx(*transactionTableModel, txid2).isValid());
302 BumpFee(transactionView, txid1,
true,
"not BIP 125 replaceable",
false);
303 BumpFee(transactionView, txid2,
false, {},
true);
304 BumpFee(transactionView, txid2,
false, {},
false);
305 BumpFee(transactionView, txid2,
true,
"already bumped",
false);
309 overviewPage.setWalletModel(&walletModel);
311 CompareBalance(walletModel, walletModel.
wallet().
getBalance(), overviewPage.findChild<QLabel*>(
"labelBalance"));
315 receiveCoinsDialog.setModel(&walletModel);
319 QLineEdit* labelInput = receiveCoinsDialog.findChild<QLineEdit*>(
"reqLabel");
320 labelInput->setText(
"TEST_LABEL_1");
327 QLineEdit* messageInput = receiveCoinsDialog.findChild<QLineEdit*>(
"reqMessage");
328 messageInput->setText(
"TEST_MESSAGE_1");
329 int initialRowCount = requestTableModel->
rowCount({});
330 QPushButton* requestPaymentButton = receiveCoinsDialog.findChild<QPushButton*>(
"receiveButton");
331 requestPaymentButton->click();
333 for (QWidget* widget : QApplication::topLevelWidgets()) {
334 if (widget->inherits(
"ReceiveRequestDialog")) {
336 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"payment_header")->text(), QString(
"Payment information"));
337 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"uri_tag")->text(), QString(
"URI:"));
338 QString uri = receiveRequestDialog->QObject::findChild<QLabel*>(
"uri_content")->text();
339 QCOMPARE(uri.count(
"bitcoin:"), 2);
340 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"address_tag")->text(), QString(
"Address:"));
341 QVERIFY(address.isEmpty());
342 address = receiveRequestDialog->QObject::findChild<QLabel*>(
"address_content")->text();
343 QVERIFY(!address.isEmpty());
345 QCOMPARE(uri.count(
"amount=0.00000001"), 2);
346 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"amount_tag")->text(), QString(
"Amount:"));
347 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"amount_content")->text(), QString::fromStdString(
"0.00000001 " +
CURRENCY_UNIT));
349 QCOMPARE(uri.count(
"label=TEST_LABEL_1"), 2);
350 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"label_tag")->text(), QString(
"Label:"));
351 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"label_content")->text(), QString(
"TEST_LABEL_1"));
353 QCOMPARE(uri.count(
"message=TEST_MESSAGE_1"), 2);
354 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"message_tag")->text(), QString(
"Message:"));
355 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"message_content")->text(), QString(
"TEST_MESSAGE_1"));
360 QPushButton* clearButton = receiveCoinsDialog.findChild<QPushButton*>(
"clearButton");
361 clearButton->click();
362 QCOMPARE(labelInput->text(), QString(
""));
364 QCOMPARE(messageInput->text(), QString(
""));
367 int currentRowCount = requestTableModel->
rowCount({});
368 QCOMPARE(currentRowCount, initialRowCount+1);
372 QCOMPARE(requests.size(),
size_t{1});
376 QCOMPARE(entry.
id, int64_t{1});
377 QVERIFY(entry.
date.isValid());
386 QTableView* table = receiveCoinsDialog.findChild<QTableView*>(
"recentRequestsView");
387 table->selectRow(currentRowCount-1);
388 QPushButton* removeRequestButton = receiveCoinsDialog.findChild<QPushButton*>(
"removeRequestButton");
389 removeRequestButton->click();
390 QCOMPARE(requestTableModel->
rowCount({}), currentRowCount-1);
398 const std::shared_ptr<CWallet>&
wallet = SetupLegacyWatchOnlyWallet(
node, test);
402 MiniGUI mini_gui(
node, platformStyle.get());
403 mini_gui.initModelForWallet(
node,
wallet, platformStyle.get());
411 sendCoinsDialog.findChild<QLabel*>(
"labelBalance"));
418 timer.setInterval(500);
419 QObject::connect(&timer, &QTimer::timeout, [&](){
420 for (QWidget* widget : QApplication::topLevelWidgets()) {
421 if (widget->inherits(
"QMessageBox") && widget->objectName().compare(
"psbt_copied_message") == 0) {
422 QMessageBox* dialog = qobject_cast<QMessageBox*>(widget);
423 QAbstractButton* button = dialog->button(QMessageBox::Discard);
424 button->setEnabled(true);
434 SendCoins(*
wallet.get(), sendCoinsDialog,
PKHash(), 5 *
COIN,
false, QMessageBox::Save);
435 const std::string& psbt_string = QApplication::clipboard()->text().toStdString();
436 QVERIFY(!psbt_string.empty());
439 std::optional<std::vector<unsigned char>> decoded_psbt =
DecodeBase64(psbt_string);
440 QVERIFY(decoded_psbt);
450 for (
int i = 0; i < 5; ++i) {
458 const std::shared_ptr<CWallet>& desc_wallet = SetupDescriptorsWallet(
node, test);
459 TestGUI(
node, desc_wallet);
463 TestGUIWatchOnly(
node, test);
471 if (QApplication::platformName() ==
"minimal") {
476 QWARN(
"Skipping WalletTests on mac build with 'minimal' platform set due to Qt bugs. To run AppTests, invoke "
477 "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, 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.
An encapsulated public key.
CKeyID GetID() const
Get the KeyID of this public key (hash of its serialization)
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()
interfaces::Wallet & wallet() const
TransactionTableModel * getTransactionTableModel() const
OptionsModel * getOptionsModel() const
interfaces::WalletBalances getCachedBalance() const
interfaces::Node & m_node
std::string ToString() const
Top-level interface for a bitcoin node (bitcoind process).
virtual CoinsList listCoins()=0
virtual std::vector< std::string > getAddressReceiveRequests()=0
Get receive requests.
virtual CAmount getBalance()=0
Get balance.
virtual WalletBalances getBalances()=0
Get balances.
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 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)
std::shared_ptr< CWallet > wallet
CTxDestination GetDestinationForKey(const CPubKey &key, OutputType type)
Get a destination of the requested type (if possible) to the specified key.
bool DecodeRawPSBT(PartiallySignedTransaction &psbt, 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.
constexpr auto MakeUCharSpan(V &&v) -> decltype(UCharSpanCast(Span{std::forward< V >(v)}))
Like the Span constructor, but for (const) unsigned char member types only.
Span< const std::byte > MakeByteSpan(V &&v) noexcept
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...
CAmount watch_only_balance
interfaces::WalletLoader * wallet_loader
Reference to chain client that should used to load or create wallets opened by the gui.
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)