#17331 did some refactors and cleanup of CreateTransactionInternal to make it easier to understand, however it is still a bit convoluted even though it doesn't have to be. This PR does additional cleanup and refactoring to CreateTransactionInternal so that it is easier to understand. Some unnecessary code was removed, some variables moved around to where they matter, and several indents removed.
wallet: Cleanup and refactor CreateTransactionInternal #22008
pull achow101 wants to merge 9 commits into bitcoin:master from achow101:refactor-createtx changing 4 files +282 −291-
achow101 commented at 8:35 PM on May 20, 2021: member
- fanquake added the label Wallet on May 20, 2021
-
DrahtBot commented at 3:55 AM on May 21, 2021: member
<!--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:
- #22155 (wallet test: Add test for subtract fee from recipient behavior by ryanofsky)
- #22100 (refactor: Clean up new wallet spend, receive files added #21207 by ryanofsky)
- #22009 (wallet: Decide which coin selection solution to use based on waste metric by achow101)
- #21206 (refactor: Make CWalletTx sync state type-safe by ryanofsky)
- #20205 (wallet: Properly support a wallet id by achow101)
- #17526 (Use Single Random Draw In addition to knapsack as coin selection fallback by achow101)
- #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.
-
Sjors commented at 2:39 PM on May 21, 2021: member
utACK 6c0cb3c (the 9 commits in this PR)
Linter seems upset (about indentation?) in the scripted diff commit.
Maybe also move
CreateTransactionInternalout of the header?For code archaeologists wondering about 472e42c2974c3ecca6bdb71f082925d86e3def35 why "Dummy fill vin" was added in the first place: it was introduced in #12699, but it's not clear to me why. @instagibbs?
- achow101 force-pushed on May 25, 2021
- achow101 marked this as ready for review on May 25, 2021
- achow101 force-pushed on May 25, 2021
-
Sjors commented at 4:13 PM on May 25, 2021: member
re-utACK a2dd0dbc91da89a5672d2e578998252b1a3ceabc
- DrahtBot added the label Needs rebase on May 26, 2021
-
in src/wallet/wallet.cpp:2804 in d58f1fb0fd outdated
2797 | @@ -2798,6 +2798,12 @@ bool CWallet::CreateTransactionInternal( 2798 | { 2799 | LOCK(cs_wallet); 2800 | 2801 | + CMutableTransaction txNew; // The resulting transaction that we make 2802 | + txNew.nLockTime = GetLocktimeForNewTransaction(chain(), GetLastBlockHash(), GetLastBlockHeight()); 2803 | + 2804 | + CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy 2805 | + coin_selection_params.m_avoid_partial_spends = coin_control.m_avoid_partial_spends;
Xekyo commented at 4:09 PM on May 26, 2021:commit d58f1fb0fdd43ed186073f9db477d9afd760c637 Move variable initializations to where they are used;
Nit: the commit message does not mention
m_avoid_partial_spends, butm_avoid_reuse, was this a mix-up?
achow101 commented at 8:52 PM on May 27, 2021:Oops, yes, that's a typo. Will fix if I have to push this again.
achow101 commented at 5:22 PM on May 28, 2021:Fixed.
achow101 force-pushed on May 26, 2021Xekyo commented at 6:10 PM on May 26, 2021: membertACK c2a5bd68ef60d9c64512b205ff6ea4ce7dca8602
The rebase ate my most pressing comment. Thanks @instagibbs for #22042. :heart:
DrahtBot removed the label Needs rebase on May 26, 2021Sjors commented at 6:26 PM on May 26, 2021: memberThe last rebase broke the fee estimation test on CI, though perhaps a coincidence; I can't reproduce:
self.run_test() File "/tmp/cirrus-build/ci/scratch/build/bitcoin-x86_64-pc-linux-gnu/test/functional/feature_fee_estimation.py", line 256, in run_test check_estimates(self.nodes[1], self.fees_per_kb) File "/tmp/cirrus-build/ci/scratch/build/bitcoin-x86_64-pc-linux-gnu/test/functional/feature_fee_estimation.py", line 140, in check_estimates check_smart_estimates(node, fees_seen) File "/tmp/cirrus-build/ci/scratch/build/bitcoin-x86_64-pc-linux-gnu/test/functional/feature_fee_estimation.py", line 130, in check_smart_estimates % (feerate, last_feerate)) AssertionError: Estimated fee (0.000565) larger than last fee (0.000479) for lower number of confirmsc2a5bd68ef60d9c64512b205ff6ea4ce7dca8602 looks like a correct rebase
ryanofsky commented at 6:59 PM on May 26, 2021: memberCode review ACK c2a5bd68ef60d9c64512b205ff6ea4ce7dca8602. I like
inputs_sumandrecipients_sumlaanwj added this to the "Blockers" column in a project
in src/wallet/wallet.cpp:2798 in c2a5bd68ef outdated
2794 | @@ -2795,287 +2795,270 @@ bool CWallet::CreateTransactionInternal( 2795 | FeeCalculation& fee_calc_out, 2796 | bool sign) 2797 | { 2798 | - CAmount nValue = 0; 2799 | + LOCK(cs_wallet);
glozow commented at 10:51 AM on May 28, 2021:In cb81bede9b Move cs_wallet lock in CreateTransactionInternal to top of function and Remove extraneous scope in CreateTransactionInternal
Seems like curly braces were supposed to limit the scope of the lock (?) but I am unsure why it should be released in the middle of
CreateTransation(). If the intention is to hold this lock for all ofCreateTransaction()(I don't see why we'd want to release it in between calls toCreateTransactionInternal()), why not grab the lock inCreateTransation()and annotate thatCreateTransactionInternal()requirescs_wallet?
achow101 commented at 5:22 PM on May 28, 2021:Done
in src/wallet/wallet.cpp:2814 in c2a5bd68ef outdated
2815 | + unsigned int outputs_to_subtract_fee_from = 0; // The number of outputs which we are subtracting the fee from 2816 | + for (const auto& recipient : vecSend) { 2817 | + if (recipients_sum < 0 || recipient.nAmount < 0) { 2818 | error = _("Transaction amounts must not be negative"); 2819 | return false; 2820 | }
glozow commented at 11:57 AM on May 28, 2021:In 806c32f6ef Move empty recipients vector check to beginning in CreateTransaction
I think this check for non-negative amounts should be moved to the beginning of
CreateTransaction()as well, since it's a sanitization step similar to checking for empty vector. It doesn't need to be checked more than once (and doesn't need the lock).
glozow commented at 12:07 PM on May 28, 2021:It could also be
if (std::any_of(vecSend.cbegin(), vecSend.cend(), [](const auto& recipient){ return recipient.nAmount < 0; }))
achow101 commented at 5:22 PM on May 28, 2021:Done
in src/wallet/wallet.cpp:2979 in c2a5bd68ef outdated
3118 | - // Dummy fill vin for maximum size estimation 3119 | - // 3120 | - for (const auto& coin : setCoins) { 3121 | - txNew.vin.push_back(CTxIn(coin.outpoint,CScript())); 3122 | - } 3123 | + // Subtract fee from the change output if not subtrating it from recipient outputs
glozow commented at 12:00 PM on May 28, 2021:In bc0035a417 Remove extraneous scope in CreateTransactionInternal
// Subtract fee from the change output if not subtracting it from recipient outputs
achow101 commented at 5:22 PM on May 28, 2021:Done
glozow commented at 12:10 PM on May 28, 2021: memberutACK c2a5bd68ef
A few minor suggestions
achow101 force-pushed on May 28, 2021meshcollider commented at 9:40 AM on May 30, 2021: contributorCode review & functional test run ACK ca46cdc2af4919093a8fce8693c49f874f57d492
Unfortunately I am going to merge #21207 first so this will need one more rebase before merge sorry.
DrahtBot added the label Needs rebase on May 30, 2021b2995963b5Move cs_wallet lock in CreateTransactionInternal to top of function
It isn't necessary to not lock parts of this function. Just lock the whole thing and get rid of an indent.
d2aee3bbc7Remove extraneous scope in CreateTransactionInternal
These brackets were restricting a scope for no apparent reason. Remove them and dedent.
dac21c793fRename nValue and nValueToSelect
nValue is the sum of the intended recipient amounts, so name it that for clarity. nValueToSelect is the coin selection target value, so name it selection_target for clarity.
cd1d6d3324Rename nSubtractFeeFromAmount in CreateTransaction
Renamed to outputs_to_subtract_fee_from for clarity.
32ab430651Move recipients vector checks to beginning of CreateTransaction
Ensuring that the recipients vector is not empty and that the amounts are non-negative can be done in CreateTransaction rather than CreateTransactionInternal. Additionally, these checks should happen as soon as possible, so they are done at the beginning of CreateTransaction.
364e0698a5Move variable initializations to where they are used
- txNew nLockTime setting to txNew init - FeeCalc to the fee estimation fetching - setCoins to prior to SelectCoins - nBytes to CalculateMaximumSignedTxSize call - tx_sizes to CalculateMaximumSignedTxSize call - coin_selection_params.m_avoid_partial_spends to params init
d39cac0547Set m_subtract_fee_outputs during recipients vector loop
Instead of setting this afterwards based on the results from the loop, just do it inside of the loop itself. Fixed some styling nearby
b583f73354Move vin filling to before final fee setting
It's unnecessary to fill in the vin with dummy inputs, calculate the fee, then fill in the vin with the actual inputs. Just fill the vin with the actual inputs the first time.
96c2c9520escripted-diff: Rename SelectCoinsMinConf to AttemptSelection
SelectCoinsMinConf is a bit of a misnomer now since it really just does all of the coin selection given some parameters. So rename this to something less annoying to say and makes a bit more sense. -BEGIN VERIFY SCRIPT- sed -i 's/SelectCoinsMinConf/AttemptSelection/g' $(git grep -l SelectCoinsMinConf ./src) -END VERIFY SCRIPT-
achow101 force-pushed on May 30, 2021DrahtBot removed the label Needs rebase on May 30, 2021in src/wallet/spend.cpp:733 in 96c2c9520e
843 | 844 | - // Include the fees for things that aren't inputs, excluding the change output 845 | - const CAmount not_input_fees = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.tx_noinputs_size); 846 | - CAmount nValueToSelect = nValue + not_input_fees; 847 | + // Shuffle selected coins and fill in final vin 848 | + std::vector<CInputCoin> selected_coins(setCoins.begin(), setCoins.end());
glozow commented at 3:48 PM on June 3, 2021:In b583f73354 Move vin filling to before final fee setting (but not super relevant to this PR)
Question: is there a reason
setCoins(andsetCoinsRet, etc. in the coin selection solvers) needs to be astd::setinstead of astd::vector? It doesn't seem like we get much benefit out of using a set, and we convert to vector here anyway?
achow101 commented at 4:42 PM on June 4, 2021:No reason other than it's always been that way.
setCoinsRetcan be traced back to 0.1.0.in src/wallet/spend.cpp:825 in 96c2c9520e
1051 | - txNew.vin.push_back(CTxIn(coin.outpoint, CScript(), nSequence)); 1052 | - } 1053 | + // Give up if change keypool ran out and change is required 1054 | + if (scriptChange.empty() && nChangePosInOut != -1) { 1055 | + return false; 1056 | + }
glozow commented at 10:47 AM on June 4, 2021:Question: just wondering, why doesn't this have an error message?
achow101 commented at 5:08 PM on June 4, 2021:I guess the original author forgot to add one. I've added an error message in a separate commit.
achow101 commented at 5:19 PM on June 4, 2021:Oh, actually it does have an error message. It's set earlier in the function when a change address is fetched. When one is not available, the error message is set. If there is a different failure before we get to this particular condition, then that failure takes precedence and the error message is changed. But if we reach this failure condition, then the error message will have been set previously and does not need to be set here. So I dropped the commit I added.
glozow commented at 11:04 AM on June 4, 2021: memberreACK 96c2c95
ryanofsky approvedryanofsky commented at 12:17 PM on June 4, 2021: memberCode review ACK 96c2c9520e80ee4fed92f0e1ab859d59fcbdb110. No significant changes since last review, just rebase, moving lock and negative amounts check and fixing up some comments
ryanofsky commented at 1:42 PM on June 4, 2021: memberMaybe another cleanup for later, but the separate
fee_neededandnFeeRetvariables haven't been needed since theCreateTransactionInternalwhile loop was removed in 9d3bd74ab4430532d6e53eef8cf77ad999044b14.fee_neededwas only introduced before that commit because if a change output was dropped in an early iteration of the loop,fee_neededwould hold the cost of the transaction without the change output for the current loop iteration, whilenFeeRetwould reflect the cost of the transaction with change for future iterations. Sofee_neededcan be dropped now without changing behavior, which I think would clarify the code:diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index c8ded4c51e2..45a02f59a18 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -756,9 +756,8 @@ bool CWallet::CreateTransactionInternal( nFeeRet = coin_selection_params.m_effective_feerate.GetFee(nBytes); // Subtract fee from the change output if not subtracting it from recipient outputs - CAmount fee_needed = nFeeRet; if (!coin_selection_params.m_subtract_fee_outputs) { - change_position->nValue -= fee_needed; + change_position->nValue -= nFeeRet; } // We want to drop the change to fees if: @@ -774,17 +773,12 @@ bool CWallet::CreateTransactionInternal( // Because we have dropped this change, the tx size and required fee will be different, so let's recalculate those tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly); nBytes = tx_sizes.vsize; - fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes); - } - - // Update nFeeRet in case fee_needed changed due to dropping the change output - if (fee_needed <= change_and_fee - change_amount) { - nFeeRet = change_and_fee - change_amount; + nFeeRet = coin_selection_params.m_effective_feerate.GetFee(nBytes); } // Reduce output values for subtractFeeFromAmount if (coin_selection_params.m_subtract_fee_outputs) { - CAmount to_reduce = fee_needed + change_amount - change_and_fee; + CAmount to_reduce = nFeeRet + change_amount - change_and_fee; int i = 0; bool fFirst = true; for (const auto& recipient : vecSend) @@ -816,7 +810,15 @@ bool CWallet::CreateTransactionInternal( } ++i; } - nFeeRet = fee_needed; + } else if (nFeeRet <= change_and_fee - change_amount) { + // If dropping the change output covered the fee, update the returned + // fee amount. Note that that in subtract-fee-from-recipients case + // above, if the change output is dropped, the change dust value will + // be paid to recipients, rather than to the miner (`to_reduce` above + // will be negative). In that case, the dust amount sent is an + // additional cost of the transaction, but it's not considered part of + // the fee since it isn't paid to the miner, so nFeeRet doesn't change here. + nFeeRet = change_and_fee - change_amount; } // Give up if change keypool ran out and change is requiredachow101 commented at 5:07 PM on June 4, 2021: member@ryanofsky I agree that
fee_neededcan be dropped, but I don't think your diff is necessarily correct. Looking at it now, I don't think the current behavior is correct either (and your diff maintains this behavior). We shouldn't ever be paying more to the recipient than the sender expects. The excess, if it is dust, should be burned as fees. So your diff, and the current behavior, is incorrect in that if the dropped change output is more than fees, then recipients will receive more than requested by the sender.By simply removing
fee_needed, I think we can get the behavior that we want. In this case, after the change is dropped, we will updatenFeeRetto be the dropped change amount. Thento_reduceends up being 0 and the recipients don't pay any fees, but also don't get anything back, so the excess is paid as fees. I believe that this is the intended behavior, and is simpler to understand too.achow101 force-pushed on Jun 4, 2021achow101 force-pushed on Jun 4, 2021in src/wallet/spend.cpp:779 in 303a6645dc outdated
777 | + nFeeRet = coin_selection_params.m_effective_feerate.GetFee(nBytes); 778 | } 779 | 780 | - // Update nFeeRet in case fee_needed changed due to dropping the change output 781 | - if (fee_needed <= change_and_fee - change_amount) { 782 | + // Update nFeeRet in case it changed due to dropping the change output
ryanofsky commented at 5:42 PM on June 4, 2021:In commit "Remove unneeded fee_needed variable" (303a6645dc604e1d074a1aa6253f4dc8a1c3109e)
I think this comment is a big vague and maybe even misleading because this code isn't just updating the return value but also increasing the fee paid in the subtract-from-outputs case. I would maybe say something like "// Increase nFeeRet to reflect extra fee paid by giving up the small change amount which is smaller than the cost of the change output."
ryanofsky approvedryanofsky commented at 6:05 PM on June 4, 2021: memberReluctant code review ACK 303a6645dc604e1d074a1aa6253f4dc8a1c3109e because it does what is described, but I would prefer previous push 96c2c9520e80ee4fed92f0e1ab859d59fcbdb110 over this push, and then addressing
fee_neededbehavior and cleanup in a followup PR, ideally including a unit test so we are sure this continues to function as intended.I wouldn't completely object to changing the behavior like 303a6645dc604e1d074a1aa6253f4dc8a1c3109e does, since the difference is only for small amounts (the cost of a change output) and only in the-subtract-from-recipient case, so I don't think it is too important. But I do think the current behavior does make sense, and I preserved it intentionally in #22008 (comment) and not by accident. Thinking about use-cases, if I enable subtracting-from-recipient, it seems likely I'm transferring funds internally and not making an external payment, and so would prefer to keep my funds instead of overpaying the miner. Even if this is an external payment, then the subtract-from-recipient option already implies the recipient is not sensitive to the exact amount, and so if I'm topping up an external account or a channel, or paying for an online service, again the new behavior would be throwing away my own money/credits to give to a miner.
The one part of current behavior that is perhaps is less than ideal is the fact that the returned
nFeeRetdoesn't reflect the full cost of sending the transaction. I would fix this just by changing theelse if (nFeeRet..toif (nFeeRet...and dropping the long "note" in #22008 (comment)achow101 force-pushed on Jun 4, 2021achow101 commented at 6:10 PM on June 4, 2021: memberI've reverted back to 96c2c9520e80ee4fed92f0e1ab859d59fcbdb110
I think we should discuss the intended behavior of subtract fee from recipients during the wallet meeting today.
ryanofsky approvedryanofsky commented at 6:19 PM on June 4, 2021: memberCode review ACK 96c2c9520e80ee4fed92f0e1ab859d59fcbdb110 also acked previously (was reverted).
Will check out the IRC. I will say I don't see a need to tackle
fee_neededin this PR since the PR is already doing a bunch of things and has already had a good amount of review.fee_neededlogic also seems more tied to removing the while loop in #17331 than anything done here. And especially if it will change behavior, I think it could be better as standalone PR so it can be discussed and understood without having wade through a ton of refactoring.ryanofsky referenced this in commit 73bd26cb3d on Jun 4, 2021ryanofsky commented at 3:20 PM on June 7, 2021: memberre: #22008 (comment)
Maybe another cleanup for later, but the separate fee_needed and nFeeRet variables haven't been needed [...]
fee_needed cleanup was discussed more in IRC and is addressed in #22155 for now adding by a unit test to exercise and check the relevant behavior, since there isn't other test coverage
ryanofsky commented at 3:25 PM on June 7, 2021: memberThis is probably close to ready to being merged
Up to date ACKs
glozow #22008#pullrequestreview-675439314 ryanofsky #22008#pullrequestreview-676223208
Previous ACKS
Sjors #22008#pullrequestreview-665540991 Sjors #22008 (comment) Xekyo #22008 (comment) ryanofsky #22008 (comment) glozow #22008#pullrequestreview-671176599 meshcollider #22008#pullrequestreview-671810704
ryanofsky referenced this in commit 4f7c483754 on Jun 8, 2021ryanofsky referenced this in commit 972e87c082 on Jun 8, 2021meshcollider commented at 5:18 AM on June 9, 2021: contributorre-utACK 96c2c9520e80ee4fed92f0e1ab859d59fcbdb110
meshcollider merged this on Jun 9, 2021meshcollider closed this on Jun 9, 2021fanquake removed this from the "Blockers" column in a project
sidhujag referenced this in commit c3ea2f5b82 on Jun 10, 2021ryanofsky referenced this in commit 8f6113f51d on Jun 10, 2021ryanofsky referenced this in commit 814b8fb28e on Jun 10, 2021ryanofsky referenced this in commit 6a50482691 on Jun 10, 2021ryanofsky referenced this in commit f3cbde4fbb on Jun 12, 2021ryanofsky referenced this in commit 0cc10f8377 on Jun 12, 2021ryanofsky referenced this in commit def672f870 on Jul 14, 2021gwillen referenced this in commit a5d97b363b on Jun 1, 2022DrahtBot locked this on Aug 18, 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-02 18:14 UTC
This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me