Skip to content

Commit c4e3b7d

Browse files
committed
wallet: SelectCoins, return early if wallet's UTXOs cannot cover the target
The CoinsResult class will now count the raw total amount and the effective total amount internally (inside the 'CoinsResult::Add' and 'CoinsResult::Erase' methods). So there is no discrepancy between what we add/erase and the total values. (which is what was happening on the coinselector_test because the 'CoinsResult' object is manually created there, and we were not keeping the total amount in sync with the outputs being added/removed).
1 parent cac2725 commit c4e3b7d

File tree

3 files changed

+25
-4
lines changed

3 files changed

+25
-4
lines changed

src/wallet/coinselection.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ struct COutput {
110110
assert(effective_value.has_value());
111111
return effective_value.value();
112112
}
113+
114+
bool HasEffectiveValue() const { return effective_value.has_value(); }
113115
};
114116

115117
/** Parameters for one iteration of Coin Selection. */

src/wallet/spend.cpp

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,13 @@ void CoinsResult::Erase(const std::unordered_set<COutPoint, SaltedOutpointHasher
106106
{
107107
for (auto& [type, vec] : coins) {
108108
auto remove_it = std::remove_if(vec.begin(), vec.end(), [&](const COutput& coin) {
109-
return coins_to_remove.count(coin.outpoint) == 1;
109+
// remove it if it's on the set
110+
if (coins_to_remove.count(coin.outpoint) == 0) return false;
111+
112+
// update cached amounts
113+
total_amount -= coin.txout.nValue;
114+
if (coin.HasEffectiveValue()) total_effective_amount = *total_effective_amount - coin.GetEffectiveValue();
115+
return true;
110116
});
111117
vec.erase(remove_it, vec.end());
112118
}
@@ -122,6 +128,11 @@ void CoinsResult::Shuffle(FastRandomContext& rng_fast)
122128
void CoinsResult::Add(OutputType type, const COutput& out)
123129
{
124130
coins[type].emplace_back(out);
131+
total_amount += out.txout.nValue;
132+
if (out.HasEffectiveValue()) {
133+
total_effective_amount = total_effective_amount.has_value() ?
134+
*total_effective_amount + out.GetEffectiveValue() : out.GetEffectiveValue();
135+
}
125136
}
126137

127138
static OutputType GetOutputType(TxoutType type, bool is_from_p2sh)
@@ -319,8 +330,6 @@ CoinsResult AvailableCoins(const CWallet& wallet,
319330
result.Add(GetOutputType(type, is_from_p2sh),
320331
COutput(outpoint, output, nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate));
321332

322-
// Cache total amount as we go
323-
result.total_amount += output.nValue;
324333
// Checks the sum amount of all UTXO's.
325334
if (params.min_sum_amount != MAX_MONEY) {
326335
if (result.total_amount >= params.min_sum_amount) {
@@ -575,6 +584,14 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
575584
return result;
576585
}
577586

587+
// Return early if we cannot cover the target with the wallet's UTXO.
588+
// We use the total effective value if we are not subtracting fee from outputs and 'available_coins' contains the data.
589+
CAmount available_coins_total_amount = coin_selection_params.m_subtract_fee_outputs ? available_coins.total_amount :
590+
(available_coins.total_effective_amount.has_value() ? *available_coins.total_effective_amount : 0);
591+
if (selection_target > available_coins_total_amount) {
592+
return std::nullopt; // Insufficient funds
593+
}
594+
578595
// Start wallet Coin Selection procedure
579596
auto op_selection_result = AutomaticCoinSelection(wallet, available_coins, selection_target, coin_control, coin_selection_params);
580597
if (!op_selection_result) return op_selection_result;

src/wallet/spend.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,10 @@ struct CoinsResult {
5151
void Shuffle(FastRandomContext& rng_fast);
5252
void Add(OutputType type, const COutput& out);
5353

54-
/** Sum of all available coins */
54+
/** Sum of all available coins raw value */
5555
CAmount total_amount{0};
56+
/** Sum of all available coins effective value (each output value minus fees required to spend it) */
57+
std::optional<CAmount> total_effective_amount{0};
5658
};
5759

5860
struct CoinFilterParams {

0 commit comments

Comments
 (0)