wallet: Use GetSelectionAmount in ApproximateBestSubset #22686

pull achow101 wants to merge 3 commits into bitcoin:master from achow101:use-getselectionvalue changing 3 files +63 −2
  1. achow101 commented at 2:16 am on August 12, 2021: member

    The m_value used for the target calculation in ApproximateBestSubset is incorrect, it should be GetSelectionAmount. This causes a bug that is only apparent when the minimum relay fee is set to be very high.

    A test case is added for this, in addition to an assert in CreateTransactionInternal that would have also caught this issue if someone were able to hit the edge case.

    Fixes #22670

  2. achow101 commented at 2:16 am on August 12, 2021: member
    For 22.0 backport (this bug may cause other issues that are not as immediately obvious).
  3. fanquake added the label Wallet on Aug 12, 2021
  4. fanquake added the label Needs backport (22.x) on Aug 12, 2021
  5. fanquake added this to the milestone 22.0 on Aug 12, 2021
  6. unknown approved
  7. fanquake commented at 3:11 am on August 12, 2021: member

    Failure in the previous releases CI. Similar to #18737 / #18737:

     0 test  2021-08-12T02:39:40.038000Z TestFramework (ERROR): Assertion failed 
     1                                   Traceback (most recent call last):
     2                                     File "/tmp/cirrus-build/ci/scratch/build/bitcoin-x86_64-pc-linux-gnu/test/functional/test_framework/test_framework.py", line 131, in main
     3                                       self.run_test()
     4                                     File "/tmp/cirrus-build/ci/scratch/build/bitcoin-x86_64-pc-linux-gnu/test/functional/feature_fee_estimation.py", line 271, in run_test
     5                                       check_estimates(self.nodes[1], self.fees_per_kb)
     6                                     File "/tmp/cirrus-build/ci/scratch/build/bitcoin-x86_64-pc-linux-gnu/test/functional/feature_fee_estimation.py", line 155, in check_estimates
     7                                       check_smart_estimates(node, fees_seen)
     8                                     File "/tmp/cirrus-build/ci/scratch/build/bitcoin-x86_64-pc-linux-gnu/test/functional/feature_fee_estimation.py", line 145, in check_smart_estimates
     9                                       % (feerate, last_feerate))
    10                                   AssertionError: Estimated fee (0.000238) larger than last fee (0.000201) for lower number of confirms
    11 test  2021-08-12T02:39:40.040000Z TestFramework (DEBUG): Closing down network thread 
    12 test  2021-08-12T02:39:40.041000Z TestFramework (INFO): Stopping nodes 
    13 test  2021-08-12T02:39:40.041000Z TestFramework.node0 (DEBUG): Stopping node 
    
  8. DrahtBot commented at 7:18 pm on August 12, 2021: contributor

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

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #22049 (rpc: allow specifying min chain depth for inputs in fund calls by champo)
    • #17211 (Allow fundrawtransaction and walletcreatefundedpsbt to take external inputs by achow101)

    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.

  9. fanquake requested review from instagibbs on Aug 13, 2021
  10. fanquake commented at 0:30 am on August 13, 2021: member
    cc @Xekyo
  11. in test/functional/rpc_fundrawtransaction.py:996 in 5a89197a22 outdated
    991+        # than any single input available, and require more than 1 input. So we make 3 outputs
    992+        for i in range(0, 3):
    993+            funds.sendtoaddress(tester.getnewaddress(address_type="bech32"), 1)
    994+        self.nodes[0].generate(1)
    995+
    996+        # With 3 outputs of 1 BTC, the target value must between 1 and 2 BTC. We choose a value where with 2 inputs, the
    


    instagibbs commented at 2:09 am on August 13, 2021:

    can this be done programmatically?

    blocks of comments means the chances of the description and/or the test going stale quite high

    in other words, can we get size estimations, calculate the final total, round properly, using python rather than english?


    achow101 commented at 4:35 am on August 13, 2021:
    Done
  12. in test/functional/rpc_fundrawtransaction.py:983 in 5a89197a22 outdated
    978+        # that this bug is not detected. It is most easily observed when the minimum relay fee is set to
    979+        # an extremely high value (0.1 BTC/kvB in this test).
    980+        self.log.info("Test issue 22670 ApproximateBestSubset bug")
    981+        # Make sure the default wallet will not be loaded when restarted with a high minrelaytxfee
    982+        self.nodes[0].unloadwallet(self.default_wallet_name, False)
    983+        self.restart_node(0, ["-minrelaytxfee=0.1", "-changetype=bech32"])
    


    instagibbs commented at 2:12 am on August 13, 2021:
    Maybe create DEFAULT_MAX_RAW_TX_FEE_RATE constant and use that so you can refer to it later as well?

    achow101 commented at 4:35 am on August 13, 2021:
    Done.. ish
  13. in src/wallet/spend.cpp:783 in fdf2111ab5 outdated
    777@@ -778,6 +778,10 @@ bool CWallet::CreateTransactionInternal(
    778         fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes);
    779     }
    780 
    781+    // The only time that fee_needed should be less than the amount available for fees (in change_and_fee - change_amount) is when
    782+    // we are subtracting the fee from the outputs. If this occurs at any other time, it is a bug.
    783+    assert(coin_selection_params.m_subtract_fee_outputs || fee_needed <= change_and_fee - change_amount);
    


    instagibbs commented at 2:21 am on August 13, 2021:

    am I correct in noting that change_and_fee - change_amount may be non-zero in coin_selection_params.m_subtract_fee_outputs instance in the case where change is dropped to fee?

    just thinking if any tighter assertion can be placed.


    achow101 commented at 2:45 am on August 13, 2021:
    Yes. If m_subtract_fee_outputs, change_and_fee - change_amount can be positive, negative, or 0. It’s pretty much a useless value in that case.
  14. instagibbs commented at 2:21 am on August 13, 2021: member

    To be clear, the bug is that it is using the output value, even if we’re not subtracting fee from outputs. We use GetSelectionAmount to switch between effective and output value as our target based on subtracting fee from outputs or not.

    This explanation should go in the commit message, or at least the OP to get pulled in on merge.

  15. wallet: Use GetSelectionAmount for target value calculations
    For target value calculations, GetSelectionAmount should be used, not
    m_effective_value or m_value.
    
    Specifically, ApproximateBestSubset mistakenly uses m_value when
    calculating whether the target value has been met. This has been changed
    to use GetSelectionAmount.
    2de222c401
  16. wallet: Assert that enough was selected to cover the fees
    When the fee is not subtracted from the outputs, the amount that has
    been reserved for the fee (change_and_fee - change_amount) must be
    enough to cover the fee that is needed. It would be a bug to not do so,
    so use an assert to make this obvious if such a situation were to occur.
    d9262324e8
  17. test: Test for ApproximateBestSubset edge case with too little fees
    ApproximateBestSubset had an edge case (due to not using
    GetSelectionAmount) where it was possible for it to return success but
    fail to select enough to cover transaction fees. A test is added that
    could trigger this failure prior to the fix being implemented.
    92885c4f69
  18. achow101 commented at 4:35 am on August 13, 2021: member

    To be clear, the bug is that it is using the output value, even if we’re not subtracting fee from outputs. We use GetSelectionAmount to switch between effective and output value as our target based on subtracting fee from outputs or not.

    This explanation should go in the commit message, or at least the OP to get pulled in on merge.

    I’ve added a specific explanation in the first commit

  19. achow101 force-pushed on Aug 13, 2021
  20. fanquake requested review from meshcollider on Aug 13, 2021
  21. meshcollider commented at 4:46 am on August 15, 2021: contributor

    utACK 6e6a7e1fe874f0d93de7254d0884a1b9a95f6b19

    Verified the test fails on master but passes with this PR

  22. fanquake merged this on Aug 16, 2021
  23. fanquake closed this on Aug 16, 2021

  24. murchandamus commented at 7:18 pm on August 18, 2021: contributor
    Post-merge utACK 92885c4f69a5e6fc4989677d6e5be8a666fbff0d
  25. fanquake commented at 0:00 am on August 19, 2021: member
    @Xekyo thanks for following up.
  26. hebasto referenced this in commit d45b9b7cc9 on Aug 19, 2021
  27. hebasto referenced this in commit 3f03d7678c on Aug 19, 2021
  28. hebasto referenced this in commit c5ae6304f3 on Aug 19, 2021
  29. hebasto commented at 7:51 am on August 19, 2021: member
    Backported in #22629.
  30. hebasto removed the label Needs backport (22.x) on Aug 19, 2021
  31. in test/functional/rpc_fundrawtransaction.py:1003 in 92885c4f69
     998+        # Create transactions in order to calculate fees for the target bounds that can trigger this bug
     999+        change_tx = tester.fundrawtransaction(tester.createrawtransaction([], [{funds.getnewaddress(): 1.5}]))
    1000+        tx = tester.createrawtransaction([], [{funds.getnewaddress(): 2}])
    1001+        no_change_tx = tester.fundrawtransaction(tx, {"subtractFeeFromOutputs": [0]})
    1002+
    1003+        overhead_fees = feerate * len(tx) / 2 / 1000
    


    S3RK commented at 8:14 am on August 19, 2021:
    Why do we divide by 2 here?

    achow101 commented at 4:02 pm on August 19, 2021:
    To get the byte length of the hex string.
  32. in test/functional/rpc_fundrawtransaction.py:1023 in 92885c4f69
    1018+        # but not enough to need 3 inputs when not considering the fee.
    1019+        # So the target value must be at least 2.00000001 - fee.
    1020+        lower_bound = Decimal("2.00000001") - fees
    1021+        # The target value must be at most 2 - cost_of_change - not_input_fees - min_change (these are all
    1022+        # included in the target before ApproximateBestSubset).
    1023+        upper_bound = Decimal("2.0") - cost_of_change - overhead_fees - Decimal("0.01")
    


    S3RK commented at 8:14 am on August 19, 2021:
    The test also passes with much higher upper_bound.

    achow101 commented at 4:04 pm on August 19, 2021:
    The lower and upper bounds are the minimum and maximum values that cause this test to fail prior to this PR. So a higher upper bound should still pass after this PR, and all values in between should pass after this PR.
  33. S3RK commented at 8:14 am on August 19, 2021: contributor
    See #22742
  34. MarcoFalke referenced this in commit 92f3a4b4d0 on Aug 19, 2021
  35. hebasto referenced this in commit 7d5247a5b6 on Aug 20, 2021
  36. hebasto referenced this in commit 4b121f75bc on Aug 20, 2021
  37. hebasto referenced this in commit eeb46425de on Aug 20, 2021
  38. hebasto referenced this in commit ba74c43af1 on Aug 20, 2021
  39. hebasto referenced this in commit 4a0d6c23a4 on Aug 20, 2021
  40. hebasto referenced this in commit 24a5d2e870 on Aug 20, 2021
  41. hebasto referenced this in commit ffc81e2048 on Aug 20, 2021
  42. hebasto referenced this in commit e86b023606 on Aug 20, 2021
  43. hebasto referenced this in commit dfaffbeb63 on Aug 20, 2021
  44. laanwj referenced this in commit 4a25e39624 on Aug 26, 2021
  45. fujicoin referenced this in commit 3510129ea2 on Aug 27, 2021
  46. fujicoin referenced this in commit 4a93800a74 on Aug 27, 2021
  47. fujicoin referenced this in commit 2d4e6d1b3a on Aug 27, 2021
  48. gwillen referenced this in commit 17d3c26447 on Jul 27, 2022
  49. gwillen referenced this in commit 38445bbe68 on Jul 27, 2022
  50. gwillen referenced this in commit e1a8e2f976 on Jul 27, 2022
  51. gwillen referenced this in commit efac8a33fa on Aug 1, 2022
  52. gwillen referenced this in commit a513722516 on Aug 1, 2022
  53. gwillen referenced this in commit 58deead9b9 on Aug 1, 2022
  54. in src/wallet/spend.cpp:787 in d9262324e8 outdated
    782+    // we are subtracting the fee from the outputs. If this occurs at any other time, it is a bug.
    783+    assert(coin_selection_params.m_subtract_fee_outputs || fee_needed <= change_and_fee - change_amount);
    784+
    785     // Update nFeeRet in case fee_needed changed due to dropping the change output
    786     if (fee_needed <= change_and_fee - change_amount) {
    787         nFeeRet = change_and_fee - change_amount;
    


    apoelstra commented at 9:31 pm on August 4, 2022:

    nit: this logic looks a little bit funny now. I think it might make more sense to have modified this if block as

    0    if (fee_needed <= change_and_fee - change_amount) {
    1        nFeeRet = change_and_fee - change_amount;
    2    } else {
    3        // The only time that fee_needed should be less than the amount available for fees (in change_and_fee - change_amount) is when
    4        // we are subtracting the fee from the outputs. If this occurs at any other time, it is a bug.
    5        assert(coin_selection_params.m_subtract_fee_outputs);
    6    }
    

    which is logically equivalent but doesn’t do the same check twice


    S3RK commented at 6:29 am on August 8, 2022:
    JFYI #25647 changes these lines
  55. apoelstra referenced this in commit dfc3e891d7 on Sep 20, 2022
  56. delta1 referenced this in commit 0b24fc8b89 on Apr 9, 2023
  57. bitcoin locked this on Aug 8, 2023

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: 2024-12-18 15:12 UTC

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