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).
This commit is contained in:
furszy 2022-11-22 11:51:33 -03:00
parent cac2725fd0
commit c4e3b7d6a1
No known key found for this signature in database
GPG key ID: 5DD23CCC686AA623
3 changed files with 25 additions and 4 deletions

View file

@ -110,6 +110,8 @@ public:
assert(effective_value.has_value());
return effective_value.value();
}
bool HasEffectiveValue() const { return effective_value.has_value(); }
};
/** Parameters for one iteration of Coin Selection. */

View file

@ -106,7 +106,13 @@ void CoinsResult::Erase(const std::unordered_set<COutPoint, SaltedOutpointHasher
{
for (auto& [type, vec] : coins) {
auto remove_it = std::remove_if(vec.begin(), vec.end(), [&](const COutput& coin) {
return coins_to_remove.count(coin.outpoint) == 1;
// remove it if it's on the set
if (coins_to_remove.count(coin.outpoint) == 0) return false;
// update cached amounts
total_amount -= coin.txout.nValue;
if (coin.HasEffectiveValue()) total_effective_amount = *total_effective_amount - coin.GetEffectiveValue();
return true;
});
vec.erase(remove_it, vec.end());
}
@ -122,6 +128,11 @@ void CoinsResult::Shuffle(FastRandomContext& rng_fast)
void CoinsResult::Add(OutputType type, const COutput& out)
{
coins[type].emplace_back(out);
total_amount += out.txout.nValue;
if (out.HasEffectiveValue()) {
total_effective_amount = total_effective_amount.has_value() ?
*total_effective_amount + out.GetEffectiveValue() : out.GetEffectiveValue();
}
}
static OutputType GetOutputType(TxoutType type, bool is_from_p2sh)
@ -319,8 +330,6 @@ CoinsResult AvailableCoins(const CWallet& wallet,
result.Add(GetOutputType(type, is_from_p2sh),
COutput(outpoint, output, nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate));
// Cache total amount as we go
result.total_amount += output.nValue;
// Checks the sum amount of all UTXO's.
if (params.min_sum_amount != MAX_MONEY) {
if (result.total_amount >= params.min_sum_amount) {
@ -575,6 +584,14 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
return result;
}
// Return early if we cannot cover the target with the wallet's UTXO.
// We use the total effective value if we are not subtracting fee from outputs and 'available_coins' contains the data.
CAmount available_coins_total_amount = coin_selection_params.m_subtract_fee_outputs ? available_coins.total_amount :
(available_coins.total_effective_amount.has_value() ? *available_coins.total_effective_amount : 0);
if (selection_target > available_coins_total_amount) {
return std::nullopt; // Insufficient funds
}
// Start wallet Coin Selection procedure
auto op_selection_result = AutomaticCoinSelection(wallet, available_coins, selection_target, coin_control, coin_selection_params);
if (!op_selection_result) return op_selection_result;

View file

@ -51,8 +51,10 @@ struct CoinsResult {
void Shuffle(FastRandomContext& rng_fast);
void Add(OutputType type, const COutput& out);
/** Sum of all available coins */
/** Sum of all available coins raw value */
CAmount total_amount{0};
/** Sum of all available coins effective value (each output value minus fees required to spend it) */
std::optional<CAmount> total_effective_amount{0};
};
struct CoinFilterParams {