mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-03-20 20:49:50 +01:00
Merge #17824: wallet: Prefer full destination groups in coin selection
a2324e4d3ftest: Improve naming and logging of avoid_reuse tests (Fabian Jahr)1abbdac677wallet: Prefer full destination groups in coin selection (Fabian Jahr) Pull request description: Fixes #17603 (together with #17843) In the case of destination groups of >10 outputs existing in a wallet with `avoid_reuse` enabled, the grouping algorithm is adding left-over outputs as an "incomplete" group to the list of groups even when a full group has already been added. This leads to the strange behavior that if there are >10 outputs for a destination the transaction spending from that will effectively use `len(outputs) % 10` as inputs for that transaction. From the original PR and the code comment I understand the correct behavior should be the usage of 10 outputs. I opted for minimal changes in the current code although there maybe optimizations possible for cases with >20 outputs on a destination this sounds like too much of an edge case right now. ACKs for top commit: jonatack: Re-ACKa2324e4achow101: ACKa2324e4d3fkallewoof: ACKa2324e4d3fmeshcollider: Tested ACKa2324e4d3f(verified the new test fails on master without this change) Tree-SHA512: 4743779c5d469fcd16df5baf166024b1d3c8eaca151df1e8281b71df62b29541cf7bfee3f8ab48d83e3b34c9256e53fd38a7b146a54c79f9caa44cce3636971a
This commit is contained in:
@@ -2372,6 +2372,13 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
|
||||
++it;
|
||||
}
|
||||
|
||||
unsigned int limit_ancestor_count = 0;
|
||||
unsigned int limit_descendant_count = 0;
|
||||
chain().getPackageLimits(limit_ancestor_count, limit_descendant_count);
|
||||
size_t max_ancestors = (size_t)std::max<int64_t>(1, limit_ancestor_count);
|
||||
size_t max_descendants = (size_t)std::max<int64_t>(1, limit_descendant_count);
|
||||
bool fRejectLongChains = gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS);
|
||||
|
||||
// form groups from remaining coins; note that preset coins will not
|
||||
// automatically have their associated (same address) coins included
|
||||
if (coin_control.m_avoid_partial_spends && vCoins.size() > OUTPUT_GROUP_MAX_ENTRIES) {
|
||||
@@ -2380,14 +2387,7 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
|
||||
// explicitly shuffling the outputs before processing
|
||||
Shuffle(vCoins.begin(), vCoins.end(), FastRandomContext());
|
||||
}
|
||||
std::vector<OutputGroup> groups = GroupOutputs(vCoins, !coin_control.m_avoid_partial_spends);
|
||||
|
||||
unsigned int limit_ancestor_count;
|
||||
unsigned int limit_descendant_count;
|
||||
chain().getPackageLimits(limit_ancestor_count, limit_descendant_count);
|
||||
size_t max_ancestors = (size_t)std::max<int64_t>(1, limit_ancestor_count);
|
||||
size_t max_descendants = (size_t)std::max<int64_t>(1, limit_descendant_count);
|
||||
bool fRejectLongChains = gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS);
|
||||
std::vector<OutputGroup> groups = GroupOutputs(vCoins, !coin_control.m_avoid_partial_spends, max_ancestors);
|
||||
|
||||
bool res = value_to_select <= 0 ||
|
||||
SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 6, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) ||
|
||||
@@ -4184,32 +4184,49 @@ bool CWalletTx::IsImmatureCoinBase() const
|
||||
return GetBlocksToMaturity() > 0;
|
||||
}
|
||||
|
||||
std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const {
|
||||
std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outputs, bool single_coin, const size_t max_ancestors) const {
|
||||
std::vector<OutputGroup> groups;
|
||||
std::map<CTxDestination, OutputGroup> gmap;
|
||||
CTxDestination dst;
|
||||
std::set<CTxDestination> full_groups;
|
||||
|
||||
for (const auto& output : outputs) {
|
||||
if (output.fSpendable) {
|
||||
CTxDestination dst;
|
||||
CInputCoin input_coin = output.GetInputCoin();
|
||||
|
||||
size_t ancestors, descendants;
|
||||
chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
|
||||
if (!single_coin && ExtractDestination(output.tx->tx->vout[output.i].scriptPubKey, dst)) {
|
||||
// Limit output groups to no more than 10 entries, to protect
|
||||
// against inadvertently creating a too-large transaction
|
||||
// when using -avoidpartialspends
|
||||
if (gmap[dst].m_outputs.size() >= OUTPUT_GROUP_MAX_ENTRIES) {
|
||||
groups.push_back(gmap[dst]);
|
||||
gmap.erase(dst);
|
||||
auto it = gmap.find(dst);
|
||||
if (it != gmap.end()) {
|
||||
// Limit output groups to no more than OUTPUT_GROUP_MAX_ENTRIES
|
||||
// number of entries, to protect against inadvertently creating
|
||||
// a too-large transaction when using -avoidpartialspends to
|
||||
// prevent breaking consensus or surprising users with a very
|
||||
// high amount of fees.
|
||||
if (it->second.m_outputs.size() >= OUTPUT_GROUP_MAX_ENTRIES) {
|
||||
groups.push_back(it->second);
|
||||
it->second = OutputGroup{};
|
||||
full_groups.insert(dst);
|
||||
}
|
||||
it->second.Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants);
|
||||
} else {
|
||||
gmap[dst].Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants);
|
||||
}
|
||||
gmap[dst].Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants);
|
||||
} else {
|
||||
groups.emplace_back(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!single_coin) {
|
||||
for (const auto& it : gmap) groups.push_back(it.second);
|
||||
for (auto& it : gmap) {
|
||||
auto& group = it.second;
|
||||
if (full_groups.count(it.first) > 0) {
|
||||
// Make this unattractive as we want coin selection to avoid it if possible
|
||||
group.m_ancestors = max_ancestors - 1;
|
||||
}
|
||||
groups.push_back(group);
|
||||
}
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
@@ -830,7 +830,7 @@ public:
|
||||
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);
|
||||
|
||||
std::vector<OutputGroup> GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const;
|
||||
std::vector<OutputGroup> GroupOutputs(const std::vector<COutput>& outputs, bool single_coin, const size_t max_ancestors) const;
|
||||
|
||||
bool IsLockedCoin(uint256 hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
void LockCoin(const COutPoint& output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
||||
Reference in New Issue
Block a user