@@ -404,29 +404,33 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
404
404
return result;
405
405
}
406
406
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)
408
411
{
409
- std::vector<OutputGroup> groups_out ;
412
+ OutputGroupTypeMap output_groups ;
410
413
411
414
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) ;
424
427
group.Insert (std::make_shared<COutput>(output), ancestors, descendants);
425
428
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 );
427
431
}
428
432
}
429
- return groups_out ;
433
+ return output_groups ;
430
434
}
431
435
432
436
// 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
435
439
// For each COutput, we check if the scriptPubKey is in the map, and if it is, the COutput is added
436
440
// to the last OutputGroup in the vector for the scriptPubKey. When the last OutputGroup has
437
441
// 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)];
448
448
449
449
if (groups.size () == 0 ) {
450
450
// No OutputGroups for this scriptPubKey yet, add one
@@ -467,28 +467,49 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
467
467
if (!positive_only || output.GetEffectiveValue () > 0 ) {
468
468
group->Insert (std::make_shared<COutput>(output), ancestors, descendants);
469
469
}
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
+ }
470
486
}
471
487
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;
475
494
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 ;
479
496
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
+ }
484
501
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
+ }
488
506
}
489
- }
507
+ };
490
508
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;
492
513
}
493
514
494
515
// 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
497
518
util::Result<SelectionResult> AttemptSelection (const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const CoinsResult& available_coins,
498
519
const CoinSelectionParams& coin_selection_params, bool allow_mixed_output_types)
499
520
{
521
+ // Calculate all the output groups filtered by type at once
522
+ OutputGroupTypeMap groups = GroupOutputs (wallet, available_coins, coin_selection_params, {eligibility_filter});
523
+
500
524
// Run coin selection on each OutputType and compute the Waste Metric
501
525
std::vector<SelectionResult> results;
502
526
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)};
507
530
// If any specific error message appears here, then something particularly wrong happened.
508
531
if (HasErrorMsg (result)) return result; // So let's return the specific error.
509
532
// Append the favorable result.
@@ -517,11 +540,7 @@ util::Result<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmo
517
540
// over all available coins, which would allow mixing.
518
541
// If TypesCount() <= 1, there is nothing to mix.
519
542
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);
525
544
}
526
545
// 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
527
546
// find a solution using all available coins
0 commit comments