wallet: Allow negative effective value inputs when subtracting fee from outputs #23534

pull achow101 wants to merge 3 commits into bitcoin:master from achow101:no-change-fee-w-sffo changing 2 files +55 −2
  1. achow101 commented at 5:12 am on November 17, 2021: member

    Often the goal with subtracting fee from outputs is to sweep an entire wallet’s balance. As such, it is reasonable to allow negative effective value UTXOs as inputs as otherwise the target value (the balance) cannot be reached. However this is not always desirable as there may be solutions with only positive EV UTXOs, so we only try with negative EV UTXOs if we are unable to find any solutions with the normal groupings.

    Fixes #23026

  2. fanquake added the label Wallet on Nov 17, 2021
  3. MarcoFalke added the label Needs backport (22.x) on Nov 17, 2021
  4. MarcoFalke added this to the milestone 22.1 on Nov 17, 2021
  5. in test/functional/rpc_fundrawtransaction.py:1161 in 05ef67ad94 outdated
    1156+        self.generate(self.nodes[0], 1)
    1157+
    1158+        created = w.createrawtransaction([], [{self.nodes[0].getnewaddress(address_type="bech32"): 1.00001000}])
    1159+
    1160+        # With 15 sat/vb, the low value input is negative ev. This should still fund.
    1161+        funded = w.fundrawtransaction(created, {"subtractFeeFromOutputs":[0], "fee_rate": 15})
    


    fanquake commented at 8:22 am on November 17, 2021:
    test/functional/rpc_fundrawtransaction.py:1161:9: F841 local variable 'funded' is assigned to but never used

    achow101 commented at 6:12 pm on November 17, 2021:
    Fixed.
  6. achow101 force-pushed on Nov 17, 2021
  7. DrahtBot commented at 3:01 am on November 18, 2021: member

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

    Conflicts

    No conflicts as of last run.

  8. meshcollider commented at 2:54 am on November 22, 2021: contributor

    Code review ACK c351e53f42c85c7ed95bde842f07554c9798dbcb

    Checked that the new test fails on current master but passes with this PR.

  9. in test/functional/rpc_fundrawtransaction.py:1148 in c351e53f42 outdated
    1143@@ -1143,6 +1144,21 @@ def test_feerate_rounding(self):
    1144         rawtx = w.createrawtransaction(inputs=[], outputs=[{self.nodes[0].getnewaddress(address_type="bech32"): 1 - 0.00000202}])
    1145         assert_raises_rpc_error(-4, "Insufficient funds", w.fundrawtransaction, rawtx, {"fee_rate": 1.85})
    1146 
    1147+    def test_sffo_with_negative_ev(self):
    1148+        self.log.info("Testing that subtract fee from outputs with negative effective value outputs still works")
    


    S3RK commented at 7:08 am on November 22, 2021:
    0    def test_wallet_sweaping(self):
    1        self.log.info("Testing that wallet sweeping works even with negative effective value.")
    

    instagibbs commented at 8:20 am on November 25, 2021:
    the functionality change isn’t for sweeping per-se, maybe just add “often used for sweeping” in the comment

    rajarshimaitra commented at 12:40 pm on November 27, 2021:
    I am finding this line a little a confusing, maybe something like “Testing that sweeping transaction with “subtract fee from outputs” with negative effective value utxos works”.

    achow101 commented at 7:29 pm on December 5, 2021:
    This functionality is for more than just sweeping. I’ve changed the text to Test that enable subtractFeeFromOutputs will include negative effective value UTXOs.
  10. in test/functional/rpc_fundrawtransaction.py:1161 in c351e53f42 outdated
    1156+        self.generate(self.nodes[0], 1)
    1157+
    1158+        created = w.createrawtransaction([], [{self.nodes[0].getnewaddress(address_type="bech32"): 1.00001000}])
    1159+
    1160+        # With 15 sat/vb, the low value input is negative ev. This should still fund.
    1161+        w.fundrawtransaction(created, {"subtractFeeFromOutputs":[0], "fee_rate": 15})
    


    S3RK commented at 7:09 am on November 22, 2021:
    I’d add another assertion that it fails without subtractFeeFromOutputs

    instagibbs commented at 8:25 am on November 25, 2021:
    can we make 15 more programmatic with respect to the 1000 sat output?

    achow101 commented at 7:29 pm on December 5, 2021:

    Added an assert_raises_rpc_error.

    Also made the fee rate calculation programmatic

  11. S3RK commented at 7:19 am on November 22, 2021: member

    Concept ACK.

    Did a light code review and I think it’s better to drop the first commit as it’s not required to fix the linked bug. If you’d like to keep it, I’d suggest to make inclusion/exclusion of the change fee explicit when stetting targets for coinselection algorithms, rather than implicit m_change_fee = 0 (it’s not zero, it’s just payed by recipient!).

  12. S3RK commented at 7:20 am on November 22, 2021: member
    some nits below
  13. fanquake requested review from instagibbs on Nov 25, 2021
  14. fanquake commented at 7:45 am on November 25, 2021: member
  15. in src/wallet/spend.cpp:387 in 851e73e05b outdated
    383@@ -384,7 +384,7 @@ bool AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue, const
    384     std::vector<std::tuple<CAmount, std::set<CInputCoin>, CAmount>> results;
    385 
    386     // Note that unlike KnapsackSolver, we do not include the fee for creating a change output as BnB will not create a change output.
    387-    std::vector<OutputGroup> positive_groups = GroupOutputs(wallet, coins, coin_selection_params, eligibility_filter, true /* positive_only */);
    388+    std::vector<OutputGroup> positive_groups = GroupOutputs(wallet, coins, coin_selection_params, eligibility_filter, !coin_selection_params.m_subtract_fee_outputs/* positive_only */);
    


    instagibbs commented at 8:19 am on November 25, 2021:
    positive_groups is a misnomer, perhaps rename it non_knapsack_groups?

    achow101 commented at 7:30 pm on December 5, 2021:
    Named this to just groups and the groups that KnapsackSolver uses to be knapsack_groups. It’s better for the special case to be named after the special case rather than the normal case to be named as the negation of the special case.
  16. in test/functional/rpc_fundrawtransaction.py:1160 in c351e53f42 outdated
    1143@@ -1143,6 +1144,21 @@ def test_feerate_rounding(self):
    1144         rawtx = w.createrawtransaction(inputs=[], outputs=[{self.nodes[0].getnewaddress(address_type="bech32"): 1 - 0.00000202}])
    1145         assert_raises_rpc_error(-4, "Insufficient funds", w.fundrawtransaction, rawtx, {"fee_rate": 1.85})
    1146 
    1147+    def test_sffo_with_negative_ev(self):
    1148+        self.log.info("Testing that subtract fee from outputs with negative effective value outputs still works")
    1149+
    1150+        self.nodes[1].createwallet("negevsffotest")
    1151+        w = self.nodes[1].get_wallet_rpc("negevsffotest")
    


    instagibbs commented at 8:22 am on November 25, 2021:
    assert w has no balance/utxos, may seem duh but it helps readability of the two wallet dance

    achow101 commented at 7:30 pm on December 5, 2021:
    Done
  17. instagibbs commented at 8:28 am on November 25, 2021: member
    concept ACK, if someone wants to do “send max value possible” we could support this as another option and choose positive only groups again.
  18. rajarshimaitra commented at 12:55 pm on November 27, 2021: contributor

    tACK https://github.com/bitcoin/bitcoin/pull/23534/commits/c351e53f42c85c7ed95bde842f07554c9798dbcb

    Verified that the test fails in master.

    Though I am thinking, the problem of negative ev utxos are only relevant for sweeping transactions with fundrawtransaction rpc (there are other ways to create sweeps). Does this warrant removing negative ev for all cases with subtract_fee_outputs? Maybe we can have a more sweeping focused approach to trigger BnB?

  19. luke-jr changes_requested
  20. luke-jr commented at 8:10 pm on November 30, 2021: member

    But this solution means you will end up with less bitcoins on the receiving end, doesn’t it? Users probably would prefer the higher amount received.

    I suppose the best UX would be to either ignore the net-negative UTXOs and simply send less than the requested amount. But that is apt to unprovably “burn” the dust unless we can somehow publish the dust private keys for others to sweep (but that seems dangerous and potentially ties to some centralised publisher).

    Second-best might be to effectively reduce the fee rate by simply refusing to pay more for these UTXOs than they’re worth. But care is needed to ensure the user is aware of this and transactions don’t get stuck.

    Also, any of this should only occur when trying to send the entire balance I think?

  21. achow101 commented at 6:54 pm on December 5, 2021: member

    But this solution means you will end up with less bitcoins on the receiving end, doesn’t it?

    Yes.

    Users probably would prefer the higher amount received.

    I’m not sure. Subtract fee from outputs has always been “X amount leaves the wallet”, so I think having less than that leave would be surprising?

    I suppose the best UX would be to either ignore the net-negative UTXOs and simply send less than the requested amount. But that is apt to unprovably “burn” the dust unless we can somehow publish the dust private keys for others to sweep (but that seems dangerous and potentially ties to some centralised publisher).

    Negative EV UTXOs can become positive if the feerate were to drop (assuming it was high).

    Second-best might be to effectively reduce the fee rate by simply refusing to pay more for these UTXOs than they’re worth. But care is needed to ensure the user is aware of this and transactions don’t get stuck.

    I think that might be dangerous, and surprising to users.

    Also, any of this should only occur when trying to send the entire balance I think?

    I think it is reasonable for this to occur for all subtract fee from outputs sends. Maybe it should try positive only first, and if that fails, allow negative?

  22. achow101 force-pushed on Dec 5, 2021
  23. DrahtBot added the label Needs rebase on Dec 9, 2021
  24. wallet: Set change fee only if not subtracting fee from outputs 227803cb95
  25. achow101 force-pushed on Dec 9, 2021
  26. DrahtBot removed the label Needs rebase on Dec 9, 2021
  27. fanquake requested review from meshcollider on Dec 10, 2021
  28. fanquake requested review from Sjors on Dec 10, 2021
  29. alirezaayande referenced this in commit fad2e0a36a on Dec 10, 2021
  30. Sjors commented at 5:59 am on December 10, 2021: member

    Nit: typo in the PR title “negtive” (may make it harder to find later)

    I typically use the “subtract fee” feature in combination with manual coin selection (for which this PR has no effect). That’s different from the use case of emptying out a wallet. At minimum the RPC docs should warn that this will spend negative net value coins. The GUI should also warn, preferably only when this is actually the case. It can get quite expensive if a wallet has lots of dust.

    The first commit is worth its own PR if this doesn’t make it.

  31. fanquake renamed this:
    wallet: Allow negtive effective value inputs when subtracting fee from outputs
    wallet: Allow negative effective value inputs when subtracting fee from outputs
    on Dec 10, 2021
  32. wallet: Try selecting with negative ev coins when subtractFeeFromOutputs
    Sometimes subtractFeeFromOutputs requires spending negative ev UTXOs
    (e.g. when sweeping an entire wallet). If we are unable to find any
    solutions normally, then try again but this time allowing negative ev
    UTXOs.
    89d1d6ff4c
  33. tests: Test for spending neg ev UTXOs when subtracting fee from outputs
    When subtracting fee from outputs, it should be okay to spend negative
    ev UTXOs because the recipient is paying for it.
    a26a64c63d
  34. achow101 force-pushed on Dec 10, 2021
  35. achow101 commented at 2:28 pm on December 10, 2021: member
    I’ve changed this to try negative EVs if there are no solutions with the normal groupings.
  36. meshcollider commented at 9:48 am on December 13, 2021: contributor

    Code review ACK a26a64c63dd8d55d0ac972f5420f5dd27e7f53d8

    Haven’t re-verified test

  37. in src/wallet/spend.cpp:408 in a26a64c63d
    402@@ -403,6 +403,20 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm
    403         results.push_back(*srd_result);
    404     }
    405 
    406+    // If we didn't find any solutions, and are subtracting the fee from the outputs, we may need to include negative effective value coins.
    407+    // So try BnB and SRD again with all_groups.
    408+    // It is safe to do this because a solution including negative EVs will have a greater waste.
    


    instagibbs commented at 4:03 am on December 14, 2021:
    What about just throwing these two selections into the mix unconditionally? Would that be correct? Efficiency aside.
  38. instagibbs approved
  39. instagibbs commented at 4:04 am on December 14, 2021: member
  40. in src/wallet/spend.cpp:414 in 89d1d6ff4c outdated
    409+    if (results.size() == 0 && coin_selection_params.m_subtract_fee_outputs) {
    410+        if (auto bnb_result{SelectCoinsBnB(all_groups, nTargetValue, coin_selection_params.m_cost_of_change)}) {
    411+            bnb_result->ComputeAndSetWaste(CAmount(0));
    412+            results.push_back(*bnb_result);
    413+        }
    414+        if (auto srd_result{SelectCoinsSRD(all_groups, srd_target)}) {
    


    glozow commented at 1:44 pm on January 3, 2022:
    Might this trigger the Assume(group.GetSelectionAmount() > 0) statement in SelectCoinsSRD()?

    glozow commented at 1:45 pm on January 3, 2022:
    (also note the comment on the declaration for SelectCoinsSRD() in coinselection.h states that it expects positive ev groups, might need to change that)
  41. Xekyo commented at 4:45 pm on January 5, 2022: member

    I already mentioned this in person, but I find that “subtract fee” leads to unintuitive behavior, especially in the context of sweeping (and makes all tests a lot more complicated).

    It would make more sense to me that the wallet never spends UTXOs with negative effective value unless explicitly tasked to do so, since it just costs users (either sender or receiver) more fees and more blockspace for the debatable reason of not leaving any UTXOs behind. The main use-case where I see “subtract fee from recipient” making sense, is in the context of Coin Control, when the sender picks the inputs explicitly and doesn’t want to calculate the appropriate fee manually.

    Especially in the case of sweeping, I agree with Luke, that the user should prefer getting more money over using all UTXOs. If they sweep at minFeeRate, they’re getting the optimal outcome anyway. We might want to introduce a dedicated sweep function that sends the maximum by default, and optionally can be toggled to spend all UTXOs.

  42. ryanofsky commented at 4:56 pm on January 5, 2022: member

    I already mentioned this in person, but I find that “subtract fee” leads to unintuitive behavior, especially in the context of sweeping (and makes all tests a lot more complicated).

    Could you maybe illustrate what the unintuitive behavior is with an example? The rest of your comment about “user should get more money by default” obviously sounds good if it doesn’t have other tradeoffs.

  43. achow101 commented at 5:52 pm on January 5, 2022: member

    The main use-case where I see “subtract fee from recipient” making sense, is in the context of Coin Control, when the sender picks the inputs explicitly and doesn’t want to calculate the appropriate fee manually.

    And that scenario could be considered sweeping as well. Sweeping can be generalized to “spend UTXOs XYZ fully with all value going to one output” where XYZ could be all UTXOs in the wallet, or a preset input list.

    Especially in the case of sweeping, I agree with Luke, that the user should prefer getting more money over using all UTXOs. If they sweep at minFeeRate, they’re getting the optimal outcome anyway. We might want to introduce a dedicated sweep function that sends the maximum by default, and optionally can be toggled to spend all UTXOs.

    I think it is unintuitive and not very good UX to not spend uneconomical UTXOs in a sweep. I think that users would expect a sweep function to spend all UTXOs regardless, and it would be surprising if sweeping a wallet left behind any balance, even uneconomical.

    Could you maybe illustrate what the unintuitive behavior is with an example?

    I’m not sure about unintuitive, but subtractFeeFromOutputs definitely causes a lot of headaches in coin selection code, so getting rid of it would greatly simplify a lot of it. It would certainly make the logic much easier to follow.


    For some historical context, #5831 added subtractFeeFromAmount (which was a rebase of #4331) and that points to 2 issues where the proposed use case is sweeping.

  44. fanquake removed the label Needs backport (22.x) on Mar 5, 2022
  45. fanquake added the label Needs backport (22.x) on Mar 5, 2022
  46. achow101 commented at 2:21 pm on March 10, 2022: member
    Closing this in favor of introducing sendall to solve the problem.
  47. achow101 closed this on Mar 10, 2022

  48. MarcoFalke removed the label Needs backport (22.x) on Mar 31, 2022
  49. DrahtBot locked this on Mar 31, 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-07-08 19:13 UTC

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