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()}, {{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;
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);
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});
375 QCOMPARE(entry.nVersion,
int{1});
376 QCOMPARE(entry.id, int64_t{1});
377 QVERIFY(entry.date.isValid());
378 QCOMPARE(entry.recipient.address, address);
379 QCOMPARE(entry.recipient.label, QString{
"TEST_LABEL_1"});
380 QCOMPARE(entry.recipient.amount,
CAmount{1});
381 QCOMPARE(entry.recipient.message, QString{
"TEST_MESSAGE_1"});
382 QCOMPARE(entry.recipient.sPaymentRequest, std::string{});
383 QCOMPARE(entry.recipient.authenticatedMerchant, QString{});
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")) {
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.");
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 Select(const COutPoint &output)
Lock-in the given output for spending.
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
std::variant< CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, WitnessUnknown > CTxDestination
A txout script categorized into standard templates.
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.
bool error(const char *fmt, const Args &... args)
const CChainParams & Params()
Return the currently selected parameters.
Interface to Bitcoin wallet from Qt view code.
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.
std::shared_ptr< CWallet > wallet
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
Reference to chain client that should used to load or create wallets opened by the gui...
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)
#define Assert(val)
Identity function.
void pollBalanceChanged()
static constexpr CAmount COIN
The amount of satoshis in one BTC.