@@ -404,12 +404,12 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
404404 return result;
405405}
406406
407- OutputGroupTypeMap GroupOutputs (const CWallet& wallet,
407+ FilteredOutputGroups GroupOutputs (const CWallet& wallet,
408408 const CoinsResult& coins,
409409 const CoinSelectionParams& coin_sel_params,
410- const CoinEligibilityFilter& filter )
410+ const std::vector<SelectionFilter>& filters )
411411{
412- OutputGroupTypeMap output_groups ;
412+ FilteredOutputGroups filtered_groups ;
413413
414414 if (!coin_sel_params.m_avoid_partial_spends ) {
415415 // Allowing partial spends means no grouping. Each COutput gets its own OutputGroup
@@ -426,11 +426,15 @@ OutputGroupTypeMap GroupOutputs(const CWallet& wallet,
426426 OutputGroup group (coin_sel_params);
427427 group.Insert (std::make_shared<COutput>(output), ancestors, descendants);
428428
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+ }
431435 }
432436 }
433- return output_groups ;
437+ return filtered_groups ;
434438 }
435439
436440 // We want to combine COutputs that have the same scriptPubKey into single OutputGroups
@@ -492,41 +496,40 @@ OutputGroupTypeMap GroupOutputs(const CWallet& wallet,
492496 for (auto group_it = groups.rbegin (); group_it != groups.rend (); group_it++) {
493497 const OutputGroup& group = *group_it;
494498
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 ;
496503
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+ }
501508
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+ }
505513 }
506514 }
507515 };
508516
509517 push_output_groups (spk_to_groups_map, /* positive_only=*/ false );
510518 push_output_groups (spk_to_positive_groups_map, /* positive_only=*/ true );
511519
512- return output_groups ;
520+ return filtered_groups ;
513521}
514522
515523// Returns true if the result contains an error and the message is not empty
516524static bool HasErrorMsg (const util::Result<SelectionResult>& res) { return !util::ErrorString (res).empty (); }
517525
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 ,
519527 const CoinSelectionParams& coin_selection_params, bool allow_mixed_output_types)
520528{
521- // Calculate all the output groups filtered by type at once
522- OutputGroupTypeMap groups = GroupOutputs (wallet, available_coins, coin_selection_params, {eligibility_filter});
523-
524529 // Run coin selection on each OutputType and compute the Waste Metric
525530 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)};
530533 // If any specific error message appears here, then something particularly wrong happened.
531534 if (HasErrorMsg (result)) return result; // So let's return the specific error.
532535 // Append the favorable result.
@@ -539,7 +542,7 @@ util::Result<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmo
539542 // If we can't fund the transaction from any individual OutputType, run coin selection one last time
540543 // over all available coins, which would allow mixing.
541544 // 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 ) {
543546 return ChooseSelectionResult (nTargetValue, groups.all_groups , coin_selection_params);
544547 }
545548 // 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
634637 return op_selection_result;
635638}
636639
637- struct SelectionFilter {
638- CoinEligibilityFilter filter;
639- bool allow_mixed_output_types{true };
640- };
641-
642640util::Result<SelectionResult> AutomaticCoinSelection (const CWallet& wallet, CoinsResult& available_coins, const CAmount& value_to_select, const CCoinControl& coin_control, const CoinSelectionParams& coin_selection_params)
643641{
644642 unsigned int limit_ancestor_count = 0 ;
@@ -693,12 +691,17 @@ util::Result<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, Coin
693691 }
694692 }
695693
694+ // Group outputs and map them by coin eligibility filter
695+ FilteredOutputGroups filtered_groups = GroupOutputs (wallet, available_coins, coin_selection_params, ordered_filters);
696+
696697 // Walk-through the filters until the solution gets found.
697698 // If no solution is found, return the first detailed error (if any).
698699 // future: add "error level" so the worst one can be picked instead.
699700 std::vector<util::Result<SelectionResult>> res_detailed_errors;
700701 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 ,
702705 coin_selection_params, select_filter.allow_mixed_output_types )}) {
703706 return res; // result found
704707 } else {
0 commit comments