wallet: allow transaction without change if keypool is empty #17219

pull Sjors wants to merge 3 commits into bitcoin:master from Sjors:2019/10/change-without-keypool changing 4 files +73 −21
  1. Sjors commented at 7:12 pm on October 22, 2019: member

    Extracted from #16944

    First this PR simplifies the check when generating a change address, by dropping CanGetAddresses and just letting reservedest.GetReservedDestination do this check.

    Second, when the keypool is empty, instead of immediately giving up, we create a dummy change address and pass that to coin selection. If we didn’t need the change address (e.g. when spending the entire balance), then it’s all good. If we did need a change address, we throw the original error.

  2. fanquake added the label Wallet on Oct 22, 2019
  3. in src/wallet/wallet.cpp:2632 in b2da2d9e56 outdated
    2980@@ -2980,21 +2981,23 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
    2981                 //  rediscover unknown transactions that were written with keys of ours to recover
    2982                 //  post-backup change.
    2983 
    2984-                // Reserve a new key pair from key pool
    2985+                // Reserve a new key pair from key pool. If it fails, provide a dummy
    


    Sjors commented at 7:14 pm on October 22, 2019:

    @instagibbs wrote:

    At the risk of making the error message less precise, I think this whole block is easier to read if it just relies on GetReservedDestination to check if CanGetAddress is true or not. You also can just not store ret and use it directly in the conditional.

  4. instagibbs commented at 7:16 pm on October 22, 2019: member
    appreciated, will review
  5. DrahtBot commented at 10:14 pm on October 22, 2019: member

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

    Conflicts

    No conflicts as of last run.

  6. promag commented at 10:18 am on October 23, 2019: member

    Looks like you could avoid failed_to_get_change_address by applying

     0--- a/src/wallet/wallet.cpp
     1+++ b/src/wallet/wallet.cpp
     2@@ -2968,7 +2968,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
     3             // TODO: pass in scriptChange instead of reservedest so
     4             // change transaction isn't always pay-to-bitcoin-address
     5             CScript scriptChange;
     6-            bool failed_to_get_change_address{false};
     7+            assert(scriptChange.empty());
     8
     9             // coin control: send change to custom address
    10             if (!boost::get<CNoDestination>(&coin_control.destChange)) {
    11@@ -2984,7 +2984,6 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
    12                 // Reserve a new key pair from key pool. If it fails, provide a dummy
    13                 // destination in case we don't need change.
    14                 if (!CanGetAddresses(true)) {
    15-                    failed_to_get_change_address = true;
    16                     strFailReason = _("Can't generate a change-address key. No keys in the internal keypool and can't generate any keys.").translated;
    17                 } else {
    18                     CTxDestination dest;
    19@@ -2993,7 +2992,6 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
    20                     if (ret) {
    21                         scriptChange = GetScriptForDestination(dest);
    22                     } else {
    23-                        failed_to_get_change_address = true;
    24                         strFailReason = _("Keypool ran out, please call keypoolrefill first").translated;
    25                     }
    26
    27@@ -3210,10 +3208,9 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
    28             }
    29
    30             // Give up if change keypool ran out and we failed to find a solution without change:
    31-            if (failed_to_get_change_address && nChangePosInOut != -1) {
    32+            if (scriptChange.empty() && nChangePosInOut != -1) {
    33                 return false;
    34             }
    35-
    36         }
    37
    38         // Shuffle selected coins and fill in final vin
    
  7. laanwj commented at 10:22 am on October 23, 2019: member
    Concept ACK
  8. Sjors force-pushed on Oct 23, 2019
  9. Sjors commented at 1:21 pm on October 23, 2019: member
    Updated with @promag’s suggestion to avoid failed_to_get_change_address and @instagibbs suggestion to simplify the change address check.
  10. Sjors force-pushed on Oct 23, 2019
  11. Sjors force-pushed on Oct 23, 2019
  12. in src/wallet/wallet.cpp:3209 in 3deb73c853 outdated
    3200@@ -3200,6 +3201,12 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
    3201                 coin_selection_params.use_bnb = false;
    3202                 continue;
    3203             }
    3204+
    3205+            // Give up if change keypool ran out and we failed to find a solution without change:
    3206+            if (scriptChange.empty() && nChangePosInOut != -1) {
    3207+                return false;
    3208+            }
    3209+
    


    promag commented at 2:11 pm on October 23, 2019:
    nit, remove 🏃.
  13. Sjors force-pushed on Oct 23, 2019
  14. Sjors force-pushed on Oct 23, 2019
  15. in src/wallet/wallet.cpp:2538 in db2e40036f outdated
    2985+                // Reserve a new key pair from key pool. If it fails, provide a dummy
    2986+                // destination in case we don't need change.
    2987                 CTxDestination dest;
    2988                 const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend);
    2989                 if (reservedest.GetReservedDestination(change_type, dest, true)) {
    2990                     scriptChange = GetScriptForDestination(dest);
    


    achow101 commented at 10:48 pm on October 23, 2019:
    To avoid overestimating on fees, maybe move change_prototype_txout and change_output_size setting into here?. Then set change_output_size to 0 below in the else block.

    Sjors commented at 2:56 pm on October 24, 2019:
    Done, but I wasn’t able to write a test that shows a difference.
  16. Sjors force-pushed on Oct 24, 2019
  17. Sjors force-pushed on Oct 24, 2019
  18. Sjors force-pushed on Oct 24, 2019
  19. Sjors commented at 3:04 pm on October 24, 2019: member

    @achow101 suggested in #16944 (review) to avoid knapsack when there’s no change. I added a commit that achieves that, but it’s non-trivial.

    It does this by enabling BnB when subtracting fees from outputs. Gotchas:

    1. when coins are pre-selected, it still uses knapsack
    2. it still excludes dust inputs based on their value minus the fees needed to spend them, but it does not use their effective value in BnB. I don’t think this matters in practice, because subtractFeesFromOutputs is generally used to spend either all coins or a selected bunch. But there is an edge case where e.g. it can choose between two 0.01 BTC inputs, one SegWit and one legacy, and it won’t care.

    I tested #16944 on top of this, in particular: GUI coin selection still works with a watch-only wallet without keypool.

  20. Sjors force-pushed on Oct 24, 2019
  21. Sjors force-pushed on Oct 24, 2019
  22. Sjors force-pushed on Oct 24, 2019
  23. achow101 commented at 8:23 pm on October 24, 2019: member

    @achow101 suggested in #16944 (comment) to avoid knapsack when there’s no change. I added a commit that achieves that, but it’s non-trivial.

    In the interest of reducing complexity, I think that change should be done as a separate PR. I do plan on working on the coin selection logic again soon and clean this up as well.

  24. Sjors force-pushed on Oct 25, 2019
  25. Sjors commented at 8:32 am on October 25, 2019: member
    @achow101 done, I moved that commit into #17246
  26. DrahtBot added the label Needs rebase on Oct 29, 2019
  27. Sjors force-pushed on Oct 29, 2019
  28. DrahtBot removed the label Needs rebase on Oct 29, 2019
  29. in src/wallet/wallet.cpp:2638 in 388b153f9e outdated
    2548+                    scriptChange = GetScriptForDestination(dest);
    2549+                } else {
    2550+                    strFailReason = _("Can't generate a transaction without change. Please call keypoolrefill first.").translated;
    2551                 }
    2552-
    2553                 scriptChange = GetScriptForDestination(dest);
    


    fjahr commented at 1:35 pm on October 30, 2019:

    GetScriptForDestination(dest) is called twice here if we can reserve a dest. I would optimize this slightly:

    0                if (!reservedest.GetReservedDestination(change_type, dest, true)) {
    1                    strFailReason = _("Can't generate a transaction without change. Please call keypoolrefill first.").translated;
    2                }
    3                scriptChange = GetScriptForDestination(dest);
    

    The suggestion should replace L2537 - 2542. Seems I can not edit that part of my suggestion.


    Sjors commented at 4:55 pm on November 4, 2019:
    Done
  30. in src/wallet/wallet.cpp:2540 in 388b153f9e outdated
    2545-                    strFailReason = "Keypool ran out, please call keypoolrefill first";
    2546-                    return false;
    2547+                if (reservedest.GetReservedDestination(change_type, dest, true)) {
    2548+                    scriptChange = GetScriptForDestination(dest);
    2549+                } else {
    2550+                    strFailReason = _("Can't generate a transaction without change. Please call keypoolrefill first.").translated;
    


    fjahr commented at 1:40 pm on October 30, 2019:
    I think this error message is ambiguous: It could mean we can’t generate a transaction with change without a change address because we can’t get the dest. But it could also mean we can not generate a transaction that does not need a change at all. I could see my self thinking here: “What do you mean? I have change in this tx!”.

    Sjors commented at 4:55 pm on November 4, 2019:
    Changed to: Transaction needs a change address, but we can't generate it. Please call keypoolrefill first.
  31. fjahr approved
  32. fjahr commented at 1:42 pm on October 30, 2019: member

    Code review ACK 388b153f9ea7ae9cc47e928e08d4f244ee624571

    Two nits that would be worthwhile changing IMO.

  33. in src/wallet/wallet.cpp:2520 in 388b153f9e outdated
    2516@@ -2517,6 +2517,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
    2517             // TODO: pass in scriptChange instead of reservedest so
    2518             // change transaction isn't always pay-to-bitcoin-address
    2519             CScript scriptChange;
    2520+            assert(scriptChange.empty());
    


    luke-jr commented at 4:26 pm on November 4, 2019:
    This seems unnecessary? How could it ever NOT be empty here?

    Sjors commented at 4:55 pm on November 4, 2019:
    Removed. I added a potentially more useful assert below scriptChange = GetScriptForDestination(dest);
  34. Sjors force-pushed on Nov 4, 2019
  35. DrahtBot added the label Needs rebase on Nov 22, 2019
  36. Sjors force-pushed on Nov 23, 2019
  37. Sjors requested review from achow101 on Nov 23, 2019
  38. Sjors requested review from promag on Nov 23, 2019
  39. Sjors requested review from luke-jr on Nov 23, 2019
  40. DrahtBot removed the label Needs rebase on Nov 23, 2019
  41. Sjors force-pushed on Nov 23, 2019
  42. DrahtBot added the label Needs rebase on Dec 6, 2019
  43. Sjors force-pushed on Jan 9, 2020
  44. DrahtBot removed the label Needs rebase on Jan 9, 2020
  45. Sjors force-pushed on Jan 16, 2020
  46. Sjors force-pushed on Jan 30, 2020
  47. Sjors commented at 2:23 pm on January 30, 2020: member
    @instagibbs @achow101 @fjahr @luke-jr this should be ready for another review. @gwillen you may also find it useful.
  48. [wallet] translate "Keypool ran out" message 5efc25f963
  49. [wallet] CreateTransaction: simplify change address check 709f8685ac
  50. [wallet] allow transaction without change if keypool is empty 92bcd70808
  51. in src/wallet/wallet.cpp:2635 in c7d433f572 outdated
    2638-                bool ret = reservedest.GetReservedDestination(dest, true);
    2639-                if (!ret)
    2640-                {
    2641-                    strFailReason = _("Keypool ran out, please call keypoolrefill first").translated;
    2642+                const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend);
    2643+                ReserveDestination reservedest(this, change_type);
    


    fjahr commented at 6:15 pm on February 2, 2020:
    I think L2634-2635 can be removed again. They are already declared in L2578-2579.

    Sjors commented at 10:27 am on February 4, 2020:
    Odd, removed again.
  52. Sjors force-pushed on Feb 4, 2020
  53. fjahr commented at 10:49 am on February 5, 2020: member
    Code review ACK 92bcd70808b9cac56b184903aa6d37baf9641b37
  54. meshcollider commented at 2:34 am on April 16, 2020: contributor
    Code review ACK 92bcd70808b9cac56b184903aa6d37baf9641b37
  55. jonasschnelli commented at 6:19 am on April 16, 2020: contributor
    Thanks @Sjors. Clever way. utACK 92bcd70808b9cac56b184903aa6d37baf9641b37
  56. jonasschnelli added the label Needs release note on Apr 16, 2020
  57. jonasschnelli commented at 6:21 am on April 16, 2020: contributor
    This should have its release notes part (added label).
  58. in src/wallet/wallet.cpp:2639 in 92bcd70808
    2637-                    strFailReason = _("Can't generate a change-address key. Please call keypoolrefill first.").translated;
    2638-                    return false;
    2639+                    strFailReason = _("Transaction needs a change address, but we can't generate it. Please call keypoolrefill first.").translated;
    2640                 }
    2641                 scriptChange = GetScriptForDestination(dest);
    2642+                assert(!dest.empty() || scriptChange.empty());
    


    achow101 commented at 4:18 pm on April 16, 2020:

    It’s not clear to me what this assert is checking for.

    If dest is not empty, we got a change destination and so scriptChange is not empty. If dest is empty, then scriptChange will be empty. So this just covers all cases and can’t fail?


    Sjors commented at 8:31 am on April 17, 2020:
    I can’t remember why I wrote this, but I think the idea was to ensure GetScriptForDestination() returns an empty script for an empty destination. Perhaps that’s overkill; I can drop it if there’s any other changes needed in this PR.

    achow101 commented at 4:34 pm on April 17, 2020:
    I guess it’s fine, but it just seems like useless code that might be confusing to future readers. I would suggest that it be removed in a followup that touches this code.
  59. achow101 commented at 4:34 pm on April 17, 2020: member
    ACK 92bcd70808b9cac56b184903aa6d37baf9641b37
  60. meshcollider merged this on Apr 18, 2020
  61. meshcollider closed this on Apr 18, 2020

  62. Sjors deleted the branch on Apr 18, 2020
  63. sidhujag referenced this in commit 7208ebc3e6 on Apr 19, 2020
  64. fanquake commented at 7:30 am on May 23, 2020: member
  65. MarcoFalke removed the label Needs release note on May 26, 2020
  66. Sjors commented at 9:28 am on May 27, 2020: member
    @fanquake note that this isn’t in 0.20
  67. furszy referenced this in commit 0724bbbad2 on Jun 28, 2020
  68. Fabcien referenced this in commit 0fa7d0acde on Dec 13, 2020
  69. DrahtBot locked this on Feb 15, 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: 2024-07-08 22:13 UTC

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