@@ -404,12 +404,12 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
404
404
return result;
405
405
}
406
406
407
- OutputGroupTypeMap GroupOutputs (const CWallet& wallet,
407
+ FilteredOutputGroups GroupOutputs (const CWallet& wallet,
408
408
const CoinsResult& coins,
409
409
const CoinSelectionParams& coin_sel_params,
410
- const CoinEligibilityFilter& filter )
410
+ const std::vector<SelectionFilter>& filters )
411
411
{
412
- OutputGroupTypeMap output_groups ;
412
+ FilteredOutputGroups filtered_groups ;
413
413
414
414
if (!coin_sel_params.m_avoid_partial_spends ) {
415
415
// Allowing partial spends means no grouping. Each COutput gets its own OutputGroup
@@ -426,11 +426,15 @@ OutputGroupTypeMap GroupOutputs(const CWallet& wallet,
426
426
OutputGroup group (coin_sel_params);
427
427
group.Insert (std::make_shared<COutput>(output), ancestors, descendants);
428
428
429
- if (!group.EligibleForSpending (filter)) continue ;
430
- output_groups.Push (group, type, /* insert_positive=*/ true , /* insert_mixed=*/ true );
429
+ // Each filter maps to a different set of groups
430
+ for (const auto & sel_filter : filters) {
431
+ const auto & filter = sel_filter.filter ;
432
+ if (!group.EligibleForSpending (filter)) continue ;
433
+ filtered_groups[filter].Push (group, type, /* insert_positive=*/ true , /* insert_mixed=*/ true );
434
+ }
431
435
}
432
436
}
433
- return output_groups ;
437
+ return filtered_groups ;
434
438
}
435
439
436
440
// We want to combine COutputs that have the same scriptPubKey into single OutputGroups
@@ -492,41 +496,40 @@ OutputGroupTypeMap GroupOutputs(const CWallet& wallet,
492
496
for (auto group_it = groups.rbegin (); group_it != groups.rend (); group_it++) {
493
497
const OutputGroup& group = *group_it;
494
498
495
- if (!group.EligibleForSpending (filter)) continue ;
499
+ // Each filter maps to a different set of groups
500
+ for (const auto & sel_filter : filters) {
501
+ const auto & filter = sel_filter.filter ;
502
+ if (!group.EligibleForSpending (filter)) continue ;
496
503
497
- // Don't include partial groups if there are full groups too and we don't want partial groups
498
- if (group_it == groups.rbegin () && groups.size () > 1 && !filter.m_include_partial_groups ) {
499
- continue ;
500
- }
504
+ // Don't include partial groups if there are full groups too and we don't want partial groups
505
+ if (group_it == groups.rbegin () && groups.size () > 1 && !filter.m_include_partial_groups ) {
506
+ continue ;
507
+ }
501
508
502
- OutputType type = script.second ;
503
- // Either insert the group into the positive-only groups or the mixed ones.
504
- output_groups.Push (group, type, positive_only, /* insert_mixed=*/ !positive_only);
509
+ OutputType type = script.second ;
510
+ // Either insert the group into the positive-only groups or the mixed ones.
511
+ filtered_groups[filter].Push (group, type, positive_only, /* insert_mixed=*/ !positive_only);
512
+ }
505
513
}
506
514
}
507
515
};
508
516
509
517
push_output_groups (spk_to_groups_map, /* positive_only=*/ false );
510
518
push_output_groups (spk_to_positive_groups_map, /* positive_only=*/ true );
511
519
512
- return output_groups ;
520
+ return filtered_groups ;
513
521
}
514
522
515
523
// Returns true if the result contains an error and the message is not empty
516
524
static bool HasErrorMsg (const util::Result<SelectionResult>& res) { return !util::ErrorString (res).empty (); }
517
525
518
- util::Result<SelectionResult> AttemptSelection (const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const CoinsResult& available_coins ,
526
+ util::Result<SelectionResult> AttemptSelection (const CAmount& nTargetValue, OutputGroupTypeMap& groups ,
519
527
const CoinSelectionParams& coin_selection_params, bool allow_mixed_output_types)
520
528
{
521
- // Calculate all the output groups filtered by type at once
522
- OutputGroupTypeMap groups = GroupOutputs (wallet, available_coins, coin_selection_params, {eligibility_filter});
523
-
524
529
// Run coin selection on each OutputType and compute the Waste Metric
525
530
std::vector<SelectionResult> results;
526
- for (const auto & [type, coins] : available_coins.coins ) {
527
- auto group_for_type = groups.Find (type);
528
- if (!group_for_type) continue ;
529
- auto result{ChooseSelectionResult (nTargetValue, *group_for_type, coin_selection_params)};
531
+ for (auto & [type, group] : groups.groups_by_type ) {
532
+ auto result{ChooseSelectionResult (nTargetValue, group, coin_selection_params)};
530
533
// If any specific error message appears here, then something particularly wrong happened.
531
534
if (HasErrorMsg (result)) return result; // So let's return the specific error.
532
535
// Append the favorable result.
@@ -539,7 +542,7 @@ util::Result<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmo
539
542
// If we can't fund the transaction from any individual OutputType, run coin selection one last time
540
543
// over all available coins, which would allow mixing.
541
544
// If TypesCount() <= 1, there is nothing to mix.
542
- if (allow_mixed_output_types && available_coins .TypesCount () > 1 ) {
545
+ if (allow_mixed_output_types && groups .TypesCount () > 1 ) {
543
546
return ChooseSelectionResult (nTargetValue, groups.all_groups , coin_selection_params);
544
547
}
545
548
// 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
@@ -634,11 +637,6 @@ util::Result<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& av
634
637
return op_selection_result;
635
638
}
636
639
637
- struct SelectionFilter {
638
- CoinEligibilityFilter filter;
639
- bool allow_mixed_output_types{true };
640
- };
641
-
642
640
util::Result<SelectionResult> AutomaticCoinSelection (const CWallet& wallet, CoinsResult& available_coins, const CAmount& value_to_select, const CCoinControl& coin_control, const CoinSelectionParams& coin_selection_params)
643
641
{
644
642
unsigned int limit_ancestor_count = 0 ;
@@ -693,12 +691,17 @@ util::Result<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, Coin
693
691
}
694
692
}
695
693
694
+ // Group outputs and map them by coin eligibility filter
695
+ FilteredOutputGroups filtered_groups = GroupOutputs (wallet, available_coins, coin_selection_params, ordered_filters);
696
+
696
697
// Walk-through the filters until the solution gets found.
697
698
// If no solution is found, return the first detailed error (if any).
698
699
// future: add "error level" so the worst one can be picked instead.
699
700
std::vector<util::Result<SelectionResult>> res_detailed_errors;
700
701
for (const auto & select_filter : ordered_filters) {
701
- if (auto res{AttemptSelection (wallet, value_to_select, select_filter.filter , available_coins,
702
+ auto it = filtered_groups.find (select_filter.filter );
703
+ if (it == filtered_groups.end ()) continue ;
704
+ if (auto res{AttemptSelection (value_to_select, it->second ,
702
705
coin_selection_params, select_filter.allow_mixed_output_types )}) {
703
706
return res; // result found
704
707
} else {
0 commit comments