@@ -4193,58 +4193,90 @@ bool CWalletTx::IsImmatureCoinBase() const
4193
4193
return GetBlocksToMaturity () > 0 ;
4194
4194
}
4195
4195
4196
- std::vector<OutputGroup> CWallet::GroupOutputs (const std::vector<COutput>& outputs, bool single_coin, const CFeeRate& effective_feerate, const CFeeRate& long_term_feerate, const CoinEligibilityFilter& filter, bool positive_only) const {
4197
- std::vector<OutputGroup> groups;
4198
- std::map<CTxDestination, OutputGroup> gmap;
4199
- std::set<CTxDestination> full_groups;
4196
+ std::vector<OutputGroup> CWallet::GroupOutputs (const std::vector<COutput>& outputs, bool separate_coins, const CFeeRate& effective_feerate, const CFeeRate& long_term_feerate, const CoinEligibilityFilter& filter, bool positive_only) const
4197
+ {
4198
+ std::vector<OutputGroup> groups_out;
4200
4199
4201
- for (const auto & output : outputs) {
4202
- if (output.fSpendable ) {
4203
- CTxDestination dst;
4204
- CInputCoin input_coin = output.GetInputCoin ();
4200
+ if (separate_coins) {
4201
+ // Single coin means no grouping. Each COutput gets its own OutputGroup.
4202
+ for (const COutput& output : outputs) {
4203
+ // Skip outputs we cannot spend
4204
+ if (!output.fSpendable ) continue ;
4205
4205
4206
4206
size_t ancestors, descendants;
4207
4207
chain ().getTransactionAncestry (output.tx ->GetHash (), ancestors, descendants);
4208
- if (!single_coin && ExtractDestination (output.tx ->tx ->vout [output.i ].scriptPubKey , dst)) {
4209
- auto it = gmap.find (dst);
4210
- if (it != gmap.end ()) {
4211
- // Limit output groups to no more than OUTPUT_GROUP_MAX_ENTRIES
4212
- // number of entries, to protect against inadvertently creating
4213
- // a too-large transaction when using -avoidpartialspends to
4214
- // prevent breaking consensus or surprising users with a very
4215
- // high amount of fees.
4216
- if (it->second .m_outputs .size () >= OUTPUT_GROUP_MAX_ENTRIES) {
4217
- groups.push_back (it->second );
4218
- it->second = OutputGroup{effective_feerate, long_term_feerate};
4219
- full_groups.insert (dst);
4220
- }
4221
- it->second .Insert (input_coin, output.nDepth , output.tx ->IsFromMe (ISMINE_ALL), ancestors, descendants, positive_only);
4222
- } else {
4223
- auto ins = gmap.emplace (dst, OutputGroup{effective_feerate, long_term_feerate});
4224
- ins.first ->second .Insert (input_coin, output.nDepth , output.tx ->IsFromMe (ISMINE_ALL), ancestors, descendants, positive_only);
4225
- }
4226
- } else {
4227
- // This is for if each output gets it's own OutputGroup
4228
- OutputGroup coin (effective_feerate, long_term_feerate);
4229
- coin.Insert (input_coin, output.nDepth , output.tx ->IsFromMe (ISMINE_ALL), ancestors, descendants, positive_only);
4230
- if (positive_only && coin.effective_value <= 0 ) continue ;
4231
- if (coin.m_outputs .size () > 0 && coin.EligibleForSpending (filter)) groups.push_back (coin);
4232
- }
4208
+ CInputCoin input_coin = output.GetInputCoin ();
4209
+
4210
+ // Make an OutputGroup containing just this output
4211
+ OutputGroup group{effective_feerate, long_term_feerate};
4212
+ group.Insert (input_coin, output.nDepth , output.tx ->IsFromMe (ISMINE_ALL), ancestors, descendants, positive_only);
4213
+
4214
+ // Check the OutputGroup's eligibility. Only add the eligible ones.
4215
+ if (positive_only && group.effective_value <= 0 ) continue ;
4216
+ if (group.m_outputs .size () > 0 && group.EligibleForSpending (filter)) groups_out.push_back (group);
4233
4217
}
4218
+ return groups_out;
4234
4219
}
4235
- if (!single_coin) {
4236
- for (auto & it : gmap) {
4237
- auto & group = it.second ;
4238
- if (full_groups.count (it.first ) > 0 && !filter.m_include_partial_groups ) {
4239
- // Don't include partial groups if we don't want them
4220
+
4221
+ // We want to combine COutputs that have the same scriptPubKey into single OutputGroups
4222
+ // except when there are more than OUTPUT_GROUP_MAX_ENTRIES COutputs grouped in an OutputGroup.
4223
+ // To do this, we maintain a map where the key is the scriptPubKey and the value is a vector of OutputGroups.
4224
+ // For each COutput, we check if the scriptPubKey is in the map, and if it is, the COutput's CInputCoin is added
4225
+ // to the last OutputGroup in the vector for the scriptPubKey. When the last OutputGroup has
4226
+ // OUTPUT_GROUP_MAX_ENTRIES CInputCoins, a new OutputGroup is added to the end of the vector.
4227
+ std::map<CScript, std::vector<OutputGroup>> spk_to_groups_map;
4228
+ for (const auto & output : outputs) {
4229
+ // Skip outputs we cannot spend
4230
+ if (!output.fSpendable ) continue ;
4231
+
4232
+ size_t ancestors, descendants;
4233
+ chain ().getTransactionAncestry (output.tx ->GetHash (), ancestors, descendants);
4234
+ CInputCoin input_coin = output.GetInputCoin ();
4235
+ CScript spk = input_coin.txout .scriptPubKey ;
4236
+
4237
+ std::vector<OutputGroup>& groups = spk_to_groups_map[spk];
4238
+
4239
+ if (groups.size () == 0 ) {
4240
+ // No OutputGroups for this scriptPubKey yet, add one
4241
+ groups.emplace_back (effective_feerate, long_term_feerate);
4242
+ }
4243
+
4244
+ // Get the last OutputGroup in the vector so that we can add the CInputCoin to it
4245
+ // A pointer is used here so that group can be reassigned later if it is full.
4246
+ OutputGroup* group = &groups.back ();
4247
+
4248
+ // Check if this OutputGroup is full. We limit to OUTPUT_GROUP_MAX_ENTRIES when using -avoidpartialspends
4249
+ // to avoid surprising users with very high fees.
4250
+ if (group->m_outputs .size () >= OUTPUT_GROUP_MAX_ENTRIES) {
4251
+ // The last output group is full, add a new group to the vector and use that group for the insertion
4252
+ groups.emplace_back (effective_feerate, long_term_feerate);
4253
+ group = &groups.back ();
4254
+ }
4255
+
4256
+ // Add the input_coin to group
4257
+ group->Insert (input_coin, output.nDepth , output.tx ->IsFromMe (ISMINE_ALL), ancestors, descendants, positive_only);
4258
+ }
4259
+
4260
+ // Now we go through the entire map and pull out the OutputGroups
4261
+ for (const auto & spk_and_groups_pair: spk_to_groups_map) {
4262
+ const std::vector<OutputGroup>& groups_per_spk= spk_and_groups_pair.second ;
4263
+
4264
+ // Go through the vector backwards. This allows for the first item we deal with being the partial group.
4265
+ for (auto group_it = groups_per_spk.rbegin (); group_it != groups_per_spk.rend (); group_it++) {
4266
+ const OutputGroup& group = *group_it;
4267
+
4268
+ // Don't include partial groups if there are full groups too and we don't want partial groups
4269
+ if (group_it == groups_per_spk.rbegin () && groups_per_spk.size () > 1 && !filter.m_include_partial_groups ) {
4240
4270
continue ;
4241
4271
}
4242
- // If the OutputGroup is not eligible, don't add it
4272
+
4273
+ // Check the OutputGroup's eligibility. Only add the eligible ones.
4243
4274
if (positive_only && group.effective_value <= 0 ) continue ;
4244
- if (group.m_outputs .size () > 0 && group.EligibleForSpending (filter)) groups .push_back (group);
4275
+ if (group.m_outputs .size () > 0 && group.EligibleForSpending (filter)) groups_out .push_back (group);
4245
4276
}
4246
4277
}
4247
- return groups;
4278
+
4279
+ return groups_out;
4248
4280
}
4249
4281
4250
4282
bool CWallet::IsCrypted () const
0 commit comments