Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1// Copyright (c) 2015-present The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or http://www.opensource.org/licenses/mit-license.php.5#include <qt/test/wallettests.h>6#include <qt/test/util.h>8#include <wallet/coincontrol.h>9#include <interfaces/chain.h>10#include <interfaces/node.h>11#include <key_io.h>12#include <qt/bitcoinamountfield.h>13#include <qt/bitcoinunits.h>14#include <qt/clientmodel.h>15#include <qt/optionsmodel.h>16#include <qt/overviewpage.h>17#include <qt/platformstyle.h>18#include <qt/qvalidatedlineedit.h>19#include <qt/receivecoinsdialog.h>20#include <qt/receiverequestdialog.h>21#include <qt/recentrequeststablemodel.h>22#include <qt/sendcoinsdialog.h>23#include <qt/sendcoinsentry.h>24#include <qt/transactiontablemodel.h>25#include <qt/transactionview.h>26#include <qt/walletmodel.h>27#include <script/solver.h>28#include <test/util/setup_common.h>29#include <validation.h>30#include <wallet/test/util.h>31#include <wallet/wallet.h>33#include <chrono>34#include <memory>36#include <QAbstractButton>37#include <QAction>38#include <QApplication>39#include <QCheckBox>40#include <QClipboard>41#include <QObject>42#include <QPushButton>43#include <QTimer>44#include <QVBoxLayout>45#include <QTextEdit>46#include <QListView>47#include <QDialogButtonBox>49using wallet::AddWallet;50using wallet::CWallet;51using wallet::CreateMockableWalletDatabase;52using wallet::RemoveWallet;53using wallet::WALLET_FLAG_DESCRIPTORS;54using wallet::WALLET_FLAG_DISABLE_PRIVATE_KEYS;55using wallet::WalletContext;56using wallet::WalletDescriptor;57using wallet::WalletRescanReserver;59namespace60{61//! Press "Yes" or "Cancel" buttons in modal send confirmation dialog.62void ConfirmSend(QString* text = nullptr, QMessageBox::StandardButton confirm_type = QMessageBox::Yes)63{64QTimer::singleShot(0, [text, confirm_type]() {65for (QWidget* widget : QApplication::topLevelWidgets()) {66if (widget->inherits("SendConfirmationDialog")) {67SendConfirmationDialog* dialog = qobject_cast<SendConfirmationDialog*>(widget);68if (text) *text = dialog->text();69QAbstractButton* button = dialog->button(confirm_type);70button->setEnabled(true);71button->click();72}73}74});75}77//! Send coins to address and return txid.78Txid SendCoins(CWallet& wallet, SendCoinsDialog& sendCoinsDialog, const CTxDestination& address, CAmount amount,79QMessageBox::StandardButton confirm_type = QMessageBox::Yes)80{81QVBoxLayout* entries = sendCoinsDialog.findChild<QVBoxLayout*>("entries");82SendCoinsEntry* entry = qobject_cast<SendCoinsEntry*>(entries->itemAt(0)->widget());83entry->findChild<QValidatedLineEdit*>("payTo")->setText(QString::fromStdString(EncodeDestination(address)));84entry->findChild<BitcoinAmountField*>("payAmount")->setValue(amount);85Txid txid;86btcsignals::scoped_connection c(wallet.NotifyTransactionChanged.connect([&txid](const Txid& hash, ChangeType status) {87if (status == CT_NEW) txid = hash;88}));89ConfirmSend(/*text=*/nullptr, confirm_type);90bool invoked = QMetaObject::invokeMethod(&sendCoinsDialog, "sendButtonClicked", Q_ARG(bool, false));91assert(invoked);92return txid;93}95//! Find index of txid in transaction list.96QModelIndex FindTx(const QAbstractItemModel& model, const Txid& txid)97{98QString hash = QString::fromStdString(txid.ToString());99int rows = model.rowCount({});100for (int row = 0; row < rows; ++row) {101QModelIndex index = model.index(row, 0, {});102if (model.data(index, TransactionTableModel::TxHashRole) == hash) {103return index;104}105}106return {};107}109//! Invoke bumpfee on txid and check results.110void BumpFee(TransactionView& view, const Txid& txid, bool expectDisabled, std::string expectError, bool cancel)111{112QTableView* table = view.findChild<QTableView*>("transactionView");113QModelIndex index = FindTx(*table->selectionModel()->model(), txid);114QVERIFY2(index.isValid(), "Could not find BumpFee txid");116// Select row in table, invoke context menu, and make sure bumpfee action is117// enabled or disabled as expected.118QAction* action = view.findChild<QAction*>("bumpFeeAction");119table->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);120action->setEnabled(expectDisabled);121table->customContextMenuRequested({});122QCOMPARE(action->isEnabled(), !expectDisabled);124action->setEnabled(true);125QString text;126if (expectError.empty()) {127ConfirmSend(&text, cancel ? QMessageBox::Cancel : QMessageBox::Yes);128} else {129ConfirmMessage(&text, 0ms);130}131action->trigger();132QVERIFY(text.indexOf(QString::fromStdString(expectError)) != -1);133}135void CompareBalance(WalletModel& walletModel, CAmount expected_balance, QLabel* balance_label_to_check)136{137BitcoinUnit unit = walletModel.getOptionsModel()->getDisplayUnit();138QString balanceComparison = BitcoinUnits::formatWithUnit(unit, expected_balance, false, BitcoinUnits::SeparatorStyle::ALWAYS);139QCOMPARE(balance_label_to_check->text().trimmed(), balanceComparison);140}142// Verify the 'useAvailableBalance' functionality. With and without manually selected coins.143// Case 1: No coin control selected coins.144// 'useAvailableBalance' should fill the amount edit box with the total available balance145// Case 2: With coin control selected coins.146// 'useAvailableBalance' should fill the amount edit box with the sum of the selected coins values.147void VerifyUseAvailableBalance(SendCoinsDialog& sendCoinsDialog, const WalletModel& walletModel)148{149// Verify first entry amount and "useAvailableBalance" button150QVBoxLayout* entries = sendCoinsDialog.findChild<QVBoxLayout*>("entries");151QVERIFY(entries->count() == 1); // only one entry152SendCoinsEntry* send_entry = qobject_cast<SendCoinsEntry*>(entries->itemAt(0)->widget());153QVERIFY(send_entry->getValue().amount == 0);154// Now click "useAvailableBalance", check updated balance (the entire wallet balance should be set)155Q_EMIT send_entry->useAvailableBalance(send_entry);156QVERIFY(send_entry->getValue().amount == walletModel.getCachedBalance().balance);158// Now manually select two coins and click on "useAvailableBalance". Then check updated balance159// (only the sum of the selected coins should be set).160int COINS_TO_SELECT = 2;161auto coins = walletModel.wallet().listCoins();162CAmount sum_selected_coins = 0;163int selected = 0;164QVERIFY(coins.size() == 1); // context check, coins received only on one destination165for (const auto& [outpoint, tx_out] : coins.begin()->second) {166sendCoinsDialog.getCoinControl()->Select(outpoint);167sum_selected_coins += tx_out.txout.nValue;168if (++selected == COINS_TO_SELECT) break;169}170QVERIFY(selected == COINS_TO_SELECT);172// Now that we have 2 coins selected, "useAvailableBalance" should update the balance label only with173// the sum of them.174Q_EMIT send_entry->useAvailableBalance(send_entry);175QVERIFY(send_entry->getValue().amount == sum_selected_coins);176}178void SyncUpWallet(const std::shared_ptr<CWallet>& wallet, interfaces::Node& node)179{180WalletRescanReserver reserver(*wallet);181reserver.reserve();182CWallet::ScanResult result = wallet->ScanForWalletTransactions(Params().GetConsensus().hashGenesisBlock, /*start_height=*/0, /*max_height=*/{}, reserver, /*save_progress=*/false);183QCOMPARE(result.status, CWallet::ScanResult::SUCCESS);184QCOMPARE(result.last_scanned_block, WITH_LOCK(node.context()->chainman->GetMutex(), return node.context()->chainman->ActiveChain().Tip()->GetBlockHash()));185QVERIFY(result.last_failed_block.IsNull());186}188std::shared_ptr<CWallet> SetupDescriptorsWallet(interfaces::Node& node, TestChain100Setup& test, bool watch_only = false)189{190std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(node.context()->chain.get(), "", CreateMockableWalletDatabase());191LOCK(wallet->cs_wallet);192wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);193if (watch_only) {194wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);195} else {196wallet->SetupDescriptorScriptPubKeyMans();197}199// Add the coinbase key200FlatSigningProvider provider;201std::string error;202std::string key_str;203if (watch_only) {204key_str = HexStr(test.coinbaseKey.GetPubKey());205} else {206key_str = EncodeSecret(test.coinbaseKey);207}208auto descs = Parse("combo(" + key_str + ")", provider, error, /* require_checksum=*/ false);209assert(!descs.empty());210assert(descs.size() == 1);211auto& desc = descs.at(0);212WalletDescriptor w_desc(std::move(desc), 0, 0, 1, 1);213Assert(wallet->AddWalletDescriptor(w_desc, provider, "", false));214const PKHash dest{test.coinbaseKey.GetPubKey()};215wallet->SetAddressBook(dest, "", wallet::AddressPurpose::RECEIVE);216wallet->SetLastBlockProcessed(105, WITH_LOCK(node.context()->chainman->GetMutex(), return node.context()->chainman->ActiveChain().Tip()->GetBlockHash()));217SyncUpWallet(wallet, node);218wallet->SetBroadcastTransactions(true);219return wallet;220}222struct MiniGUI {223public:224SendCoinsDialog sendCoinsDialog;225TransactionView transactionView;226OptionsModel optionsModel;227std::unique_ptr<ClientModel> clientModel;228std::unique_ptr<WalletModel> walletModel;230MiniGUI(interfaces::Node& node, const PlatformStyle* platformStyle) : sendCoinsDialog(platformStyle), transactionView(platformStyle), optionsModel(node) {231bilingual_str error;232QVERIFY(optionsModel.Init(error));233clientModel = std::make_unique<ClientModel>(node, &optionsModel);234}236void initModelForWallet(interfaces::Node& node, const std::shared_ptr<CWallet>& wallet, const PlatformStyle* platformStyle)237{238WalletContext& context = *node.walletLoader().context();239AddWallet(context, wallet);240walletModel = std::make_unique<WalletModel>(interfaces::MakeWallet(context, wallet), *clientModel, platformStyle);241RemoveWallet(context, wallet, /* load_on_start= */ std::nullopt);242sendCoinsDialog.setModel(walletModel.get());243transactionView.setModel(walletModel.get());244}246};248//! Simple qt wallet tests.249//250// Test widgets can be debugged interactively calling show() on them and
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.