-
Notifications
You must be signed in to change notification settings - Fork 38.8k
wallet: re-activate "AmountWithFeeExceedsBalance" error #25269
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: re-activate "AmountWithFeeExceedsBalance" error #25269
Conversation
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. Code Coverage & BenchmarksFor details see: https://corecheck.dev/bitcoin/bitcoin/pulls/25269. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. 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. |
|
Seems like an awful lot of refactoring and optimisations tied into what is in essence a bugfix. Maybe split them up? |
Hey Luke, only the last three commits count for this work. It's mentioned in the PR description to not generate confusion: This was built on top of #25005 because needed the structure introduced in 5b6124d to implement this flow properly. (where "properly" means not doing an ugly workaround and be able to place the new code on top of a cleaner structure) Now that #25005 got merged, will rebase it and get rid of all the extra commits. |
d506b50 to
9adfbc0
Compare
9adfbc0 to
6c260d1
Compare
|
Looks like this works around a regression introduced by #20640 Might be better to make |
6c260d1 to
30971a0
Compare
|
Hey @luke-jr thanks for the review, was tackling other PRs before moving back to this one.
As we are now returning an A possible elegant solution going into the "always return information" path would be to return an object that wraps the transaction creation process information (with the fee, the used coin selection algorithm, change output, etc..) in the error field of the returned But aside from that (which I think that would be a good long term goal), maybe might worth to continue with the PR as is now, primarily because it's unifying the errors that we throw on the GUI and on the RPC server. Still, have to say that I would like to be able to move this kind of errors from the wallet sources to a class that lives on an upper layer like the wallet interface. So GUI and RPC receive the same errors and the wallet isn't in charge of describe this type of errors for the user. |
furszy
left a 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.
Based on yesterday's wallet meeting, have been thinking about 032e544 further and the possibility to move the error to SelectCoins instead of placing it inside CreateTransaction.
The commit, as is now, it does not cover the "fee exceeds balance" error in a 100%. It's only covering the "fee exceeds balance" if the selection fails for the not-inputs fees, it is not contemplating the inputs fees (this is not different to what we were doing before #20640).
So, need to move the error to SelectCoins, and add a mechanism to keep track of the inputs fee total amount (data that we don't have in CreateTransaction if SelectCoins fail).
But.. ideally, before implementing this changes, would love to get #25806 merged as it simplifies the whole coin selection process greatly (which depends on #25685, which is ready to go).
25685 has been merged, and it looks like 25806 has just been rebased. How about marking this PR draft for now, given the dependency on 25806, as well as adding a note at the top of the PR description, indicating to reviewers that they should review 25806 first. |
…error messages 76dc547 gui: create tx, launch error dialog if backend throws runtime_error (furszy) f4d7947 wallet: coin selection, add duplicated inputs checks (furszy) 0aa065b wallet: return accurate error messages from Coin Selection (furszy) 7e8340a wallet: make SelectCoins flow return util::Result (furszy) e5e147f wallet: refactor eight consecutive 'AttemptSelection' calls into a loop (furszy) Pull request description: Work decoupled from #25806, which cleanup and improves the Coin Selection flow further. Adding the capability to propagate specific error messages from the Coin Selection process to the user. Instead of always returning the general "Insufficient funds" message which is not always accurate to what happened internally. Letting us instruct the user how to proceed under certain circumstances. The following error messages were added: 1) If the selection result exceeds the maximum transaction weight, we now will return: -> "The inputs size exceeds the maximum weight. Please try sending a smaller amount or manually consolidating your wallet's UTXOs". 2) If the user pre-selected inputs and disallowed the automatic coin selection process (no other inputs are allowed), we now will return: -> "The preselected coins total amount does not cover the transaction target. Please allow other inputs to be automatically selected or include more coins manually". 3) The double-counted preset inputs during Coin Selection error will now throw an "internal bug detected" message instead of crashing the node. The essence of this work comes from several comments: 1. bitcoin/bitcoin#26560 (comment) 2. bitcoin/bitcoin#25729 (comment) 3. bitcoin/bitcoin#25269 (review) 4. bitcoin/bitcoin#23144 (which is connected to #24845) ACKs for top commit: ishaanam: crACK 76dc547 achow101: ACK 76dc547 aureleoules: ACK 76dc547 theStack: ACK 76dc547 🌇 Tree-SHA512: 9de30792d7a5849cae77747aa978e70390b66ee9d082779a56088a024f82e725b0af050e6603aece0ac8229f6d73bc471ba97b4ab69dc7eddf419f5f56ae89a5
murchandamus
left a 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.
Concept ACK
|
Needs rebase, if still relevant |
900e5ed to
8cefff3
Compare
murchandamus
left a 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.
I’m wondering whether this is correct when SFFO is active.
src/wallet/spend.cpp
Outdated
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.
Note to reviewers: preset_inputs.total_amount will be the sum of the outputs’ effective values if "subtract fee from outputs" is disabled, but will be the nominal amount if SFFO is active. See src/wallet/spend.h:164–172
src/wallet/spend.cpp
Outdated
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’m wondering whether there is a bug here. It seems to me that this is assuming that the effective value is only available when SFFO is not being used, but COutput.effective_value is set and aggregated whenever the COutputs have a feerate. Does this not explicitly need to handle SFFO? I.e., maybe this needs to be something along the lines of:
| CAmount available_effective_balance = preset_inputs.total_amount + available_coins.GetEffectiveTotalAmount().value_or(available_coins.GetTotalAmount()); | |
| CAmount available_coins_total_amount = coin_selection_params.m_subtract_fee_outputs ? available_coins.GetTotalAmount() : available_coins.GetEffectiveTotalAmount(); | |
| CAmount available_effective_balance = preset_inputs.total_amount +available_coins_total_amount; |
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 catch!, fixed. This also revealed that the optional field CoinsResult::total_effective_amount has never being optional (it was being set to 0 during construction).
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.
Since fees are not included when SFFO, I don't think this is reachable as should available_balance is always greater than recipients_sum in that case, so I don't think this needs any special SFFO treatment. Furthermore, if it were possible to reach this with SFFO, I think the error message would be confusing and it would actually be better to return "Insufficent Funds", so this whole check could probably be guarded by an if (m_subtract_fee_outputs) to skip it when SFFO is on.
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.
ha.. true. It seems I'm not putting much brain power here.. updated per feedback. Thanks.
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: #25269 (comment)
Furthermore, if it were possible to reach this with SFFO, I think the error message would be confusing and it would actually be better to return "Insufficent Funds", so this whole check could probably be guarded by an
if (m_subtract_fee_outputs)to skip it when SFFO is on.
I don't think I follow the logic behind this but it's possible I'm misunderstanding. If user is trying to send less money than their balance, and it fails because fees are too high it would seem like an "exceeds your balance when fees are included" message (especially one that specifies what fee the wallet was targeting) would be clearer than an "insufficient funds" message. It seems like should be true independently of whether the sender or recipient is paying the fees.
So I guess I don't understand the reason for a m_subtract_fee_outputs special case here, and it would seem simpler and better to drop. I do that see in practice that effective values are ignored when SFFO is used, so it should be safe to Assume(!coin_selection_params.m_subtract_fee_outputs) here if no other code changes, but this would seem like unnecessary assumption and not something this code needs to concern itself with.
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: #25269 (comment)
Thinking about this more I can see why SFFO should make the "amount exceeds your balance when fees are included" condition harder to trigger than when SFFO is not used, because when SFFO is enabled the wallet is able to reduce the amounts recipients receive to cover any fees, whereas without SFFO the amount received needs to be fixed. But I'd still expect the (available_balance >= recipients_sum && available_effective_balance < selection_target) condition to be able to trigger when SFFO is used if there are coins are which are not economical to send. Also even if there is other code making this condition difficult or impossible to trigger when SFFO is used, I still don't see a reason this code should be checking for SFFO specially. Even with SFFO, an error message that mentions fees and includes the attempted fee would still be more descriptive and helpful than the generic "insufficient funds" message.
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: #25269 (comment)
Update: think I finally figured this out. In the SFFO case the fee exceeds balance error can still happen but the checks here will not detect it. So I think the change I would suggest would just be dropping the m_subtract_fee_outputs code and writing a more descriptive comment:
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -1164,12 +1164,19 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
// Check if we have enough balance but cannot cover the fees
CAmount available_balance = preset_inputs.total_amount + available_coins.GetTotalAmount();
- // Note: Since SFFO reduces existing outputs to cover fees, recipients_sum should already be
- // strictly less than available_balance when enabled. Still, as a sanity-check, skip when SFFO is enabled.
- if (available_balance >= recipients_sum && !coin_selection_params.m_subtract_fee_outputs) {
+ if (available_balance >= recipients_sum) {
CAmount available_effective_balance = preset_inputs.total_amount + *available_coins.GetEffectiveTotalAmount();
if (available_effective_balance < selection_target) {
- return util::Error{_("The total transaction amount exceeds your balance when fees are included")};
+ // Note: this fees exceeds balance error will not currently
+ // trigger when SFFO is enabled because the effective balance
+ // will equal the original balance in this case. This is not
+ // ideal because it doesn't provide the most descriptive error
+ // message, but it's not worth making this code more complex by
+ // checking the sizes of outputs fees can be subtracted from
+ // here. Also, fees exceeding the balance should happen more
+ // rarely with SFFO given the ability to take fees from outputs.
+ Assume(!coin_selection_params.m_subtract_fee_outputs);
+ return util::Error{strprintf(_("The total exceeds your balance when the %s transaction fee is included."), FormatMoney(selection_target - recipients_sum))};
}
}
21dc635 to
5731295
Compare
|
Updated per feedback. Thanks murchandamus! |
5731295 to
e0673d5
Compare
…error This was previously implemented at the GUI level but has been broken since bitcoin#20640
e0673d5 to
98b7f10
Compare
| CAmount available_balance = preset_inputs.total_amount + available_coins.GetTotalAmount(); | ||
| // Note: Since SFFO reduces existing outputs to cover fees, recipients_sum should already be | ||
| // strictly less than available_balance when enabled. Still, as a sanity-check, skip when SFFO is enabled. | ||
| if (available_balance >= recipients_sum && !coin_selection_params.m_subtract_fee_outputs) { |
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 98b7f10 "wallet: introduce "tx amount exceeds balance when fees are included" error"
I'm pretty sure that !coin_selection_params.m_subtract_fee_outputs is not necessary as it should not be possible for SelectCoins to fail but available_balance >= recipients_sum with SFFO.
Perhaps this could be made into an Assume inside the if?
|
🐙 This pull request conflicts with the target branch and needs rebase. |
|
⌛ There hasn't been much activity lately and the patch still needs rebase. What is the status here?
|
|
Closing this as it has not had any activity in a while. If you are interested in continuing work on this, please leave a comment so that it can be reopened. Marking as up for grabs. |
ryanofsky
left a 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.
Code review ACK 98b7f10 for the second commit, but I am unsure about the first commit (see below). I started reviewing this because bitcoin-core/gui#807 depends on it, but this seems like a nice change independently that provides a clearer error message and fixes a GUI feature that was accidentally broken in #20640.
| CAmount total_amount{0}; | ||
| /** Sum of all available coins effective value (each output value minus fees required to spend it) */ | ||
| std::optional<CAmount> total_effective_amount{0}; | ||
| std::optional<CAmount> total_effective_amount; |
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: fix, make 'total_effective_amount' optional actually optional" (5687530)
I wonder if this change requires updates to any code that might be assuming total_effective_amount is not null like:
Line 226 in 56e9703
| if (coin.HasEffectiveValue()) total_effective_amount = *total_effective_amount - coin.GetEffectiveValue(); |
Also it's not really clear what the connection is between this change in the rest of the PR. It would help to mention some motivation in the commit description.
| if (available_balance >= recipients_sum && !coin_selection_params.m_subtract_fee_outputs) { | ||
| CAmount available_effective_balance = preset_inputs.total_amount + *available_coins.GetEffectiveTotalAmount(); | ||
| if (available_effective_balance < selection_target) { | ||
| return util::Error{_("The total transaction amount exceeds your balance when fees are included")}; |
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: introduce "tx amount exceeds balance when fees are included" error" (98b7f10)
Can this error message include amount of fees that was in the original error message. It seems like knowing how much the wallet is trying to spend could help the user address the problem. It looks like equivalent amount would be selection_target - recipient_sum here.
| // Note: Since SFFO reduces existing outputs to cover fees, recipients_sum should already be | ||
| // strictly less than available_balance when enabled. Still, as a sanity-check, skip when SFFO is enabled. | ||
| if (available_balance >= recipients_sum && !coin_selection_params.m_subtract_fee_outputs) { | ||
| CAmount available_effective_balance = preset_inputs.total_amount + *available_coins.GetEffectiveTotalAmount(); |
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: introduce "tx amount exceeds balance when fees are included" error" (98b7f10)
Is it safe to assume GetEffectiveTotalAmount() returns not null here after previous commit? Might be better to use value_or
src/wallet/spend.cpp
Outdated
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: #25269 (comment)
Furthermore, if it were possible to reach this with SFFO, I think the error message would be confusing and it would actually be better to return "Insufficent Funds", so this whole check could probably be guarded by an
if (m_subtract_fee_outputs)to skip it when SFFO is on.
I don't think I follow the logic behind this but it's possible I'm misunderstanding. If user is trying to send less money than their balance, and it fails because fees are too high it would seem like an "exceeds your balance when fees are included" message (especially one that specifies what fee the wallet was targeting) would be clearer than an "insufficient funds" message. It seems like should be true independently of whether the sender or recipient is paying the fees.
So I guess I don't understand the reason for a m_subtract_fee_outputs special case here, and it would seem simpler and better to drop. I do that see in practice that effective values are ignored when SFFO is used, so it should be safe to Assume(!coin_selection_params.m_subtract_fee_outputs) here if no other code changes, but this would seem like unnecessary assumption and not something this code needs to concern itself with.
Since bitcoin#20640, the 'createTransaction' does no longer retrieve the fee if the process fails due to insufficient funds. But, since bitcoin#25269, 'createTransaction' retrieves an error message indicating that the total transaction amount exceeds the wallet available balance when fees are included. So this enum is no longer needed.
this is not needed for the remaining commits but good to fix and came up in bitcoin#25269 review. Co-authored-by: furszy <[email protected]>
During a talk with theStack, it was noted that the
AmountWithFeeExceedsBalanceerror insideWalletModel::prepareTransactionis never thrown.This is because
createTransactiondoes not retrieve the fee when the process fails due to insufficient funds since #20640. The fee return arg is set only at the end of the process, when the transaction is successfully created. Therefore, if the transaction creation fails, the fee is not available insideWalletModel::prepareTransactionto trigger theAmountWithFeeExceedsBalanceerror.This PR re-implements the feature inside
createTransactionand adds test coverage for it.