wallet, doc: clarify the coin selection filters that enforce cluster count #34037

pull glozow wants to merge 2 commits into bitcoin:master from glozow:2025-12-ancestry changing 8 files +42 −35
  1. glozow commented at 1:28 am on December 10, 2025: member

    Followup to #33629.

    Fix misleading docs and variable names. Namely, getTransactionAncestry returns the cluster count, not max descendant count of ancestor set (not worth reimplementing as it is merely a heuristic). No behavior changes - I don’t think much needs to be changed for the first release containing cluster mempool.

    Current CoinEligibilityFilters enforce a maximum ancestor count (summed across all outputs, potentially overestimating) and max descendant count across ancestors of the output.

    Since #33629, these filters have started using cluster count instead of max desc count across ancestors. The change isn’t dangerous, as the cluster count bounds descendant count as well. Currently, the wallet is essentially enforcing a mixture of both limits - this is good while we are transitioning. Note that the cluster count enforced is 25, not 64, since it’s grabbing the node’s descendant count limit. While it is not an apples-to-apples comparison, a cluster count limit of 25 helps us avoid busting legacy descendant limits (which will be common on the network for a while).

    Potential things for the future, out of scope for this PR:

    • When we get rid of the ancestor/descendant config options, getPackageLimits can probably be replaced with hard-coded values.
    • Change the OutputGroups to track the actual cluster count that results from spending these outputs and merging their clusters.
    • Loosen from 25 after that policy is no longer common.
    • Clean up getPackageLimits.
  2. [refactor] rename variable to clarify it is unused and cluster count 9c299f6c43
  3. glozow added the label Docs on Dec 10, 2025
  4. glozow added the label Wallet on Dec 10, 2025
  5. DrahtBot commented at 1:28 am on December 10, 2025: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/34037.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    Concept ACK rkrux

    If your review is incorrectly listed, please copy-paste <!–meta-tag:bot-skip–> into the comment that the bot should ignore.

    LLM Linter (✨ experimental)

    Possible places where named args for integral literals may be used (e.g. func(x, /*named_arg=*/0) in C++, and func(x, named_arg=0) in Python):

    • CoinEligibilityFilter(0, 1, 2) in src/wallet/spend.cpp

    2025-12-10

  6. DrahtBot added the label CI failed on Dec 10, 2025
  7. DrahtBot commented at 2:43 am on December 10, 2025: contributor

    🚧 At least one of the CI tasks failed. Task tidy: https://github.com/bitcoin/bitcoin/actions/runs/20084176691/job/57617902247 LLM reason (✨ experimental): clang-tidy errors (bugprone-argument-comment) due to mismatched argument comments for the cluster_count parameter in coinselection tests.

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  8. [doc] coin selection filters by max cluster count, not descendant
    Avoid confusion by clarifying the docs and renaming the variables that
    now hold cluster count rather than descendant count. No behavior change.
    dc5e20cf97
  9. glozow force-pushed on Dec 10, 2025
  10. DrahtBot removed the label CI failed on Dec 10, 2025
  11. rkrux commented at 11:35 am on December 12, 2025: contributor

    Since #33639, these filters have started using cluster count instead of max desc count across ancestors.

    The linked PR doesn’t seem correct to me. Is it supposed to be #33629 instead?

  12. in src/wallet/spend.cpp:409 in dc5e20cf97
    404@@ -405,8 +405,8 @@ CoinsResult AvailableCoins(const CWallet& wallet,
    405                     if (wtx.truc_child_in_mempool.has_value()) continue;
    406 
    407                     // this unconfirmed v3 transaction has a parent: spending would create a third generation
    408-                    size_t ancestors, descendants;
    409-                    wallet.chain().getTransactionAncestry(wtx.tx->GetHash(), ancestors, descendants);
    410+                    size_t ancestors, unused_cluster_count;
    411+                    wallet.chain().getTransactionAncestry(wtx.tx->GetHash(), ancestors, unused_cluster_count);
    


    rkrux commented at 1:22 pm on December 12, 2025:

    In dc5e20cf97f87fa9a37aeea76a480e412c49834c “[doc] coin selection filters by max cluster count, not descendant”

    This portion seems suitable for the first commit - 9c299f6c4364bb7110daec16dd8e1feb36737e97 “[refactor] rename variable to clarify it is unused and cluster count”.

  13. in src/wallet/spend.cpp:1 in dc5e20cf97


    rkrux commented at 1:30 pm on December 12, 2025:

    In dc5e20cf97f87fa9a37aeea76a480e412c49834c “[doc] coin selection filters by max cluster count, not descendant”

    The following remaining places within GroupOutputs seems appropriate to be updated in this commit because getTransactionAncestry and OutputGroup.Insert() accept cluster_count now.

    Based on the tone in the PR description, I’m not certain if it was intentional to skip these changes.

     0diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
     1index 59c6ab116c..e2e2403a99 100644
     2--- a/src/wallet/spend.cpp
     3+++ b/src/wallet/spend.cpp
     4@@ -583,12 +583,12 @@ FilteredOutputGroups GroupOutputs(const CWallet& wallet,
     5         for (const auto& [type, outputs] : coins.coins) {
     6             for (const COutput& output : outputs) {
     7                 // Get mempool info
     8-                size_t ancestors, descendants;
     9-                wallet.chain().getTransactionAncestry(output.outpoint.hash, ancestors, descendants);
    10+                size_t ancestors, cluster_count;
    11+                wallet.chain().getTransactionAncestry(output.outpoint.hash, ancestors, cluster_count);
    12 
    13                 // Create a new group per output and add it to the all groups vector
    14                 OutputGroup group(coin_sel_params);
    15-                group.Insert(std::make_shared<COutput>(output), ancestors, descendants);
    16+                group.Insert(std::make_shared<COutput>(output), ancestors, cluster_count);
    17 
    18                 // Each filter maps to a different set of groups
    19                 bool accepted = false;
    20@@ -612,7 +612,7 @@ FilteredOutputGroups GroupOutputs(const CWallet& wallet,
    21     // OUTPUT_GROUP_MAX_ENTRIES COutputs, a new OutputGroup is added to the end of the vector.
    22     typedef std::map<std::pair<CScript, OutputType>, std::vector<OutputGroup>> ScriptPubKeyToOutgroup;
    23     const auto& insert_output = [&](
    24-            const std::shared_ptr<COutput>& output, OutputType type, size_t ancestors, size_t descendants,
    25+            const std::shared_ptr<COutput>& output, OutputType type, size_t ancestors, size_t cluster_count,
    26             ScriptPubKeyToOutgroup& groups_map) {
    27         std::vector<OutputGroup>& groups = groups_map[std::make_pair(output->txout.scriptPubKey,type)];
    28 
    29@@ -633,24 +633,24 @@ FilteredOutputGroups GroupOutputs(const CWallet& wallet,
    30             group = &groups.back();
    31         }
    32 
    33-        group->Insert(output, ancestors, descendants);
    34+        group->Insert(output, ancestors, cluster_count);
    35     };
    36 
    37     ScriptPubKeyToOutgroup spk_to_groups_map;
    38     ScriptPubKeyToOutgroup spk_to_positive_groups_map;
    39     for (const auto& [type, outs] : coins.coins) {
    40         for (const COutput& output : outs) {
    41-            size_t ancestors, descendants;
    42-            wallet.chain().getTransactionAncestry(output.outpoint.hash, ancestors, descendants);
    43+            size_t ancestors, cluster_count;
    44+            wallet.chain().getTransactionAncestry(output.outpoint.hash, ancestors, cluster_count);
    45 
    46             const auto& shared_output = std::make_shared<COutput>(output);
    47             // Filter for positive only before adding the output
    48             if (output.GetEffectiveValue() > 0) {
    49-                insert_output(shared_output, type, ancestors, descendants, spk_to_positive_groups_map);
    50+                insert_output(shared_output, type, ancestors, cluster_count, spk_to_positive_groups_map);
    51             }
    52 
    53             // 'All' groups
    54-            insert_output(shared_output, type, ancestors, descendants, spk_to_groups_map);
    55+            insert_output(shared_output, type, ancestors, cluster_count, spk_to_groups_map);
    56         }
    57     }
    58 
    
  14. rkrux commented at 1:41 pm on December 12, 2025: contributor

    Concept ACK dc5e20cf97f87fa9a37aeea76a480e412c49834c

    Nice, this change comes at a right time. Recently, I got confused by getTransaction Ancestry() in a previous PR: #33528 (review) - not anymore.

    I have limited idea of Cluster Mempool currently, thus I have based this review off of the GetTransactionAncestry implementation in src/txmempool.h|cpp.


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2025-12-13 03:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me