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 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);
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());
199 wallet->SetupLegacyScriptPubKeyMan();
202 bool import_keys =
wallet->ImportPubKeys({{pubKey.
GetID(),
false}}, {{pubKey.
GetID(), pubKey}} , {},
false, 1);
204 wallet->SetLastBlockProcessed(105,
WITH_LOCK(
node.context()->chainman->GetMutex(),
return node.context()->chainman->ActiveChain().Tip()->GetBlockHash()));
216 wallet->SetupDescriptorScriptPubKeyMans();
223 assert(descs.size() == 1);
224 auto& desc = descs.at(0);
226 if (!
wallet->AddWalletDescriptor(w_desc, provider,
"",
false))
assert(
false);
229 wallet->SetLastBlockProcessed(105,
WITH_LOCK(
node.context()->chainman->GetMutex(),
return node.context()->chainman->ActiveChain().Tip()->GetBlockHash()));
231 wallet->SetBroadcastTransactions(
true);
240 std::unique_ptr<ClientModel> clientModel;
241 std::unique_ptr<WalletModel> walletModel;
245 QVERIFY(optionsModel.
Init(error));
246 clientModel = std::make_unique<ClientModel>(
node, &optionsModel);
255 sendCoinsDialog.
setModel(walletModel.get());
256 transactionView.
setModel(walletModel.get());
278 MiniGUI mini_gui(
node, platformStyle.get());
279 mini_gui.initModelForWallet(
node,
wallet, platformStyle.get());
287 CompareBalance(walletModel, walletModel.
wallet().
getBalance(), sendCoinsDialog.findChild<QLabel*>(
"labelBalance"));
290 VerifyUseAvailableBalance(sendCoinsDialog, walletModel);
294 QCOMPARE(transactionTableModel->
rowCount({}), 105);
298 qApp->processEvents();
299 QCOMPARE(transactionTableModel->
rowCount({}), 107);
300 QVERIFY(FindTx(*transactionTableModel, txid1).isValid());
301 QVERIFY(FindTx(*transactionTableModel, txid2).isValid());
304 BumpFee(transactionView, txid1,
true,
"not BIP 125 replaceable",
false);
305 BumpFee(transactionView, txid2,
false, {},
true);
306 BumpFee(transactionView, txid2,
false, {},
false);
307 BumpFee(transactionView, txid2,
true,
"already bumped",
false);
313 CompareBalance(walletModel, walletModel.
wallet().
getBalance(), overviewPage.findChild<QLabel*>(
"labelBalance"));
317 receiveCoinsDialog.
setModel(&walletModel);
321 QLineEdit* labelInput = receiveCoinsDialog.findChild<QLineEdit*>(
"reqLabel");
322 labelInput->setText(
"TEST_LABEL_1");
329 QLineEdit* messageInput = receiveCoinsDialog.findChild<QLineEdit*>(
"reqMessage");
330 messageInput->setText(
"TEST_MESSAGE_1");
331 int initialRowCount = requestTableModel->
rowCount({});
332 QPushButton* requestPaymentButton = receiveCoinsDialog.findChild<QPushButton*>(
"receiveButton");
333 requestPaymentButton->click();
335 for (QWidget* widget : QApplication::topLevelWidgets()) {
336 if (widget->inherits(
"ReceiveRequestDialog")) {
338 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"payment_header")->text(), QString(
"Payment information"));
339 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"uri_tag")->text(), QString(
"URI:"));
340 QString uri = receiveRequestDialog->QObject::findChild<QLabel*>(
"uri_content")->text();
341 QCOMPARE(uri.count(
"bitcoin:"), 2);
342 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"address_tag")->text(), QString(
"Address:"));
343 QVERIFY(address.isEmpty());
344 address = receiveRequestDialog->QObject::findChild<QLabel*>(
"address_content")->text();
345 QVERIFY(!address.isEmpty());
347 QCOMPARE(uri.count(
"amount=0.00000001"), 2);
348 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"amount_tag")->text(), QString(
"Amount:"));
349 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"amount_content")->text(), QString::fromStdString(
"0.00000001 " +
CURRENCY_UNIT));
351 QCOMPARE(uri.count(
"label=TEST_LABEL_1"), 2);
352 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"label_tag")->text(), QString(
"Label:"));
353 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"label_content")->text(), QString(
"TEST_LABEL_1"));
355 QCOMPARE(uri.count(
"message=TEST_MESSAGE_1"), 2);
356 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"message_tag")->text(), QString(
"Message:"));
357 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"message_content")->text(), QString(
"TEST_MESSAGE_1"));
362 QPushButton* clearButton = receiveCoinsDialog.findChild<QPushButton*>(
"clearButton");
363 clearButton->click();
364 QCOMPARE(labelInput->text(), QString(
""));
366 QCOMPARE(messageInput->text(), QString(
""));
369 int currentRowCount = requestTableModel->
rowCount({});
370 QCOMPARE(currentRowCount, initialRowCount+1);
374 QCOMPARE(requests.size(),
size_t{1});
377 QCOMPARE(entry.nVersion,
int{1});
378 QCOMPARE(entry.id, int64_t{1});
379 QVERIFY(entry.date.isValid());
380 QCOMPARE(entry.recipient.address, address);
381 QCOMPARE(entry.recipient.label, QString{
"TEST_LABEL_1"});
382 QCOMPARE(entry.recipient.amount,
CAmount{1});
383 QCOMPARE(entry.recipient.message, QString{
"TEST_MESSAGE_1"});
384 QCOMPARE(entry.recipient.sPaymentRequest, std::string{});
385 QCOMPARE(entry.recipient.authenticatedMerchant, QString{});
388 QTableView* table = receiveCoinsDialog.findChild<QTableView*>(
"recentRequestsView");
389 table->selectRow(currentRowCount-1);
390 QPushButton* removeRequestButton = receiveCoinsDialog.findChild<QPushButton*>(
"removeRequestButton");
391 removeRequestButton->click();
392 QCOMPARE(requestTableModel->
rowCount({}), currentRowCount-1);
400 const std::shared_ptr<CWallet>&
wallet = SetupLegacyWatchOnlyWallet(
node, test);
404 MiniGUI mini_gui(
node, platformStyle.get());
405 mini_gui.initModelForWallet(
node,
wallet, platformStyle.get());
413 sendCoinsDialog.findChild<QLabel*>(
"labelBalance"));
420 timer.setInterval(500);
421 QObject::connect(&timer, &QTimer::timeout, [&](){
422 for (QWidget* widget : QApplication::topLevelWidgets()) {
423 if (widget->inherits(
"QMessageBox") && widget->objectName().compare(
"psbt_copied_message") == 0) {
424 QMessageBox* dialog = qobject_cast<QMessageBox*>(widget);
425 QAbstractButton* button = dialog->button(QMessageBox::Discard);
426 button->setEnabled(
true);
436 SendCoins(*
wallet.get(), sendCoinsDialog,
PKHash(), 5 *
COIN,
false, QMessageBox::Save);
437 const std::string& psbt_string = QApplication::clipboard()->text().toStdString();
438 QVERIFY(!psbt_string.empty());
441 std::optional<std::vector<unsigned char>> decoded_psbt =
DecodeBase64(psbt_string);
442 QVERIFY(decoded_psbt);
452 for (
int i = 0; i < 5; ++i) {
460 const std::shared_ptr<CWallet>& desc_wallet = SetupDescriptorsWallet(
node, test);
461 TestGUI(
node, desc_wallet);
465 TestGUIWatchOnly(
node, test);
473 if (QApplication::platformName() ==
"minimal") {
478 qWarning() <<
"Skipping WalletTests on mac build with 'minimal' platform set due to Qt bugs. To run AppTests, invoke " 479 "with 'QT_QPA_PLATFORM=cocoa test_bitcoin-qt' on mac, or else use a linux or windows build.";
Widget for entering bitcoin amounts.
std::unique_ptr< interfaces::Chain > chain
static UniValue Parse(std::string_view raw)
Parse string to UniValue or throw runtime_error if string contains invalid JSON.
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.
CKeyID GetID() const
Get the KeyID of this public key (hash of its serialization)
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)
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
CAmount watch_only_balance
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...
Double ended buffer combining vector and stream-like interfaces.
std::optional< std::vector< unsigned char > > DecodeBase64(std::string_view str)
An encapsulated public key.
Widget showing the transaction list for a wallet, including a filter row.
Indicate that this wallet supports DescriptorScriptPubKeyMan.
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...
std::string ToString() const
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.
Span< const std::byte > MakeByteSpan(V &&v) noexcept
WalletContext struct containing references to state shared between CWallet instances, like the reference to the chain interface, and the list of opened wallets.
constexpr auto MakeUCharSpan(V &&v) -> decltype(UCharSpanCast(Span
Like the Span constructor, but for (const) unsigned char member types only.
CTxDestination GetDestinationForKey(const CPubKey &key, OutputType type)
Get a destination of the requested type (if possible) to the specified key.
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).
bool DecodeRawPSBT(PartiallySignedTransaction &psbt, Span< const std::byte > tx_data, std::string &error)
Decode a raw (binary blob) PSBT into a PartiallySignedTransaction.
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.
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.