New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
wallet: Avoid requesting fee rates multiple times during coin selection #21083
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ConflictsReviewers, this pull request conflicts with the following ones:
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. |
Concept ACK, was thinking about this as well |
Concept / light-look-through ACK |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code review ACK 7b4b48f. What a fucking horror show this code is! But overall the change does make it better. I left some suggestions below, but they're not important so feel free to ignore!
src/wallet/wallet.cpp
Outdated
return false; | ||
} | ||
|
||
nFeeNeeded = coin_selection_params.effective_fee.GetFee(nBytes); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In commit "wallet: Use existing feerate instead of getting a new one" (81b6a6b)
Review note: this assignment is equivalent to previous GetMinimumFee
assignment because GetMinimumFeeRate
is always assigned to nFeeRateNeeded
line 2810, and nFeeRateNeeded
is always assigned to coin_selection_params.effective_fee
on line 2896 (thanks to pick_new_inputs
spaghetti) and because GetMinimumFee()
is just a wrapper for GetMinimumFeeRate().GetFee()
src/wallet/wallet.h
Outdated
size_t tx_noinputs_size = 0; | ||
//! Indicate that we are subtracting the fee from outputs | ||
bool m_subtract_fee_outputs = false; | ||
bool m_avoid_partial_spends = false; | ||
|
||
CoinSelectionParams(bool use_bnb, size_t change_output_size, size_t change_spend_size, CFeeRate effective_fee, size_t tx_noinputs_size, bool avoid_partial) : | ||
CoinSelectionParams(bool use_bnb, size_t change_output_size, size_t change_spend_size, CFeeRate effective_fee, CFeeRate long_term_feerate, CFeeRate discard_feerate, size_t tx_noinputs_size, bool avoid_partial) : |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest not expanding the monster constructor here. Overall code readability seems to improve just assigning the new members after construction.
(Also much cleaner to backport...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #21083 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
re https://github.com/bitcoin/bitcoin/pull/21083/files#r584415074 and #21083 (comment):
I agree with @ryanofsky and @luke-jr that expanding this very large ctor signature makes the code less readable. Russ's suggestion of using std::optionals is good. Alternatively, I think these small changes would make things more readable:
- add line breaks to the declaration and call sites, so there aren't 200+ column lines
- add doxygen comments to the members so it's obvious what they're doing
- use inline comments in the call sites:
-CoinSelectionParams coin_selection_params(false, 0, 0, CFeeRate(0), CFeeRate(0), CFeeRate(0), 0, false);
+CoinSelectionParams coin_selection_params(/* use_bnb= */ false, /* change_output_size= */ 0,
+ /* change_spend_size= */ 0, /* effective_fee= */ CFeeRate(0),
+ /* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0),
+ /* tx_noinputs_size= */ 0, /* avoid_partial= */ false);
c++20 will bring designated initializers, which make initializing large structs like this much more readable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tried to make it more readable. However I also think that setting all of these things should be set in the constructor and that the current thing where we set all the parameters ad-hoc is not a good design.
Code review ACK 7b4b48f The code is definitely an improvement and can be merged as-is. Looking at #19229 I was skeptical initially that this would fix it fully because, even though nobody said It explicitly, I assume some people have tried running the command multiple times. It sounded like it was a particular tx that caused these issues, rather than it just happening inconsistently on different txs which would seem more reasonable if a volatile fee market is the cause for this. But I have stared at the code for a pretty long time now (particularly the |
That's correct, @fjahr, we have observed that a retry can still be met with the error. Bad luck maybe? If a 0.21 backport branch were available, I might be able to try this out. |
During each loop of CreateTransaction, instead of constantly getting a new feerate, use the feerate that we have already fetched for all fee calculations. Thix fixes a race condition where the feerate required changes during each iteration of the loop. This commit changes behavior as the "Fee estimation failed" error will now take priority over "Signing transaction failed".
Make sure that all fee calculations use the same feerate. coin_selection_params.effective_fee is the variable we use for all fee calculations, so get rid of remaining nFeeRateNeeded usages and just directly set coin_selection_params.effective_fee. Does not change behavior.
cca8b00
to
cb93fbd
Compare
@chappjc Here is a branch of this PR backported to 0.21: https://github.com/achow101/bitcoin/tree/0.21-createtx-same-feerate |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review ACK (cb93fbd)
Great improvement! Please do rename the effective_fee
to effective_feerate
, though, if you have to update again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
utACK cb93fbd
I think this is a good change but actually could go a lot further, heh. In my opinion everything in CoinSelectionParams
should be const
and grabbed before coin selection begins. And a second attempt (e.g. not using bnb anymore) can be a copy/move of the first set of params.
Instead of setting the long term feerate for each SelectCoinsMinConf iteration, set it once during CreateTransaction and let it be shared with each SelectCoinsMinConf through coin_selection_params.m_long_term_feerate. Does not change behavior.
Instead of fetching the discard feerate for each SelectCoinsMinConf iteration, fetch and cache it once during CreateTransaction so that it is shared for each SelectCoinsMinConf through coin_selection_params.m_discard_feerate. Does not change behavior.
cb93fbd
to
3e77fbc
Compare
It's a feerate, not a fee. Also follow the style guide for member names.
3e77fbc
to
f9cd2bf
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tACK f9cd2bf
use_bnb(use_bnb), | ||
change_output_size(change_output_size), | ||
change_spend_size(change_spend_size), | ||
effective_fee(effective_fee), | ||
m_effective_feerate(effective_feerate), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should perhaps all of these now start with m_
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Good for followup?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They should, but that's an unrelated change. The newly added members use m_
.
reACK f9cd2bf Changes since last review:
|
Code review re-ACK f9cd2bf Checked range-diff since last review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code review + test run ACK f9cd2bf
…s during coin selection
@chappjc please let us still know how it's going. Thanks! |
I haven't been able to test in our use case, but incidentally the error has been rare lately even with release. I will provide feed back when possible. |
@achow101 did you want to open a PR to 0.21 with this branch? |
During each loop of CreateTransaction, instead of constantly getting a new feerate, use the feerate that we have already fetched for all fee calculations. Thix fixes a race condition where the feerate required changes during each iteration of the loop. This commit changes behavior as the "Fee estimation failed" error will now take priority over "Signing transaction failed". Github-Pull: bitcoin#21083 Rebased-From: 1a6a0b0
Make sure that all fee calculations use the same feerate. coin_selection_params.effective_fee is the variable we use for all fee calculations, so get rid of remaining nFeeRateNeeded usages and just directly set coin_selection_params.effective_fee. Does not change behavior. Github-Pull: bitcoin#21083 Rebased-From: e2f429e
Instead of setting the long term feerate for each SelectCoinsMinConf iteration, set it once during CreateTransaction and let it be shared with each SelectCoinsMinConf through coin_selection_params.m_long_term_feerate. Does not change behavior. Github-Pull: bitcoin#21083 Rebased-From: 448d04b
Instead of fetching the discard feerate for each SelectCoinsMinConf iteration, fetch and cache it once during CreateTransaction so that it is shared for each SelectCoinsMinConf through coin_selection_params.m_discard_feerate. Does not change behavior. Github-Pull: bitcoin#21083 Rebased-From: bdd0c29
It's a feerate, not a fee. Also follow the style guide for member names. Github-Pull: bitcoin#21083 Rebased-From: f9cd2bf
It's a feerate, not a fee. Also follow the style guide for member names. Github-Pull: bitcoin#21083 Rebased-From: f9cd2bf
…s during coin selection d61fb07 Rename CoinSelectionParams::effective_fee to m_effective_feerate (Andrew Chow) 5fc381e wallet: Move discard feerate fetching to CreateTransaction (Andrew Chow) bcd7166 wallet: Move long term feerate setting to CreateTransaction (Andrew Chow) 34c89f9 wallet: Replace nFeeRateNeeded with effective_fee (Andrew Chow) 48fc675 wallet: Use existing feerate instead of getting a new one (Andrew Chow) Pull request description: Backport of #21083 ACKs for top commit: MarcoFalke: cherry-pick-only re-ACK d61fb07 🔙 instagibbs: utACK d61fb07 Tree-SHA512: 23b212301bb467153dd9723903918ae01dd520525c81d541c411e7a4381e46594fe032e2a7c06ddcff7dc56dcb546991d50187c33fcff08ec45bd835cc01bd19
During coin selection, there are various places where we need to have a feerate. We need the feerate for the transaction itself, the discard fee rate, and long term feerate. Fetching these each time we need them can lead to a race condition where two feerates that should be the same are actually different. One particular instance where this can happen is during the loop in
CreateTransactionInternal
. After inputs are chosen, the expected transaction fee is calculated using a newly fetched feerate. Ifpick_new_inputs == false
, the loop will go again with the assumption that the fee for the transaction remains the same. However because the feerate is fetched again, it is possible that it actually isn't and this causes coin selection to fail.Instead of fetching the feerate each time it is needed, we fetch them all at once at the top of
CreateTransactionInternal
, store them inCoinSelectionParams
, and use them where needed.While some of these fee rates probably don't need this caching, I've done it for consistency and the guarantee that they remain the same.
Fixes #19229