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")) {
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");
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));
100 QModelIndex 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, {});
114 void 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);
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);
157 QVERIFY(send_entry->getValue().amount == 0);
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);
178 Q_EMIT send_entry->useAvailableBalance(send_entry);
179 QVERIFY(send_entry->getValue().amount == sum_selected_coins);
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());
200 wallet->SetupDescriptorScriptPubKeyMans();
212 auto descs =
Parse(
"combo(" + key_str +
")", provider, error,
false);
214 assert(descs.size() == 1);
215 auto& desc = descs.at(0);
217 Assert(
wallet->AddWalletDescriptor(w_desc, provider,
"",
false));
220 wallet->SetLastBlockProcessed(105,
WITH_LOCK(
node.context()->chainman->GetMutex(),
return node.context()->chainman->ActiveChain().Tip()->GetBlockHash()));
222 wallet->SetBroadcastTransactions(
true);
231 std::unique_ptr<ClientModel> clientModel;
232 std::unique_ptr<WalletModel> walletModel;
236 QVERIFY(optionsModel.
Init(error));
237 clientModel = std::make_unique<ClientModel>(
node, &optionsModel);
246 sendCoinsDialog.
setModel(walletModel.get());
247 transactionView.
setModel(walletModel.get());
269 MiniGUI mini_gui(
node, platformStyle.get());
270 mini_gui.initModelForWallet(
node,
wallet, platformStyle.get());
278 CompareBalance(walletModel, walletModel.
wallet().
getBalance(), sendCoinsDialog.findChild<QLabel*>(
"labelBalance"));
281 VerifyUseAvailableBalance(sendCoinsDialog, walletModel);
285 QCOMPARE(transactionTableModel->
rowCount({}), 105);
289 qApp->processEvents();
290 QCOMPARE(transactionTableModel->
rowCount({}), 107);
291 QVERIFY(FindTx(*transactionTableModel, txid1).isValid());
292 QVERIFY(FindTx(*transactionTableModel, txid2).isValid());
295 BumpFee(transactionView, txid1,
false, {},
true);
296 BumpFee(transactionView, txid2,
false, {},
true);
297 BumpFee(transactionView, txid2,
false, {},
false);
298 BumpFee(transactionView, txid2,
true,
"already bumped",
false);
304 CompareBalance(walletModel, walletModel.
wallet().
getBalance(), overviewPage.findChild<QLabel*>(
"labelBalance"));
308 receiveCoinsDialog.
setModel(&walletModel);
312 QLineEdit* labelInput = receiveCoinsDialog.findChild<QLineEdit*>(
"reqLabel");
313 labelInput->setText(
"TEST_LABEL_1");
320 QLineEdit* messageInput = receiveCoinsDialog.findChild<QLineEdit*>(
"reqMessage");
321 messageInput->setText(
"TEST_MESSAGE_1");
322 int initialRowCount = requestTableModel->
rowCount({});
323 QPushButton* requestPaymentButton = receiveCoinsDialog.findChild<QPushButton*>(
"receiveButton");
324 requestPaymentButton->click();
326 for (QWidget* widget : QApplication::topLevelWidgets()) {
327 if (widget->inherits(
"ReceiveRequestDialog")) {
329 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"payment_header")->text(), QString(
"Payment information"));
330 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"uri_tag")->text(), QString(
"URI:"));
331 QString uri = receiveRequestDialog->QObject::findChild<QLabel*>(
"uri_content")->text();
332 QCOMPARE(uri.count(
"bitcoin:"), 2);
333 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"address_tag")->text(), QString(
"Address:"));
334 QVERIFY(address.isEmpty());
335 address = receiveRequestDialog->QObject::findChild<QLabel*>(
"address_content")->text();
336 QVERIFY(!address.isEmpty());
338 QCOMPARE(uri.count(
"amount=0.00000001"), 2);
339 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"amount_tag")->text(), QString(
"Amount:"));
340 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"amount_content")->text(), QString::fromStdString(
"0.00000001 " +
CURRENCY_UNIT));
342 QCOMPARE(uri.count(
"label=TEST_LABEL_1"), 2);
343 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"label_tag")->text(), QString(
"Label:"));
344 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"label_content")->text(), QString(
"TEST_LABEL_1"));
346 QCOMPARE(uri.count(
"message=TEST_MESSAGE_1"), 2);
347 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"message_tag")->text(), QString(
"Message:"));
348 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"message_content")->text(), QString(
"TEST_MESSAGE_1"));
353 QPushButton* clearButton = receiveCoinsDialog.findChild<QPushButton*>(
"clearButton");
354 clearButton->click();
355 QCOMPARE(labelInput->text(), QString(
""));
357 QCOMPARE(messageInput->text(), QString(
""));
360 int currentRowCount = requestTableModel->
rowCount({});
361 QCOMPARE(currentRowCount, initialRowCount+1);
365 QCOMPARE(requests.size(),
size_t{1});
368 QCOMPARE(entry.nVersion,
int{1});
369 QCOMPARE(entry.id, int64_t{1});
370 QVERIFY(entry.date.isValid());
371 QCOMPARE(entry.recipient.address, address);
372 QCOMPARE(entry.recipient.label, QString{
"TEST_LABEL_1"});
373 QCOMPARE(entry.recipient.amount,
CAmount{1});
374 QCOMPARE(entry.recipient.message, QString{
"TEST_MESSAGE_1"});
375 QCOMPARE(entry.recipient.sPaymentRequest, std::string{});
376 QCOMPARE(entry.recipient.authenticatedMerchant, QString{});
379 QTableView* table = receiveCoinsDialog.findChild<QTableView*>(
"recentRequestsView");
380 table->selectRow(currentRowCount-1);
381 QPushButton* removeRequestButton = receiveCoinsDialog.findChild<QPushButton*>(
"removeRequestButton");
382 removeRequestButton->click();
383 QCOMPARE(requestTableModel->
rowCount({}), currentRowCount-1);
391 const std::shared_ptr<CWallet>&
wallet = SetupDescriptorsWallet(
node, test,
true);
395 MiniGUI mini_gui(
node, platformStyle.get());
396 mini_gui.initModelForWallet(
node,
wallet, platformStyle.get());
404 sendCoinsDialog.findChild<QLabel*>(
"labelBalance"));
411 timer.setInterval(500);
412 QObject::connect(&timer, &QTimer::timeout, [&](){
413 for (QWidget* widget : QApplication::topLevelWidgets()) {
414 if (widget->inherits(
"QMessageBox") && widget->objectName().compare(
"psbt_copied_message") == 0) {
415 QMessageBox* dialog = qobject_cast<QMessageBox*>(widget);
416 QAbstractButton* button = dialog->button(QMessageBox::Discard);
417 button->setEnabled(
true);
427 SendCoins(*
wallet.get(), sendCoinsDialog,
PKHash(), 5 *
COIN,
false, QMessageBox::Save);
428 const std::string& psbt_string = QApplication::clipboard()->text().toStdString();
429 QVERIFY(!psbt_string.empty());
432 std::optional<std::vector<unsigned char>> decoded_psbt =
DecodeBase64(psbt_string);
433 QVERIFY(decoded_psbt);
443 for (
int i = 0; i < 5; ++i) {
451 const std::shared_ptr<CWallet>& desc_wallet = SetupDescriptorsWallet(
node, test);
452 TestGUI(
node, desc_wallet);
456 TestGUIWatchOnly(
node, test);
464 if (QApplication::platformName() ==
"minimal") {
469 qWarning() <<
"Skipping WalletTests on mac build with 'minimal' platform set due to Qt bugs. To run AppTests, invoke " 470 "with 'QT_QPA_PLATFORM=cocoa test_bitcoin-qt' on mac, or else use a linux or windows build.";
Widget for entering bitcoin amounts.
static UniValue Parse(std::string_view raw, ParamFormat format=ParamFormat::JSON)
Parse string to UniValue or throw runtime_error if string contains invalid JSON.
std::unique_ptr< interfaces::Chain > chain
auto MakeByteSpan(const V &v) noexcept
Model for list of recently generated payment requests / bitcoin: URIs.
virtual CoinsList listCoins()=0
Dialog for requesting payment of bitcoins.
OptionsModel * getOptionsModel() const
void setWalletModel(WalletModel *walletModel)
interfaces::Wallet & wallet() const
CTxDestination destChange
Custom change destination, if not set an address is generated.
CPubKey GetPubKey() const
Compute the public key from a private key.
wallet::CCoinControl * getCoinControl()
interfaces::WalletBalances getCachedBalance() const
TransactionTableModel * getTransactionTableModel() const
BitcoinUnit getDisplayUnit() const
std::unique_ptr< Wallet > MakeWallet(wallet::WalletContext &context, const std::shared_ptr< wallet::CWallet > &wallet)
Return implementation of Wallet interface.
RAII object to check and reserve a wallet rescan.
A version of CTransaction with the PSBT format.
QTableView * transactionView
Line edit that can be marked as "invalid" to show input validation feedback.
A single entry in the dialog for sending bitcoins.
int rowCount(const QModelIndex &parent) const override
static QString formatWithUnit(Unit unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as string (with unit)
Minimal stream for reading from an existing byte array by std::span.
bool Init(bilingual_str &error)
void ConfirmMessage(QString *text, std::chrono::milliseconds msec)
Press "Ok" button in message box dialog.
int64_t CAmount
Amount in satoshis (Can be negative)
void setModel(WalletModel *model)
std::unique_ptr< WalletLoader > MakeWalletLoader(Chain &chain, ArgsManager &args)
Return implementation of ChainClient interface for a wallet loader.
RecentRequestsTableModel * getRecentRequestsTableModel() const
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...
std::optional< std::vector< unsigned char > > DecodeBase64(std::string_view str)
Widget showing the transaction list for a wallet, including a filter row.
Indicate that this wallet supports DescriptorScriptPubKeyMan.
std::string ToString() const
bool DecodeRawPSBT(PartiallySignedTransaction &psbt, std::span< const std::byte > tx_data, std::string &error)
Decode a raw (binary blob) PSBT into a PartiallySignedTransaction.
Dialog for sending bitcoins.
const std::string CURRENCY_UNIT
A CWallet maintains a set of transactions and balances, and provides the ability to create new transa...
Testing fixture that pre-creates a 100-block REGTEST-mode block chain.
UI model for the transaction table of a wallet.
void setModel(WalletModel *model)
#define WITH_LOCK(cs, code)
Run code while locking a mutex.
Descriptor with some wallet metadata.
bool RemoveWallet(WalletContext &context, const std::shared_ptr< CWallet > &wallet, std::optional< bool > load_on_start, std::vector< bilingual_str > &warnings)
interfaces::Node & m_node
int rowCount(const QModelIndex &parent) const override
std::unique_ptr< WalletDatabase > CreateMockableWalletDatabase(MockableData records)
void setModel(WalletModel *model)
Interface from Qt to configuration data structure for Bitcoin client.
const CChainParams & Params()
Return the currently selected parameters.
Interface to Bitcoin wallet from Qt view code.
std::variant< CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, PayToAnchor, WitnessUnknown > CTxDestination
A txout script categorized into standard templates.
WalletContext struct containing references to state shared between CWallet instances, like the reference to the chain interface, and the list of opened wallets.
void useAvailableBalance(SendCoinsEntry *entry)
std::string EncodeDestination(const CTxDestination &dest)
CScript GetScriptForRawPubKey(const CPubKey &pubKey)
Generate a P2PK script for the given pubkey.
virtual CAmount getBalance()=0
Get balance.
virtual std::vector< std::string > getAddressReceiveRequests()=0
Get receive requests.
virtual WalletBalances getBalances()=0
Get balances.
ChangeType
General change type (added, updated, removed).
std::string EncodeSecret(const CKey &key)
interfaces::WalletLoader * wallet_loader
Top-level interface for a bitcoin node (bitcoind process).
bool AddWallet(WalletContext &context, const std::shared_ptr< CWallet > &wallet)
Overview ("home") page widget.
std::string HexStr(const std::span< const uint8_t > s)
Convert a span of bytes to a lower-case hexadecimal string.
void setValue(const CAmount &value)
PreselectedInput & Select(const COutPoint &outpoint)
Lock-in the given output for spending.
#define Assert(val)
Identity function.
void pollBalanceChanged()
static constexpr CAmount COIN
The amount of satoshis in one BTC.