wallet: always do avoid partial spends if fees are within a specified range #14582

pull kallewoof wants to merge 2 commits into bitcoin:master from kallewoof:181026-try-avoidpartialspends changing 5 files +119 −3
  1. kallewoof commented at 6:45 AM on October 26, 2018: member

    The -avoidpartialspends feature is normally disabled, as it may affect the optimal fee for payments. This PR introduces a new parameter -maxapsfee (max avoid partial spends fee) which acts on the following values:

    • -1: disable partial spend avoidance completely (do not even try it)
    • 0: only do partial spend avoidance if fees are the same or better as the regular coin selection
    • 1..∞: use APS variant if the absolute fee difference is less than or equal to the max APS fee

    For values other than -1, the code will now try partial spend avoidance once, and if that gives a value within the accepted range, it will use that.

    Example: -maxapsfee=0.00001000 means the wallet will do regular coin select, APS coin select, and then pick AKS iff the absolute fee difference is <= 1000 satoshi.

    Edit: updated this to reflect the fact we are now using a max fee.

  2. kallewoof force-pushed on Oct 26, 2018
  3. fanquake added the label Wallet on Oct 26, 2018
  4. bitcoin deleted a comment on Oct 26, 2018
  5. DrahtBot commented at 11:52 PM on November 1, 2018: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #19501 (send* RPCs in the wallet returns the "fee reason" by stackman27)
    • #19168 (Refactor: Improve setup_clean_chain semantics by fjahr)
    • #17484 (wallet: add cached m_is_ibd to remove Chain::isInitialBlockDownload by ariard)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  6. meshcollider commented at 12:57 PM on November 9, 2018: contributor

    Don't the existing coin selection algorithms take into account the case where having no change output would reduce the fee? Wouldn't that have the same effect already? Or am I misunderstanding what this does / the current behaviour

  7. DrahtBot added the label Needs rebase on Nov 9, 2018
  8. kallewoof commented at 4:37 AM on November 12, 2018: member

    @MeshCollider

    Don't the existing coin selection algorithms take into account the case where having no change output would reduce the fee? Wouldn't that have the same effect already? Or am I misunderstanding what this does / the current behaviour

    The wallet will not use output grouping unless the user turns on -avoidpartialspends. The biggest reason is that output grouping will, in certain cases, result in higher fees because certain outputs will be bound together. This patch will try output grouping and use it if the fees do not increase, if users have turned -avoidpartialspends off.

  9. kallewoof force-pushed on Nov 12, 2018
  10. kallewoof force-pushed on Nov 12, 2018
  11. DrahtBot removed the label Needs rebase on Nov 12, 2018
  12. DrahtBot added the label Needs rebase on Jul 10, 2019
  13. kallewoof force-pushed on Jul 10, 2019
  14. kallewoof force-pushed on Jul 10, 2019
  15. DrahtBot removed the label Needs rebase on Jul 10, 2019
  16. kallewoof force-pushed on Jul 11, 2019
  17. kallewoof force-pushed on Jul 11, 2019
  18. kallewoof force-pushed on Jul 18, 2019
  19. DrahtBot removed the label Needs rebase on Jul 18, 2019
  20. DrahtBot added the label Needs rebase on Oct 24, 2019
  21. laanwj cross-referenced this on Nov 26, 2019 from issue Stop (avoid?) spending malleable change by gmaxwell
  22. kallewoof force-pushed on Dec 24, 2019
  23. DrahtBot removed the label Needs rebase on Dec 24, 2019
  24. kallewoof force-pushed on Jan 3, 2020
  25. kallewoof force-pushed on Jan 8, 2020
  26. gmaxwell commented at 11:14 AM on January 8, 2020: contributor

    I'm super happy to see work on this!

  27. kallewoof commented at 12:09 PM on January 8, 2020: member

    @gmaxwell Glad to hear it! I actually wrote code to turn this into a variable fee (see last commit), but was hesitant whether to push it now or wait. Your comment made me decide to push now. :)

  28. kallewoof force-pushed on Jan 8, 2020
  29. kallewoof renamed this:
    wallet: try -avoidpartialspends mode and use its result if fees do not change
    wallet: always do avoid partial spends if fees are within a specified range
    on Jan 8, 2020
  30. kallewoof force-pushed on Jan 8, 2020
  31. kallewoof force-pushed on Jan 8, 2020
  32. kallewoof force-pushed on Jan 9, 2020
  33. DrahtBot added the label Needs rebase on Jan 29, 2020
  34. kallewoof force-pushed on Jan 30, 2020
  35. DrahtBot removed the label Needs rebase on Jan 30, 2020
  36. DrahtBot cross-referenced this on Feb 11, 2020 from issue Switch to weight units for all feerates computation by darosior
  37. DrahtBot cross-referenced this on Apr 17, 2020 from issue Reverse cs_main, cs_wallet lock order and reduce cs_main locking by ariard
  38. DrahtBot cross-referenced this on Apr 18, 2020 from issue wallet: Avoid translating RPC errors by MarcoFalke
  39. DrahtBot added the label Needs rebase on May 1, 2020
  40. kallewoof force-pushed on May 2, 2020
  41. kallewoof force-pushed on May 2, 2020
  42. DrahtBot removed the label Needs rebase on May 2, 2020
  43. DrahtBot added the label Needs rebase on May 4, 2020
  44. kallewoof force-pushed on May 7, 2020
  45. kallewoof force-pushed on May 7, 2020
  46. DrahtBot removed the label Needs rebase on May 7, 2020
  47. kallewoof force-pushed on May 7, 2020
  48. DrahtBot cross-referenced this on Jun 4, 2020 from issue wallet: add cached m_is_ibd to remove Chain::isInitialBlockDownload by ariard
  49. DrahtBot cross-referenced this on Jul 12, 2020 from issue send* RPCs in the wallet returns the "fee reason" by stackman27
  50. DrahtBot cross-referenced this on Jul 21, 2020 from issue refactor: Pass ArgsManager into functions that register args by S3RK
  51. DrahtBot added the label Needs rebase on Jul 30, 2020
  52. kallewoof commented at 2:59 AM on July 31, 2020: member

    I've been rebasing this for 8 months now, with no progress. Taking this as lack of interest and closing. Ping me if this is ever interesting and I'll reopen / rebase.

  53. kallewoof closed this on Jul 31, 2020

  54. jonatack commented at 2:39 PM on July 31, 2020: contributor

    This looks interesting. I was unaware of this work, as it was before I began following this repo closely. Pinging @fjahr for thoughts.

    (Don't hesitate to occasionally put out a review beg on IRC for work like this.)

  55. fjahr commented at 11:51 AM on August 1, 2020: contributor

    Thanks for the ping @jonatack . I was unaware of this as well and also think this is interesting. @kallewoof how about a smaller scoped PR that adds the behavior of -maxapsfee=0 or another sensible default without adding the parameter itself? I think some developers have parameter fatigue and fear if a parameter implication is hard to understand for users then it will not be used. I also think this may not be used much because if users understand the implications and want that behavior they will probably just use -avoidpartialspends anyway. It would make this PR smaller and easier to review as well.

  56. kallewoof commented at 3:41 AM on August 3, 2020: member

    @jonatack @fjahr Thanks for shown interest, reopening.

    This initially was actually smaller scope, but it didn't require a lot of changes to make it fully featured, so I went ahead and did it. I am open to trimming this down, but I don't think the size was the reason for lack of reviews, more my reluctancy to review beg (and timezones). :)

  57. kallewoof reopened this on Aug 3, 2020

  58. sipa commented at 4:07 AM on August 3, 2020: member

    Concept ACK

  59. in src/wallet/wallet.cpp:3923 in dd634f2470 outdated
    3862 | @@ -3823,6 +3863,21 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
    3863 |          walletInstance->m_min_fee = CFeeRate(n);
    3864 |      }
    3865 |  
    3866 | +    if (gArgs.IsArgSet("-maxapsfee")) {
    3867 | +        CAmount n = 0;
    3868 | +        if (gArgs.GetArg("-maxapsfee", "") == "-1") {
    


    fjahr commented at 3:01 PM on August 3, 2020:

    I can't really think of a good reason why users would want to deactivate this instead of keeping it at 0. Maybe we don't need this option?


    kallewoof commented at 7:15 AM on August 5, 2020:

    If someone has a really big wallet with tons of UTXO:s, they may want to disable it as it effectively does coin selection twice every time, doubling the time. I don't think there are any other reasons, though.

  60. in src/wallet/init.cpp:50 in dd634f2470 outdated
      46 | @@ -47,6 +47,7 @@ void WalletInit::AddWalletOptions() const
      47 |      gArgs.AddArg("-fallbackfee=<amt>", strprintf("A fee rate (in %s/kB) that will be used when fee estimation has insufficient data. 0 to entirely disable the fallbackfee feature. (default: %s)",
      48 |                                                                 CURRENCY_UNIT, FormatMoney(DEFAULT_FALLBACK_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
      49 |      gArgs.AddArg("-keypool=<n>", strprintf("Set key pool size to <n> (default: %u). Warning: Smaller sizes may increase the risk of losing funds when restoring from an old backup, if none of the addresses in the original keypool have been used.", DEFAULT_KEYPOOL_SIZE), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
      50 | +    gArgs.AddArg("-maxapsfee=<n>", strprintf("Spend up to this amount in (absolute) fees (in %s) if it means prioritizing partial spend avoidance (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_MAX_AVOIDPARTIALSPEND_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
    


    fjahr commented at 3:07 PM on August 3, 2020:

    As a wallet option maybe a percentage would be better? Personally I think I would prefer to be able to set a max % increase in the feerate for this option. An absolute value seems brittle in times of fluctuating fees and users might forget about updating it.


    fjahr commented at 4:19 PM on August 3, 2020:

    The text here confused me at first but the way I see it works in the code and this refers to additional fees makes more sense and this is probably equally usable as a percentage for users.

        gArgs.AddArg("-maxapsfee=<n>", strprintf("Spend up to this amount in additional (absolute) fees (in %s) if it allows to use partial spend avoidance (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_MAX_AVOIDPARTIALSPEND_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
    

    kallewoof commented at 12:55 AM on August 6, 2020:

    Thanks, tweaked a little.

  61. in src/wallet/wallet.cpp:3081 in dd634f2470 outdated
    3017 | +        const CCoinControl& coin_control,
    3018 | +        bool sign)
    3019 | +{
    3020 | +    CAmount maxapsfee = gArgs.GetArg("-maxapsfee", DEFAULT_MAX_AVOIDPARTIALSPEND_FEE);
    3021 | +    int nChangePosIn = nChangePosInOut;
    3022 | +    CTransactionRef tx2 = tx;
    


    fjahr commented at 3:16 PM on August 3, 2020:

    nit:

        const CAmount max_aps_fee = gArgs.GetArg("-maxapsfee", DEFAULT_MAX_AVOIDPARTIALSPEND_FEE);
        const int change_pos_in = nChangePosInOut;
        CTransactionRef tx_aps = tx;
    

    kallewoof commented at 1:00 AM on August 6, 2020:

    Leaving most of this as is (first line is gone, 2nd is a primitive and the name is inherited, 3rd ~I did change the name~ I left name as is to match with the others).

  62. in src/wallet/wallet.cpp:3089 in dd634f2470 outdated
    3025 | +    if (res && nFeeRet > 0 /* 0 means non-functional fee rate estimation */ && maxapsfee > -1 && !coin_control.m_avoid_partial_spends) {
    3026 | +        CCoinControl tmp_cc = coin_control;
    3027 | +        tmp_cc.m_avoid_partial_spends = true;
    3028 | +        CAmount nFeeRet2;
    3029 | +        int nChangePosInOut2 = nChangePosIn;
    3030 | +        bilingual_str error2; // fired and forgotten; if an error occurs, we discard the results
    


    fjahr commented at 3:18 PM on August 3, 2020:

    nit:

            CAmount fee_ret_aps;
            int change_pos_in_out_aps = nChangePosIn;
            bilingual_str error_aps; // fired and forgotten; if an error occurs, we discard the results
    

    kallewoof commented at 1:02 AM on August 6, 2020:

    Keeping these as is, as their names are inherited.

  63. in src/wallet/wallet.cpp:3020 in dd634f2470 outdated
    3015 | +        int& nChangePosInOut,
    3016 | +        bilingual_str& error,
    3017 | +        const CCoinControl& coin_control,
    3018 | +        bool sign)
    3019 | +{
    3020 | +    CAmount maxapsfee = gArgs.GetArg("-maxapsfee", DEFAULT_MAX_AVOIDPARTIALSPEND_FEE);
    


    fjahr commented at 8:19 PM on August 3, 2020:

    can use m_max_aps_fee

  64. in src/wallet/wallet.cpp:3025 in dd634f2470 outdated
    3020 | +    CAmount maxapsfee = gArgs.GetArg("-maxapsfee", DEFAULT_MAX_AVOIDPARTIALSPEND_FEE);
    3021 | +    int nChangePosIn = nChangePosInOut;
    3022 | +    CTransactionRef tx2 = tx;
    3023 | +    bool res = CreateTransactionInternal(vecSend, tx, nFeeRet, nChangePosInOut, error, coin_control, sign);
    3024 | +    // try with avoidpartialspends unless it's enabled already
    3025 | +    if (res && nFeeRet > 0 /* 0 means non-functional fee rate estimation */ && maxapsfee > -1 && !coin_control.m_avoid_partial_spends) {
    


    fjahr commented at 8:19 PM on August 3, 2020:
        if (res && nFeeRet > 0 /* 0 means non-functional fee rate estimation */ && m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) {
    
  65. in src/wallet/wallet.cpp:3034 in dd634f2470 outdated
    3029 | +        int nChangePosInOut2 = nChangePosIn;
    3030 | +        bilingual_str error2; // fired and forgotten; if an error occurs, we discard the results
    3031 | +        if (CreateTransactionInternal(vecSend, tx2, nFeeRet2, nChangePosInOut2, error2, tmp_cc, sign)) {
    3032 | +            // if fee of this alternative one is within the range of the max fee, we use this one
    3033 | +            WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n", nFeeRet, nFeeRet2, nFeeRet2 <= nFeeRet ? "grouped" : "non-grouped");
    3034 | +            if (nFeeRet2 <= nFeeRet + maxapsfee) {
    


    fjahr commented at 8:22 PM on August 3, 2020:

    The ternary in the logging does not match the if below. I think it should work as it does in the if.

                const bool use_aps = nFeeRet2 <= nFeeRet + m_max_aps_fee;
                WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n", nFeeRet, nFeeRet2, use_aps ? "grouped" : "non-grouped");
                if (use_aps) {
    

    kallewoof commented at 1:05 AM on August 6, 2020:

    Nice bug.. thanks!

  66. fjahr commented at 8:41 PM on August 3, 2020: contributor

    Concept ACK

    Some suggestions, feel free to ignore the renamings. And I have expanded the test here: https://github.com/fjahr/bitcoin/commit/aafa6c38a94ae4df3b7af5f3c8c7255024498b0b#diff-c2a150736b92621b8db2d91b546fbf85. Please take a look and feel free use.

  67. wallet: try -avoidpartialspends mode and use its result if fees are below threshold
    The threshold is defined by a new max avoid partial spends fee flag, which defaults to 0 (i.e. if fees are unchanged, use the grouped option).
    b82067bf69
  68. kallewoof force-pushed on Aug 6, 2020
  69. test: test the implicit avoid partial spends functionality
    Co-authored-by: Fabian Jahr <fjahr@protonmail.com>
    7f13dfb587
  70. kallewoof force-pushed on Aug 6, 2020
  71. kallewoof commented at 1:26 AM on August 6, 2020: member

    Thanks for review, @fjahr! I believe I've addressed all your points & added your test code.

  72. DrahtBot removed the label Needs rebase on Aug 6, 2020
  73. DrahtBot cross-referenced this on Aug 6, 2020 from issue Refactor: Improve setup_clean_chain semantics by fjahr
  74. DrahtBot cross-referenced this on Aug 7, 2020 from issue Run clang-tidy -*,performance-* by Warchant
  75. fjahr approved
  76. fjahr commented at 3:44 PM on August 8, 2020: contributor

    tested ACK 7f13dfb587dd6a7a5b7dfbfe689ae0ce818fe5c9

  77. in test/functional/wallet_groups.py:67 in 7f13dfb587
      63 | @@ -64,6 +64,52 @@ def run_test(self):
      64 |          assert_approx(v[0], 0.2)
      65 |          assert_approx(v[1], 1.3, 0.0001)
      66 |  
      67 | +        # Test 'avoid partial if warranted, even if disabled'
    


    achow101 commented at 7:30 PM on August 14, 2020:

    nit: use self.log.info instead of a comment.


    kallewoof commented at 4:42 AM on August 15, 2020:

    Sure, will tweak if I end up needing to rebase.

  78. achow101 commented at 7:33 PM on August 14, 2020: member

    ACK 7f13dfb587dd6a7a5b7dfbfe689ae0ce818fe5c9

  79. fanquake requested review from meshcollider on Aug 15, 2020
  80. fanquake commented at 12:32 AM on August 15, 2020: member

    @Xekyo you might also be interested in this?

  81. jonatack commented at 4:55 AM on August 15, 2020: contributor

    Concept ACK

  82. in src/wallet/wallet.cpp:3926 in 7f13dfb587
    3917 | @@ -3878,6 +3918,21 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
    3918 |          walletInstance->m_min_fee = CFeeRate(n);
    3919 |      }
    3920 |  
    3921 | +    if (gArgs.IsArgSet("-maxapsfee")) {
    3922 | +        CAmount n = 0;
    3923 | +        if (gArgs.GetArg("-maxapsfee", "") == "-1") {
    3924 | +            n = -1;
    3925 | +        } else if (!ParseMoney(gArgs.GetArg("-maxapsfee", ""), n)) {
    3926 | +            error = AmountErrMsg("maxapsfee", gArgs.GetArg("-maxapsfee", ""));
    


    jonatack commented at 12:42 PM on August 16, 2020:

    nit if you retouch, could memoize for readability and clarity

         if (gArgs.IsArgSet("-maxapsfee")) {
    +        const std::string max_aps_fee{gArgs.GetArg("-maxapsfee", "")};
             CAmount n = 0;
    -        if (gArgs.GetArg("-maxapsfee", "") == "-1") {
    +        if (max_aps_fee == "-1") {
                 n = -1;
    -        } else if (!ParseMoney(gArgs.GetArg("-maxapsfee", ""), n)) {
    -            error = AmountErrMsg("maxapsfee", gArgs.GetArg("-maxapsfee", ""));
    +        } else if (!ParseMoney(max_aps_fee, n)) {
    +            error = AmountErrMsg("maxapsfee", max_aps_fee);
                 return nullptr;
             }
    
  83. in test/functional/wallet_groups.py:106 in 7f13dfb587
     101 | +        # Test wallet option maxapsfee with Node 3
     102 | +        addr_aps = self.nodes[3].getnewaddress()
     103 | +        self.nodes[0].sendtoaddress(addr_aps, 1.0)
     104 | +        self.nodes[0].sendtoaddress(addr_aps, 1.0)
     105 | +        self.nodes[0].generate(1)
     106 | +        txid4 = self.nodes[3].sendtoaddress(self.nodes[0].getnewaddress(), 0.1)
    


    jonatack commented at 1:17 PM on August 16, 2020:

    suggestion: test the new logging

    -        txid4 = self.nodes[3].sendtoaddress(self.nodes[0].getnewaddress(), 0.1)
    +        with self.nodes[3].assert_debug_log(['Fee non-grouped = 2820, grouped = 4160, using grouped']):
    +            txid4 = self.nodes[3].sendtoaddress(self.nodes[0].getnewaddress(), 0.1)
    
  84. in test/functional/wallet_groups.py:94 in 7f13dfb587
      89 | +        # the accumulated value should be 1.5, so the outputs should be
      90 | +        # ~0.1 and 1.4 and should come from the same destination
      91 | +        values = [vout["value"] for vout in tx3["vout"]]
      92 | +        values.sort()
      93 | +        assert_approx(values[0], 0.1, 0.0001)
      94 | +        assert_approx(values[1], 1.4)
    


    jonatack commented at 1:21 PM on August 16, 2020:

    nit: missing vspan arg here (iiuc it's the same value as the others, e.g. the default, but since it looks like the intention here is to make the vspans explicit, the missing one may have been an oversight)

    can optionally use named args for the assert_approx calls when more than 1-2 args are passed:

    -        assert_approx(values[0], 0.1, 0.0001)
    -        assert_approx(values[1], 1.4)
    +        assert_approx(values[0], vexp=0.1, vspan=0.0001)
    +        assert_approx(values[1], vexp=1.4, vspan=0.0001)
    
  85. jonatack commented at 1:27 PM on August 16, 2020: contributor

    ACK 7f13dfb58, code review, debug build, verified the test fails with AssertionError: not(2 == 1) for the number of vouts when -maxapsfee=0.0001 is changed to 0, and verified the new logging with an added assertion.

    Some feedback below that can be ignored unless need to retouch, or for a follow-up. The added tests are good. I have some ideas on increasing the coverage a bit further.

  86. jonatack commented at 3:48 PM on August 16, 2020: contributor

    As a follow-up, will need a release note.

  87. meshcollider added the label Needs release note on Aug 17, 2020
  88. in src/wallet/wallet.cpp:3931 in 7f13dfb587
    3926 | +            error = AmountErrMsg("maxapsfee", gArgs.GetArg("-maxapsfee", ""));
    3927 | +            return nullptr;
    3928 | +        }
    3929 | +        if (n > HIGH_APS_FEE) {
    3930 | +            warnings.push_back(AmountHighWarn("-maxapsfee") + Untranslated(" ") +
    3931 | +                              _("This is the maximum transaction fee you pay to prioritize partial spend avoidance over regular coin selection."));
    


    meshcollider commented at 3:46 AM on August 17, 2020:

    I think this should emphasize that the maxapsfee is in addition to the normal fee

  89. in test/functional/wallet_groups.py:109 in 7f13dfb587
     104 | +        self.nodes[0].sendtoaddress(addr_aps, 1.0)
     105 | +        self.nodes[0].generate(1)
     106 | +        txid4 = self.nodes[3].sendtoaddress(self.nodes[0].getnewaddress(), 0.1)
     107 | +        tx4 = self.nodes[3].getrawtransaction(txid4, True)
     108 | +        # tx4 should have 2 inputs and 2 outputs although one output would
     109 | +        # have been enough and the transaction caused higher fees
    


    meshcollider commented at 3:54 AM on August 17, 2020:

    It would be good to also test here that the higher fee is still capped by the parameter provided

  90. meshcollider commented at 3:55 AM on August 17, 2020: contributor

    Code review ACK 7f13dfb587dd6a7a5b7dfbfe689ae0ce818fe5c9

    Neither of my comments are critical and could be done in a followup

  91. meshcollider merged this on Aug 17, 2020
  92. meshcollider closed this on Aug 17, 2020

  93. kallewoof deleted the branch on Aug 17, 2020
  94. sidhujag referenced this in commit fb6af86609 on Aug 17, 2020
  95. kallewoof cross-referenced this on Aug 17, 2020 from issue -maxapsfee follow-up by kallewoof
  96. MarcoFalke removed the label Needs release note on Aug 17, 2020
  97. jonatack cross-referenced this on Aug 17, 2020 from issue Intermittent CI failure: wallet_groups.py by practicalswift
  98. meshcollider referenced this in commit a2a250c7d0 on Aug 18, 2020
  99. jasonbcox referenced this in commit 81fe776b23 on Oct 8, 2020
  100. jonatack cross-referenced this on Jan 20, 2021 from issue doc: APS explanation clarified in 0.21.0 release notes by dscotese
  101. jonatack cross-referenced this on Mar 22, 2022 from issue test: coinselection edge cases by S3RK
  102. bitcoin locked this on Oct 31, 2022

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: 2026-05-20 06:54 UTC