@@ -404,29 +404,33 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
404404 return result;
405405}
406406
407- std::vector<OutputGroup> GroupOutputs (const CWallet& wallet, const std::vector<COutput>& outputs, const CoinSelectionParams& coin_sel_params, const CoinEligibilityFilter& filter, bool positive_only)
407+ OutputGroupTypeMap GroupOutputs (const CWallet& wallet,
408+ const CoinsResult& coins,
409+ const CoinSelectionParams& coin_sel_params,
410+ const CoinEligibilityFilter& filter)
408411{
409- std::vector<OutputGroup> groups_out ;
412+ OutputGroupTypeMap output_groups ;
410413
411414 if (!coin_sel_params.m_avoid_partial_spends ) {
412- // Allowing partial spends means no grouping. Each COutput gets its own OutputGroup.
413- for (const COutput& output : outputs ) {
414- // Skip outputs we cannot spend
415- if (!output. spendable ) continue ;
416-
417- size_t ancestors, descendants;
418- wallet. chain (). getTransactionAncestry (output. outpoint . hash , ancestors, descendants);
419-
420- // If 'positive_only' is set, filter for positive only before adding the coin
421- if (!positive_only || output. GetEffectiveValue () > 0 ) {
422- // Make an OutputGroup containing just this output
423- OutputGroup group{ coin_sel_params} ;
415+ // Allowing partial spends means no grouping. Each COutput gets its own OutputGroup
416+ for (const auto & [type, outputs] : coins. coins ) {
417+ for ( const COutput& output : outputs) {
418+ // Skip outputs we cannot spend
419+ if (!output. spendable ) continue ;
420+
421+ // Get mempool info
422+ size_t ancestors, descendants;
423+ wallet. chain (). getTransactionAncestry (output. outpoint . hash , ancestors, descendants);
424+
425+ // Create a new group per output and add it to the all groups vector
426+ OutputGroup group ( coin_sel_params) ;
424427 group.Insert (std::make_shared<COutput>(output), ancestors, descendants);
425428
426- if (group.EligibleForSpending (filter)) groups_out.push_back (group);
429+ if (!group.EligibleForSpending (filter)) continue ;
430+ output_groups.Push (group, type, /* insert_positive=*/ true , /* insert_mixed=*/ true );
427431 }
428432 }
429- return groups_out ;
433+ return output_groups ;
430434 }
431435
432436 // We want to combine COutputs that have the same scriptPubKey into single OutputGroups
@@ -435,16 +439,12 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
435439 // For each COutput, we check if the scriptPubKey is in the map, and if it is, the COutput is added
436440 // to the last OutputGroup in the vector for the scriptPubKey. When the last OutputGroup has
437441 // OUTPUT_GROUP_MAX_ENTRIES COutputs, a new OutputGroup is added to the end of the vector.
438- std::map<CScript, std::vector<OutputGroup>> spk_to_groups_map;
439- for (const auto & output : outputs) {
440- // Skip outputs we cannot spend
441- if (!output.spendable ) continue ;
442-
443- size_t ancestors, descendants;
444- wallet.chain ().getTransactionAncestry (output.outpoint .hash , ancestors, descendants);
445- CScript spk = output.txout .scriptPubKey ;
446-
447- std::vector<OutputGroup>& groups = spk_to_groups_map[spk];
442+ typedef std::map<std::pair<CScript, OutputType>, std::vector<OutputGroup>> ScriptPubKeyToOutgroup;
443+ const auto & group_outputs = [](
444+ const COutput& output, OutputType type, size_t ancestors, size_t descendants,
445+ ScriptPubKeyToOutgroup& groups_map, const CoinSelectionParams& coin_sel_params,
446+ bool positive_only) {
447+ std::vector<OutputGroup>& groups = groups_map[std::make_pair (output.txout .scriptPubKey ,type)];
448448
449449 if (groups.size () == 0 ) {
450450 // No OutputGroups for this scriptPubKey yet, add one
@@ -467,28 +467,49 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
467467 if (!positive_only || output.GetEffectiveValue () > 0 ) {
468468 group->Insert (std::make_shared<COutput>(output), ancestors, descendants);
469469 }
470+ };
471+
472+ ScriptPubKeyToOutgroup spk_to_groups_map;
473+ ScriptPubKeyToOutgroup spk_to_positive_groups_map;
474+ for (const auto & [type, outs] : coins.coins ) {
475+ for (const COutput& output : outs) {
476+ // Skip outputs we cannot spend
477+ if (!output.spendable ) continue ;
478+
479+ size_t ancestors, descendants;
480+ wallet.chain ().getTransactionAncestry (output.outpoint .hash , ancestors, descendants);
481+
482+ group_outputs (output, type, ancestors, descendants, spk_to_groups_map, coin_sel_params, /* positive_only=*/ false );
483+ group_outputs (output, type, ancestors, descendants, spk_to_positive_groups_map,
484+ coin_sel_params, /* positive_only=*/ true );
485+ }
470486 }
471487
472- // Now we go through the entire map and pull out the OutputGroups
473- for (const auto & spk_and_groups_pair: spk_to_groups_map) {
474- const std::vector<OutputGroup>& groups_per_spk= spk_and_groups_pair.second ;
488+ // Now we go through the entire maps and pull out the OutputGroups
489+ const auto & push_output_groups = [&](const ScriptPubKeyToOutgroup& groups_map, bool positive_only) {
490+ for (const auto & [script, groups] : groups_map) {
491+ // Go through the vector backwards. This allows for the first item we deal with being the partial group.
492+ for (auto group_it = groups.rbegin (); group_it != groups.rend (); group_it++) {
493+ const OutputGroup& group = *group_it;
475494
476- // Go through the vector backwards. This allows for the first item we deal with being the partial group.
477- for (auto group_it = groups_per_spk.rbegin (); group_it != groups_per_spk.rend (); group_it++) {
478- const OutputGroup& group = *group_it;
495+ if (!group.EligibleForSpending (filter)) continue ;
479496
480- // Don't include partial groups if there are full groups too and we don't want partial groups
481- if (group_it == groups_per_spk .rbegin () && groups_per_spk .size () > 1 && !filter.m_include_partial_groups ) {
482- continue ;
483- }
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+ }
484501
485- // Check the OutputGroup's eligibility. Only add the eligible ones.
486- if (positive_only && group.GetSelectionAmount () <= 0 ) continue ;
487- if (group.m_outputs .size () > 0 && group.EligibleForSpending (filter)) groups_out.push_back (group);
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);
505+ }
488506 }
489- }
507+ };
490508
491- return groups_out;
509+ push_output_groups (spk_to_groups_map, /* positive_only=*/ false );
510+ push_output_groups (spk_to_positive_groups_map, /* positive_only=*/ true );
511+
512+ return output_groups;
492513}
493514
494515// Returns true if the result contains an error and the message is not empty
@@ -497,13 +518,15 @@ static bool HasErrorMsg(const util::Result<SelectionResult>& res) { return !util
497518util::Result<SelectionResult> AttemptSelection (const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const CoinsResult& available_coins,
498519 const CoinSelectionParams& coin_selection_params, bool allow_mixed_output_types)
499520{
521+ // Calculate all the output groups filtered by type at once
522+ OutputGroupTypeMap groups = GroupOutputs (wallet, available_coins, coin_selection_params, {eligibility_filter});
523+
500524 // Run coin selection on each OutputType and compute the Waste Metric
501525 std::vector<SelectionResult> results;
502526 for (const auto & [type, coins] : available_coins.coins ) {
503- Groups groups;
504- groups.positive_group = GroupOutputs (wallet, coins, coin_selection_params, eligibility_filter, true /* positive_only */ );
505- groups.mixed_group = GroupOutputs (wallet, coins, coin_selection_params, eligibility_filter, false /* positive_only */ );
506- auto result{ChooseSelectionResult (nTargetValue, groups, coin_selection_params)};
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)};
507530 // If any specific error message appears here, then something particularly wrong happened.
508531 if (HasErrorMsg (result)) return result; // So let's return the specific error.
509532 // Append the favorable result.
@@ -517,11 +540,7 @@ util::Result<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmo
517540 // over all available coins, which would allow mixing.
518541 // If TypesCount() <= 1, there is nothing to mix.
519542 if (allow_mixed_output_types && available_coins.TypesCount () > 1 ) {
520- const auto & all = available_coins.All ();
521- Groups groups;
522- groups.positive_group = GroupOutputs (wallet, all, coin_selection_params, eligibility_filter, true /* positive_only */ );
523- groups.mixed_group = GroupOutputs (wallet, all, coin_selection_params, eligibility_filter, false /* positive_only */ );
524- return ChooseSelectionResult (nTargetValue, groups, coin_selection_params);
543+ return ChooseSelectionResult (nTargetValue, groups.all_groups , coin_selection_params);
525544 }
526545 // 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
527546 // find a solution using all available coins
0 commit comments