mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-06-03 01:33:20 +02:00
Amend bumpfee for inputs with overlapping ancestry
At the end of coin selection reduce the fees by the difference between the individual bump fee estimates and the collective bump fee estimate.
This commit is contained in:
@@ -544,13 +544,13 @@ FilteredOutputGroups GroupOutputs(const CWallet& wallet,
|
||||
// Returns true if the result contains an error and the message is not empty
|
||||
static bool HasErrorMsg(const util::Result<SelectionResult>& res) { return !util::ErrorString(res).empty(); }
|
||||
|
||||
util::Result<SelectionResult> AttemptSelection(const CAmount& nTargetValue, OutputGroupTypeMap& groups,
|
||||
util::Result<SelectionResult> AttemptSelection(interfaces::Chain& chain, const CAmount& nTargetValue, OutputGroupTypeMap& groups,
|
||||
const CoinSelectionParams& coin_selection_params, bool allow_mixed_output_types)
|
||||
{
|
||||
// Run coin selection on each OutputType and compute the Waste Metric
|
||||
std::vector<SelectionResult> results;
|
||||
for (auto& [type, group] : groups.groups_by_type) {
|
||||
auto result{ChooseSelectionResult(nTargetValue, group, coin_selection_params)};
|
||||
auto result{ChooseSelectionResult(chain, nTargetValue, group, coin_selection_params)};
|
||||
// If any specific error message appears here, then something particularly wrong happened.
|
||||
if (HasErrorMsg(result)) return result; // So let's return the specific error.
|
||||
// Append the favorable result.
|
||||
@@ -564,14 +564,14 @@ util::Result<SelectionResult> AttemptSelection(const CAmount& nTargetValue, Outp
|
||||
// over all available coins, which would allow mixing.
|
||||
// If TypesCount() <= 1, there is nothing to mix.
|
||||
if (allow_mixed_output_types && groups.TypesCount() > 1) {
|
||||
return ChooseSelectionResult(nTargetValue, groups.all_groups, coin_selection_params);
|
||||
return ChooseSelectionResult(chain, nTargetValue, groups.all_groups, coin_selection_params);
|
||||
}
|
||||
// Either mixing is not allowed and we couldn't find a solution from any single OutputType, or mixing was allowed and we still couldn't
|
||||
// find a solution using all available coins
|
||||
return util::Error();
|
||||
};
|
||||
|
||||
util::Result<SelectionResult> ChooseSelectionResult(const CAmount& nTargetValue, Groups& groups, const CoinSelectionParams& coin_selection_params)
|
||||
util::Result<SelectionResult> ChooseSelectionResult(interfaces::Chain& chain, const CAmount& nTargetValue, Groups& groups, const CoinSelectionParams& coin_selection_params)
|
||||
{
|
||||
// Vector of results. We will choose the best one based on waste.
|
||||
std::vector<SelectionResult> results;
|
||||
@@ -596,12 +596,10 @@ util::Result<SelectionResult> ChooseSelectionResult(const CAmount& nTargetValue,
|
||||
|
||||
// The knapsack solver has some legacy behavior where it will spend dust outputs. We retain this behavior, so don't filter for positive only here.
|
||||
if (auto knapsack_result{KnapsackSolver(groups.mixed_group, nTargetValue, coin_selection_params.m_min_change_target, coin_selection_params.rng_fast, max_inputs_weight)}) {
|
||||
knapsack_result->ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
|
||||
results.push_back(*knapsack_result);
|
||||
} else append_error(knapsack_result);
|
||||
|
||||
if (auto srd_result{SelectCoinsSRD(groups.positive_group, nTargetValue, coin_selection_params.m_change_fee, coin_selection_params.rng_fast, max_inputs_weight)}) {
|
||||
srd_result->ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
|
||||
results.push_back(*srd_result);
|
||||
} else append_error(srd_result);
|
||||
|
||||
@@ -611,6 +609,27 @@ util::Result<SelectionResult> ChooseSelectionResult(const CAmount& nTargetValue,
|
||||
return errors.empty() ? util::Error() : errors.front();
|
||||
}
|
||||
|
||||
// If the chosen input set has unconfirmed inputs, check for synergies from overlapping ancestry
|
||||
for (auto& result : results) {
|
||||
std::vector<COutPoint> outpoints;
|
||||
std::set<std::shared_ptr<COutput>> coins = result.GetInputSet();
|
||||
CAmount summed_bump_fees = 0;
|
||||
for (auto& coin : coins) {
|
||||
if (coin->depth > 0) continue; // Bump fees only exist for unconfirmed inputs
|
||||
outpoints.push_back(coin->outpoint);
|
||||
summed_bump_fees += coin->ancestor_bump_fees;
|
||||
}
|
||||
std::optional<CAmount> combined_bump_fee = chain.CalculateCombinedBumpFee(outpoints, coin_selection_params.m_effective_feerate);
|
||||
if (!combined_bump_fee.has_value()) {
|
||||
return util::Error{_("Failed to calculate bump fees, because unconfirmed UTXOs depend on enormous cluster of unconfirmed transactions.")};
|
||||
}
|
||||
CAmount bump_fee_overestimate = summed_bump_fees - combined_bump_fee.value();
|
||||
if (bump_fee_overestimate) {
|
||||
result.SetBumpFeeDiscount(bump_fee_overestimate);
|
||||
}
|
||||
result.ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
|
||||
}
|
||||
|
||||
// Choose the result with the least waste
|
||||
// If the waste is the same, choose the one which spends more inputs.
|
||||
return *std::min_element(results.begin(), results.end());
|
||||
@@ -740,7 +759,7 @@ util::Result<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, Coin
|
||||
for (const auto& select_filter : ordered_filters) {
|
||||
auto it = filtered_groups.find(select_filter.filter);
|
||||
if (it == filtered_groups.end()) continue;
|
||||
if (auto res{AttemptSelection(value_to_select, it->second,
|
||||
if (auto res{AttemptSelection(wallet.chain(), value_to_select, it->second,
|
||||
coin_selection_params, select_filter.allow_mixed_output_types)}) {
|
||||
return res; // result found
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user