Skip to content

Commit 27dcc07

Browse files
committed
Merge bitcoin/bitcoin#26699: wallet, gui: bugfix, getAvailableBalance skips selected coins
68eed5d test,gui: add coverage for PSBT creation on legacy watch-only wallets (furszy) 306aab5 test,gui: decouple widgets and model into a MiniGui struct (furszy) 2f76ac0 test,gui: decouple chain and wallet initialization from test case (furszy) cd98b71 gui: 'getAvailableBalance', include watch only balance (furszy) 74eac3a test: add coverage for 'useAvailableBalance' functionality (furszy) dc1cc1c gui: bugfix, getAvailableBalance skips selected coins (furszy) Pull request description: Fixes #688 and bitcoin/bitcoin#26687. First Issue Description (#688): The previous behavior for `getAvailableBalance`, when the coin control had selected coins, was to return the sum of them. Instead, we are currently returning the wallet's available total balance minus the selected coins total amount. Reason: Missed to update the `GetAvailableBalance` function to include the coin control selected coins on #25685. Context: Since #25685 we skip the selected coins inside `AvailableCoins`, the reason is that there is no need to waste resources walking through the entire wallet's txes map just to get coins that could have gotten by just doing a simple `mapWallet.find`). Places Where This Generates Issues (only when the user manually select coins via coin control): 1) The GUI balance check prior the transaction creation process. 2) The GUI "useAvailableBalance" functionality. Note 1: As the GUI uses a balance cache since #598, this issue does not affect the regular spending process. Only arises when the user manually select coins. Note 2: Added test coverage for the `useAvailableBalance` functionality. ---------------------------------- Second Issue Description (bitcoin/bitcoin#26687): As we are using a cached balance on `WalletModel::getAvailableBalance`, the function needs to include the watch-only available balance for wallets with private keys disabled. ACKs for top commit: Sjors: tACK 68eed5d achow101: ACK 68eed5d theStack: ACK 68eed5d Tree-SHA512: 674f3e050024dabda2ff4a04b9ed3750cf54a040527204c920e1e38bd3d7f5fd4d096e4fd08a0fea84ee6abb5070f022b5c0d450c58fd30202ef05ebfd7af6d3
2 parents c17d4d3 + 68eed5d commit 27dcc07

File tree

9 files changed

+248
-70
lines changed

9 files changed

+248
-70
lines changed

src/bench/wallet_create_tx.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ static void WalletCreateTx(benchmark::Bench& bench, const OutputType output_type
102102
}
103103

104104
// Check available balance
105-
auto bal = wallet::GetAvailableBalance(wallet); // Cache
105+
auto bal = WITH_LOCK(wallet.cs_wallet, return wallet::AvailableCoins(wallet).GetTotalAmount()); // Cache
106106
assert(bal == 50 * COIN * (chain_size - COINBASE_MATURITY));
107107

108108
wallet::CCoinControl coin_control;
@@ -161,7 +161,7 @@ static void AvailableCoins(benchmark::Bench& bench, const std::vector<OutputType
161161
}
162162

163163
// Check available balance
164-
auto bal = wallet::GetAvailableBalance(wallet); // Cache
164+
auto bal = WITH_LOCK(wallet.cs_wallet, return wallet::AvailableCoins(wallet).GetTotalAmount()); // Cache
165165
assert(bal == 50 * COIN * (chain_size - COINBASE_MATURITY));
166166

167167
bench.epochIterations(2).run([&] {

src/qt/sendcoinsdialog.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ void SendCoinsDialog::presentPSBT(PartiallySignedTransaction& psbtx)
403403
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
404404
ssTx << psbtx;
405405
GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
406-
QMessageBox msgBox;
406+
QMessageBox msgBox(this);
407407
//: Caption of "PSBT has been copied" messagebox
408408
msgBox.setText(tr("Unsigned Transaction", "PSBT copied"));
409409
msgBox.setInformativeText(tr("The PSBT has been copied to the clipboard. You can also save it."));
@@ -791,8 +791,13 @@ void SendCoinsDialog::useAvailableBalance(SendCoinsEntry* entry)
791791
// Include watch-only for wallets without private key
792792
m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner();
793793

794+
// Same behavior as send: if we have selected coins, only obtain their available balance.
795+
// Copy to avoid modifying the member's data.
796+
CCoinControl coin_control = *m_coin_control;
797+
coin_control.m_allow_other_inputs = !coin_control.HasSelected();
798+
794799
// Calculate available amount to send.
795-
CAmount amount = model->getAvailableBalance(m_coin_control.get());
800+
CAmount amount = model->getAvailableBalance(&coin_control);
796801
for (int i = 0; i < ui->entries->count(); ++i) {
797802
SendCoinsEntry* e = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
798803
if (e && !e->isHidden() && e != entry) {

src/qt/sendcoinsdialog.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ class SendCoinsDialog : public QDialog
4949
void pasteEntry(const SendCoinsRecipient &rv);
5050
bool handlePaymentRequest(const SendCoinsRecipient &recipient);
5151

52+
// Only used for testing-purposes
53+
wallet::CCoinControl* getCoinControl() { return m_coin_control.get(); }
54+
5255
public Q_SLOTS:
5356
void clear();
5457
void reject() override;

src/qt/test/wallettests.cpp

Lines changed: 203 additions & 55 deletions
Large diffs are not rendered by default.

src/qt/walletmodel.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -613,5 +613,17 @@ uint256 WalletModel::getLastBlockProcessed() const
613613

614614
CAmount WalletModel::getAvailableBalance(const CCoinControl* control)
615615
{
616-
return control && control->HasSelected() ? wallet().getAvailableBalance(*control) : getCachedBalance().balance;
616+
// No selected coins, return the cached balance
617+
if (!control || !control->HasSelected()) {
618+
const interfaces::WalletBalances& balances = getCachedBalance();
619+
CAmount available_balance = balances.balance;
620+
// if wallet private keys are disabled, this is a watch-only wallet
621+
// so, let's include the watch-only balance.
622+
if (balances.have_watch_only && m_wallet->privateKeysDisabled()) {
623+
available_balance += balances.watch_only_balance;
624+
}
625+
return available_balance;
626+
}
627+
// Fetch balance from the wallet, taking into account the selected coins
628+
return wallet().getAvailableBalance(*control);
617629
}

src/wallet/interfaces.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <util/system.h>
1919
#include <util/translation.h>
2020
#include <util/ui_change_type.h>
21+
#include <wallet/coincontrol.h>
2122
#include <wallet/context.h>
2223
#include <wallet/feebumper.h>
2324
#include <wallet/fees.h>
@@ -403,7 +404,24 @@ class WalletImpl : public Wallet
403404
CAmount getBalance() override { return GetBalance(*m_wallet).m_mine_trusted; }
404405
CAmount getAvailableBalance(const CCoinControl& coin_control) override
405406
{
406-
return GetAvailableBalance(*m_wallet, &coin_control);
407+
LOCK(m_wallet->cs_wallet);
408+
CAmount total_amount = 0;
409+
// Fetch selected coins total amount
410+
if (coin_control.HasSelected()) {
411+
FastRandomContext rng{};
412+
CoinSelectionParams params(rng);
413+
// Note: for now, swallow any error.
414+
if (auto res = FetchSelectedInputs(*m_wallet, coin_control, params)) {
415+
total_amount += res->total_amount;
416+
}
417+
}
418+
419+
// And fetch the wallet available coins
420+
if (coin_control.m_allow_other_inputs) {
421+
total_amount += AvailableCoins(*m_wallet, &coin_control).GetTotalAmount();
422+
}
423+
424+
return total_amount;
407425
}
408426
isminetype txinIsMine(const CTxIn& txin) override
409427
{

src/wallet/spend.cpp

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -356,12 +356,6 @@ CoinsResult AvailableCoinsListUnspent(const CWallet& wallet, const CCoinControl*
356356
return AvailableCoins(wallet, coinControl, /*feerate=*/ std::nullopt, params);
357357
}
358358

359-
CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl)
360-
{
361-
LOCK(wallet.cs_wallet);
362-
return AvailableCoins(wallet, coinControl).GetTotalAmount();
363-
}
364-
365359
const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const COutPoint& outpoint)
366360
{
367361
AssertLockHeld(wallet.cs_wallet);

src/wallet/spend.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,6 @@ CoinsResult AvailableCoins(const CWallet& wallet,
9494
*/
9595
CoinsResult AvailableCoinsListUnspent(const CWallet& wallet, const CCoinControl* coinControl = nullptr, CoinFilterParams params = {}) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
9696

97-
CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl = nullptr);
98-
9997
/**
10098
* Find non-change parent output.
10199
*/

src/wallet/test/wallet_tests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup)
581581
BOOST_CHECK_EQUAL(list.begin()->second.size(), 1U);
582582

583583
// Check initial balance from one mature coinbase transaction.
584-
BOOST_CHECK_EQUAL(50 * COIN, GetAvailableBalance(*wallet));
584+
BOOST_CHECK_EQUAL(50 * COIN, WITH_LOCK(wallet->cs_wallet, return AvailableCoins(*wallet).GetTotalAmount()));
585585

586586
// Add a transaction creating a change address, and confirm ListCoins still
587587
// returns the coin associated with the change address underneath the

0 commit comments

Comments
 (0)