@@ -544,13 +544,13 @@ FilteredOutputGroups GroupOutputs(const CWallet& wallet,
544
544
// Returns true if the result contains an error and the message is not empty
545
545
static bool HasErrorMsg (const util::Result<SelectionResult>& res) { return !util::ErrorString (res).empty (); }
546
546
547
- util::Result<SelectionResult> AttemptSelection (const CAmount& nTargetValue, OutputGroupTypeMap& groups,
547
+ util::Result<SelectionResult> AttemptSelection (interfaces::Chain& chain, const CAmount& nTargetValue, OutputGroupTypeMap& groups,
548
548
const CoinSelectionParams& coin_selection_params, bool allow_mixed_output_types)
549
549
{
550
550
// Run coin selection on each OutputType and compute the Waste Metric
551
551
std::vector<SelectionResult> results;
552
552
for (auto & [type, group] : groups.groups_by_type ) {
553
- auto result{ChooseSelectionResult (nTargetValue, group, coin_selection_params)};
553
+ auto result{ChooseSelectionResult (chain, nTargetValue, group, coin_selection_params)};
554
554
// If any specific error message appears here, then something particularly wrong happened.
555
555
if (HasErrorMsg (result)) return result; // So let's return the specific error.
556
556
// Append the favorable result.
@@ -564,14 +564,14 @@ util::Result<SelectionResult> AttemptSelection(const CAmount& nTargetValue, Outp
564
564
// over all available coins, which would allow mixing.
565
565
// If TypesCount() <= 1, there is nothing to mix.
566
566
if (allow_mixed_output_types && groups.TypesCount () > 1 ) {
567
- return ChooseSelectionResult (nTargetValue, groups.all_groups , coin_selection_params);
567
+ return ChooseSelectionResult (chain, nTargetValue, groups.all_groups , coin_selection_params);
568
568
}
569
569
// 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
570
570
// find a solution using all available coins
571
571
return util::Error ();
572
572
};
573
573
574
- util::Result<SelectionResult> ChooseSelectionResult (const CAmount& nTargetValue, Groups& groups, const CoinSelectionParams& coin_selection_params)
574
+ util::Result<SelectionResult> ChooseSelectionResult (interfaces::Chain& chain, const CAmount& nTargetValue, Groups& groups, const CoinSelectionParams& coin_selection_params)
575
575
{
576
576
// Vector of results. We will choose the best one based on waste.
577
577
std::vector<SelectionResult> results;
@@ -596,12 +596,10 @@ util::Result<SelectionResult> ChooseSelectionResult(const CAmount& nTargetValue,
596
596
597
597
// 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.
598
598
if (auto knapsack_result{KnapsackSolver (groups.mixed_group , nTargetValue, coin_selection_params.m_min_change_target , coin_selection_params.rng_fast , max_inputs_weight)}) {
599
- knapsack_result->ComputeAndSetWaste (coin_selection_params.min_viable_change , coin_selection_params.m_cost_of_change , coin_selection_params.m_change_fee );
600
599
results.push_back (*knapsack_result);
601
600
} else append_error (knapsack_result);
602
601
603
602
if (auto srd_result{SelectCoinsSRD (groups.positive_group , nTargetValue, coin_selection_params.m_change_fee , coin_selection_params.rng_fast , max_inputs_weight)}) {
604
- srd_result->ComputeAndSetWaste (coin_selection_params.min_viable_change , coin_selection_params.m_cost_of_change , coin_selection_params.m_change_fee );
605
603
results.push_back (*srd_result);
606
604
} else append_error (srd_result);
607
605
@@ -611,6 +609,27 @@ util::Result<SelectionResult> ChooseSelectionResult(const CAmount& nTargetValue,
611
609
return errors.empty () ? util::Error () : errors.front ();
612
610
}
613
611
612
+ // If the chosen input set has unconfirmed inputs, check for synergies from overlapping ancestry
613
+ for (auto & result : results) {
614
+ std::vector<COutPoint> outpoints;
615
+ std::set<std::shared_ptr<COutput>> coins = result.GetInputSet ();
616
+ CAmount summed_bump_fees = 0 ;
617
+ for (auto & coin : coins) {
618
+ if (coin->depth > 0 ) continue ; // Bump fees only exist for unconfirmed inputs
619
+ outpoints.push_back (coin->outpoint );
620
+ summed_bump_fees += coin->ancestor_bump_fees ;
621
+ }
622
+ std::optional<CAmount> combined_bump_fee = chain.CalculateCombinedBumpFee (outpoints, coin_selection_params.m_effective_feerate );
623
+ if (!combined_bump_fee.has_value ()) {
624
+ return util::Error{_ (" Failed to calculate bump fees, because unconfirmed UTXOs depend on enormous cluster of unconfirmed transactions." )};
625
+ }
626
+ CAmount bump_fee_overestimate = summed_bump_fees - combined_bump_fee.value ();
627
+ if (bump_fee_overestimate) {
628
+ result.SetBumpFeeDiscount (bump_fee_overestimate);
629
+ }
630
+ result.ComputeAndSetWaste (coin_selection_params.min_viable_change , coin_selection_params.m_cost_of_change , coin_selection_params.m_change_fee );
631
+ }
632
+
614
633
// Choose the result with the least waste
615
634
// If the waste is the same, choose the one which spends more inputs.
616
635
return *std::min_element (results.begin (), results.end ());
@@ -740,7 +759,7 @@ util::Result<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, Coin
740
759
for (const auto & select_filter : ordered_filters) {
741
760
auto it = filtered_groups.find (select_filter.filter );
742
761
if (it == filtered_groups.end ()) continue ;
743
- if (auto res{AttemptSelection (value_to_select, it->second ,
762
+ if (auto res{AttemptSelection (wallet. chain (), value_to_select, it->second ,
744
763
coin_selection_params, select_filter.allow_mixed_output_types )}) {
745
764
return res; // result found
746
765
} else {
0 commit comments