diff options
author | Ava Chow <github@achow101.com> | 2024-06-04 18:37:18 -0400 |
---|---|---|
committer | Ava Chow <github@achow101.com> | 2024-06-04 18:37:18 -0400 |
commit | 701b0cf2f33c65961374bb0e13e4481cfc0f3d01 (patch) | |
tree | c71eb8a7c867452df950aa7026dfdec5bbdd58a9 /src/wallet/coinselection.cpp | |
parent | d39f15a8a5b06d68070a3434a81c6840d4f87715 (diff) | |
parent | bd34dd85e7b8b4cc26d2173d84bbeda2e9c27624 (diff) |
Merge bitcoin/bitcoin#28366: Fix waste calculation in SelectionResult
bd34dd85e7b8b4cc26d2173d84bbeda2e9c27624 Use `exact_target` shorthand in coinselector_tests (Murch)
7aa7e30441fe77bf8e8092916e36b004bbbfe2a7 Fold GetSelectionWaste() into ComputeAndSetWaste() (Murch)
Pull request description:
PR #26152 moved waste calculation into SelectionResult to be able to correct the waste score on basis of the bump_fee_group_discount for overlapping ancestries. This left two functions with largely overlapping purpose, where one was simply a wrapper of the other. This PR cleans up the overlap, and fixes the double-meaning of `change_cost` where the `GetChange()` function assumed that no change was created when `change_cost` was set to 0. This behavior was exploited in a bunch of tests, but is problematic, because a `change_cost` of 0 is permitted with custom settings for feerate and discard_feerate (i.e. when they’re both 0).
ACKs for top commit:
achow101:
ACK bd34dd85e7b8b4cc26d2173d84bbeda2e9c27624
furszy:
Code ACK bd34dd85e7b8b4cc26d2173d84bbeda2e9c27624
ismaelsadeeq:
Code Review ACK bd34dd85e7b8b4cc26d2173d84bbeda2e9c27624
Tree-SHA512: 83a2688d45d719dc61a64b5180fe136107faccf401a59df65245c05d701748a03e85ed56fde8c9b7ef39a3ab54374dd3718c559bda5b3f55dafedfd7fed25161
Diffstat (limited to 'src/wallet/coinselection.cpp')
-rw-r--r-- | src/wallet/coinselection.cpp | 59 |
1 files changed, 23 insertions, 36 deletions
diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp index 42615b5d42..f1706b6800 100644 --- a/src/wallet/coinselection.cpp +++ b/src/wallet/coinselection.cpp @@ -194,7 +194,7 @@ util::Result<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool for (const size_t& i : best_selection) { result.AddInput(utxo_pool.at(i)); } - result.ComputeAndSetWaste(cost_of_change, cost_of_change, CAmount{0}); + result.RecalculateWaste(cost_of_change, cost_of_change, CAmount{0}); assert(best_waste == result.GetWaste()); return result; @@ -792,35 +792,6 @@ void OutputGroupTypeMap::Push(const OutputGroup& group, OutputType type, bool in } } -CAmount SelectionResult::GetSelectionWaste(CAmount change_cost, CAmount target, bool use_effective_value) -{ - // This function should not be called with empty inputs as that would mean the selection failed - assert(!m_selected_inputs.empty()); - - // Always consider the cost of spending an input now vs in the future. - CAmount waste = 0; - for (const auto& coin_ptr : m_selected_inputs) { - const COutput& coin = *coin_ptr; - waste += coin.GetFee() - coin.long_term_fee; - } - // Bump fee of whole selection may diverge from sum of individual bump fees - waste -= bump_fee_group_discount; - - if (change_cost) { - // Consider the cost of making change and spending it in the future - // If we aren't making change, the caller should've set change_cost to 0 - assert(change_cost > 0); - waste += change_cost; - } else { - // When we are not making change (change_cost == 0), consider the excess we are throwing away to fees - CAmount selected_effective_value = use_effective_value ? GetSelectedEffectiveValue() : GetSelectedValue(); - assert(selected_effective_value >= target); - waste += selected_effective_value - target; - } - - return waste; -} - CAmount GenerateChangeTarget(const CAmount payment_value, const CAmount change_fee, FastRandomContext& rng) { if (payment_value <= CHANGE_LOWER / 2) { @@ -839,16 +810,32 @@ void SelectionResult::SetBumpFeeDiscount(const CAmount discount) bump_fee_group_discount = discount; } - -void SelectionResult::ComputeAndSetWaste(const CAmount min_viable_change, const CAmount change_cost, const CAmount change_fee) +void SelectionResult::RecalculateWaste(const CAmount min_viable_change, const CAmount change_cost, const CAmount change_fee) { - const CAmount change = GetChange(min_viable_change, change_fee); + // This function should not be called with empty inputs as that would mean the selection failed + assert(!m_selected_inputs.empty()); + + // Always consider the cost of spending an input now vs in the future. + CAmount waste = 0; + for (const auto& coin_ptr : m_selected_inputs) { + const COutput& coin = *coin_ptr; + waste += coin.GetFee() - coin.long_term_fee; + } + // Bump fee of whole selection may diverge from sum of individual bump fees + waste -= bump_fee_group_discount; - if (change > 0) { - m_waste = GetSelectionWaste(change_cost, m_target, m_use_effective); + if (GetChange(min_viable_change, change_fee)) { + // if we have a minimum viable amount after deducting fees, account for + // cost of creating and spending change + waste += change_cost; } else { - m_waste = GetSelectionWaste(0, m_target, m_use_effective); + // When we are not making change (GetChange(…) == 0), consider the excess we are throwing away to fees + CAmount selected_effective_value = m_use_effective ? GetSelectedEffectiveValue() : GetSelectedValue(); + assert(selected_effective_value >= m_target); + waste += selected_effective_value - m_target; } + + m_waste = waste; } void SelectionResult::SetAlgoCompleted(bool algo_completed) |