Rewrite OutputGroups to be clearer and to use scriptPubKeys
Rewrite OutputGroups so that the logic is easier to follow and understand. There is a slight behavior change as OutputGroups will be grouped by scriptPubKey rather than CTxDestination as before. This should have no effect on users as all addresses are a CTxDestination. However by using scriptPubKeys, we can correctly group outputs which fall into the NoDestination case. But we also shouldn't have any NoDestination outputs.
This commit is contained in:
1 parent
f6b3052739
commit
5d4597666d
2 files changed
+74
-42
No files matched your search
+73
-41
@@ -4193,58 +4193,90 @@ bool CWalletTx::IsImmatureCoinBase() const
|
|||||||
return GetBlocksToMaturity() > 0;
|
return GetBlocksToMaturity() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
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
|
||||||
std::vector<OutputGroup> groups;
|
{
|
||||||
std::map<CTxDestination, OutputGroup> gmap;
|
std::vector<OutputGroup> groups_out;
|
||||||
std::set<CTxDestination> full_groups;
|
|
||||||
|
|
||||||
for (const auto& output : outputs) {
|
if (separate_coins) {
|
||||||
if (output.fSpendable) {
|
// Single coin means no grouping. Each COutput gets its own OutputGroup.
|
||||||
CTxDestination dst;
|
for (const COutput& output : outputs) {
|
||||||
CInputCoin input_coin = output.GetInputCoin();
|
// Skip outputs we cannot spend
|
||||||
|
if (!output.fSpendable) continue;
|
||||||
|
|
||||||
size_t ancestors, descendants;
|
size_t ancestors, descendants;
|
||||||
chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
|
chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
|
||||||
if (!single_coin && ExtractDestination(output.tx->tx->vout[output.i].scriptPubKey, dst)) {
|
CInputCoin input_coin = output.GetInputCoin();
|
||||||
auto it = gmap.find(dst);
|
|
||||||
if (it != gmap.end()) {
|
// Make an OutputGroup containing just this output
|
||||||
// Limit output groups to no more than OUTPUT_GROUP_MAX_ENTRIES
|
OutputGroup group{effective_feerate, long_term_feerate};
|
||||||
// number of entries, to protect against inadvertently creating
|
group.Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants, positive_only);
|
||||||
// a too-large transaction when using -avoidpartialspends to
|
|
||||||
// prevent breaking consensus or surprising users with a very
|
// Check the OutputGroup's eligibility. Only add the eligible ones.
|
||||||
// high amount of fees.
|
if (positive_only && group.effective_value <= 0) continue;
|
||||||
if (it->second.m_outputs.size() >= OUTPUT_GROUP_MAX_ENTRIES) {
|
if (group.m_outputs.size() > 0 && group.EligibleForSpending(filter)) groups_out.push_back(group);
|
||||||
groups.push_back(it->second);
|
|
||||||
it->second = OutputGroup{effective_feerate, long_term_feerate};
|
|
||||||
full_groups.insert(dst);
|
|
||||||
}
|
|
||||||
it->second.Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants, positive_only);
|
|
||||||
} else {
|
|
||||||
auto ins = gmap.emplace(dst, OutputGroup{effective_feerate, long_term_feerate});
|
|
||||||
ins.first->second.Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants, positive_only);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// This is for if each output gets it's own OutputGroup
|
|
||||||
OutputGroup coin(effective_feerate, long_term_feerate);
|
|
||||||
coin.Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants, positive_only);
|
|
||||||
if (positive_only && coin.effective_value <= 0) continue;
|
|
||||||
if (coin.m_outputs.size() > 0 && coin.EligibleForSpending(filter)) groups.push_back(coin);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return groups_out;
|
||||||
}
|
}
|
||||||
if (!single_coin) {
|
|
||||||
for (auto& it : gmap) {
|
// We want to combine COutputs that have the same scriptPubKey into single OutputGroups
|
||||||
auto& group = it.second;
|
// except when there are more than OUTPUT_GROUP_MAX_ENTRIES COutputs grouped in an OutputGroup.
|
||||||
if (full_groups.count(it.first) > 0 && !filter.m_include_partial_groups) {
|
// To do this, we maintain a map where the key is the scriptPubKey and the value is a vector of OutputGroups.
|
||||||
// Don't include partial groups if we don't want them
|
// For each COutput, we check if the scriptPubKey is in the map, and if it is, the COutput's CInputCoin is added
|
||||||
|
// to the last OutputGroup in the vector for the scriptPubKey. When the last OutputGroup has
|
||||||
|
// OUTPUT_GROUP_MAX_ENTRIES CInputCoins, a new OutputGroup is added to the end of the vector.
|
||||||
|
std::map<CScript, std::vector<OutputGroup>> spk_to_groups_map;
|
||||||
|
for (const auto& output : outputs) {
|
||||||
|
// Skip outputs we cannot spend
|
||||||
|
if (!output.fSpendable) continue;
|
||||||
|
|
||||||
|
size_t ancestors, descendants;
|
||||||
|
chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
|
||||||
|
CInputCoin input_coin = output.GetInputCoin();
|
||||||
|
CScript spk = input_coin.txout.scriptPubKey;
|
||||||
|
|
||||||
|
std::vector<OutputGroup>& groups = spk_to_groups_map[spk];
|
||||||
|
|
||||||
|
if (groups.size() == 0) {
|
||||||
|
// No OutputGroups for this scriptPubKey yet, add one
|
||||||
|
groups.emplace_back(effective_feerate, long_term_feerate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the last OutputGroup in the vector so that we can add the CInputCoin to it
|
||||||
|
// A pointer is used here so that group can be reassigned later if it is full.
|
||||||
|
OutputGroup* group = &groups.back();
|
||||||
|
|
||||||
|
// Check if this OutputGroup is full. We limit to OUTPUT_GROUP_MAX_ENTRIES when using -avoidpartialspends
|
||||||
|
// to avoid surprising users with very high fees.
|
||||||
|
if (group->m_outputs.size() >= OUTPUT_GROUP_MAX_ENTRIES) {
|
||||||
|
// The last output group is full, add a new group to the vector and use that group for the insertion
|
||||||
|
groups.emplace_back(effective_feerate, long_term_feerate);
|
||||||
|
group = &groups.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the input_coin to group
|
||||||
|
group->Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants, positive_only);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we go through the entire map and pull out the OutputGroups
|
||||||
|
for (const auto& spk_and_groups_pair: spk_to_groups_map) {
|
||||||
|
const std::vector<OutputGroup>& groups_per_spk= spk_and_groups_pair.second;
|
||||||
|
|
||||||
|
// Go through the vector backwards. This allows for the first item we deal with being the partial group.
|
||||||
|
for (auto group_it = groups_per_spk.rbegin(); group_it != groups_per_spk.rend(); group_it++) {
|
||||||
|
const OutputGroup& group = *group_it;
|
||||||
|
|
||||||
|
// Don't include partial groups if there are full groups too and we don't want partial groups
|
||||||
|
if (group_it == groups_per_spk.rbegin() && groups_per_spk.size() > 1 && !filter.m_include_partial_groups) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// If the OutputGroup is not eligible, don't add it
|
|
||||||
|
// Check the OutputGroup's eligibility. Only add the eligible ones.
|
||||||
if (positive_only && group.effective_value <= 0) continue;
|
if (positive_only && group.effective_value <= 0) continue;
|
||||||
if (group.m_outputs.size() > 0 && group.EligibleForSpending(filter)) groups.push_back(group);
|
if (group.m_outputs.size() > 0 && group.EligibleForSpending(filter)) groups_out.push_back(group);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return groups;
|
|
||||||
|
return groups_out;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CWallet::IsCrypted() const
|
bool CWallet::IsCrypted() const
|
||||||
|
|||||||
+1
-1
@@ -841,7 +841,7 @@ public:
|
|||||||
bool IsSpentKey(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
bool IsSpentKey(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
void SetSpentKeyState(WalletBatch& batch, const uint256& hash, unsigned int n, bool used, std::set<CTxDestination>& tx_destinations) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
void SetSpentKeyState(WalletBatch& batch, const uint256& hash, unsigned int n, bool used, std::set<CTxDestination>& tx_destinations) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
|
|
||||||
std::vector<OutputGroup> 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;
|
std::vector<OutputGroup> 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;
|
||||||
|
|
||||||
bool IsLockedCoin(uint256 hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
bool IsLockedCoin(uint256 hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
void LockCoin(const COutPoint& output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
void LockCoin(const COutPoint& output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
|
|||||||
Reference in new issue
Block a user