-
Notifications
You must be signed in to change notification settings - Fork 35.5k
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: fix BnB selection upper bound #28395
wallet: fix BnB selection upper bound #28395
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. 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. |
Concept ACK |
Cherry-picked this commit to test it with #28372 and did the changes to the harness to fit it. Previous crashes don't fail anymore, will leave it running overnight! diff --git a/src/wallet/test/fuzz/coinselection.cpp b/src/wallet/test/fuzz/coinselection.cpp
index 07bb0f001c..2c96c79ea2 100644
--- a/src/wallet/test/fuzz/coinselection.cpp
+++ b/src/wallet/test/fuzz/coinselection.cpp
@@ -125,9 +125,9 @@ FUZZ_TARGET(coinselection)
}
// Run coinselection algorithms
- auto result_bnb = SelectCoinsBnB(group_pos, target, coin_params.m_cost_of_change, MAX_STANDARD_TX_WEIGHT);
+ auto result_bnb = SelectCoinsBnB(group_pos, target, coin_params.min_viable_change, MAX_STANDARD_TX_WEIGHT);
if (result_bnb) {
- assert(result_bnb->GetChange(coin_params.m_cost_of_change, CAmount{0}) == 0);
+ assert(result_bnb->GetChange(coin_params.min_viable_change, CAmount{0}) == 0);
assert(result_bnb->GetSelectedValue() >= target);
(void)result_bnb->GetShuffledInputVector();
(void)result_bnb->GetInputSet(); |
cc: @murchandamus |
|
It needs to change unit tests and fuzz. |
1167ab9
to
62dca3c
Compare
…ound As BnB is an algorithm specialized in seeking changeless solutions, the final selection amount (the sum of all the selected UTXOs amount) can be as high as one unit below the amount where the transaction creation process starts creating a change output. Which is: target + min_viable_change - 1. In other words, the range of the possible solutions is: target <= solution_amount < target + min_viable_change - 1.
The following cases are covered: 1) The only available solution contain an excess equal to min_viable_change. The expected behavior here is BnB search to fail. Because, if BnB would return a valid solution, the transaction creation process would create a change output (which is what we don't expect from a BnB solution). 2) The only available solution contain an excess one unit below the min_viable_change. The expected behavior here is BnB search returning a successful solution.
62dca3c
to
f81047b
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.
Updated the PR and its description. Added test coverage in a standalone commit so it can easily be cherry-picked on top of master to verify the failure.
Also, about the changes.
I wouldn't be against continue using cost_of_change
on the BnB upper bound. I just didn't do it because that would require further modifications at the transaction creation process level rather than at the coin selection algorithm level. But, happy to discuss this possible approach too.
I don't think this is correct. The upper bound is the cost of change, not the minimum viable change. The minimum viable change is the minimum we're willing to make and this is not inherently tied to feerates. Cost of change is tied to the current feerate and the long term feerate. The minimum viable change is generally much higher than the cost of change. This PR will result in BnB solutions throwing away far more money. |
If I recall correctly, the Bitcoin Core definition of |
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 don't think this is correct. The upper bound is the cost of change, not the minimum viable change. The minimum viable change is the minimum we're willing to make and this is not inherently tied to feerates. Cost of change is tied to the current feerate and the long term feerate. The minimum viable change is generally much higher than the cost of change. This PR will result in BnB solutions throwing away far more money.
Ok, will expand. Thats partially correct. Check the following. It was all taken from the sources.
"cost of change" is computed by (link):
change_fee = effective_feerate.GetFee(change_output_size);
change_spend_fee = discard_feerate.GetFee(change_spend_size)
cost_of_change = change_spend_fee + change_fee;
And, the "minimum viable change" is computed by (link):
dust = GetDustThreshold(change_prototype_txout, discard_feerate);
change_spend_fee = discard_feerate.GetFee(change_spend_size);
min_viable_change = std::max(change_spend_fee + 1, dust);
So, as generally change_spend_fee > dust. We can say that
min_viable_change = change_spend_fee + 1
Which means that, the minimum viable change is lower than cost of change. Exactly by change_fee - 1
:
cost_of_change = min_viable_change + change_fee - 1
Therefore, conclusion is that cost_of_change > min_viable_change
.
Now, let's look at the two possible GetChange()
outcomes. Using the outputs' effective value vs SFFO.
-
Using the outputs' effective value:
GetChange()
expandsmin_viable_change
inchange_fee
, which makesmin_viable_change > cost_of_change
. Exactly by 1. -
Using SFFO:
GetChange()
usesmin_viable_change
as is. Keeping thecost_of_change > min_viable_change
inequality. The diff is exactlychange_fee - 1
.
So, it means that.. we are both correct.
The first scenario describes what you are saying, while the second one describes what I'm saying. Essentially, BnB, in master, can currently throw up to change_fee - 1
to the change output during SFFO.
Will work on this. Thanks.
I am confused by your conclusion
According to
|
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.
Thanks Murch. Summary of the off-github convo:
I am confused by your conclusion
So, as generally change_spend_fee > dust.
According to
GetDustThreshold(…)
,dust = (output_size + input_size) × discard_feerate
whilechange_spend_fee
is onlychange_spend_size × discard_feerate
. So shouldn’t that rather be:change_spend_fee < dust
Yeah. Thats true for the common change output types (p2pkh and p2wpkh), dust is greater than change_spend_fee. But, for p2sh and p2wsh the change output redeem script size can turn around the inequality. GetDustThreshold
input size calculation is fixed, while change_spend_fee
uses a dummy input mimicking the real input size.
So.. the conclusion that I had above is not accurate. As regular output types are mostly used in the change output, the bare min_viable_change
is usually higher than cost_of_change
. Unless the effective feerate is high enough. But, that is a no-prob, because GetChange
expands min_viable_change
by change_fee
which covers for the difference (change_fee
is the field linked to the effective feerate).
As far as I can deduce now, the only scenario where min_viable_change
could be lower than cost_of_change
is when SFFO is enabled and the effective feerate is high enough. Because BnB upper bound includes cost_of_change
, which includes change_fee
(the cost of the output). And.. we don't extend min_viable_change
in ´GetChange´ when SFFO is enabled. So, change_fee
is not part of the min_viable_change
expansion. Which ends up with a BnB result requiring a change output.
Apologies if this looks a bit intricate; it's a subject that really drills down into a niche within a niche..
Given that we had so much confusion on the exact details of this, maybe we should add tests that confirm our understanding of all the edge cases here. Yet again, it is revealed that SFFO is the root of all evil in Bitcoin Core wallet. ;) |
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.
Given that we had so much confusion on the exact details of this, maybe we should add tests that confirm our understanding of all the edge cases here.
Sure. Made a test for this here furszy@23333b4.
As the test is on top of master. It is failing outputting: "Error: BnB selection produced a change output!".
Now.. will spend some time thinking on an elegant way to solve this. The simplest one, and the more targeted one, would be to decrease the cost of change by change_fee
for the BnB selection function argument. But there might be other ways to fix it.
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.
Closing per discussion. Instead of modifying BnB upper bound (as is proposed here), there was consensus on disabling BnB when SFFO is enabled. @murchandamus should be pushing a new PR shortly.
The unit test from my previous comment can still be used there. Feel free to cherry-pick the following branch commits (containing a slightly different version of my previous test, using the logs instead of adding a new return value). This test branch fails without the bugfix and will pass with it.
As BnB is an algorithm specialized in seeking changeless solutions, the
final selection amount (the sum of all the selected UTXOs amount) can be
as high as one unit below the amount where the transaction creation
process starts creating a change output. Which is: target + min_viable_change - 1.
In other words, the range of the possible solutions is:
target <= solution_amount < target + min_viable_change - 1.
Code level description:
We have a discrepancy on the threshold at which we create, or not, "change" between the BnB
algorithm and the, higher level, transaction creation process.
BnB selection upper bound uses
cost_of_change
, while the transaction creation process usesthe
min_viable_change
variable to obtain the resulting change amount from the coin selectionsolution (here).
Essentially, this means that under certain circumstances; when the BnB solution excess is
in-between
min_viable_change
andcost_of_change
, the BnB algorithm returns asuccessful solution which, under the transaction creation process perspective, requires to create
change output. This, isn't an expected behavior as BnB is specialized to only return a changeless
solution.
This can happen when min_viable_change <= BnB_solution_amount - target < cost_of_change.
Note:
This should solve the issue presented in #28372.
Testing Note:
I have decoupled the test in a standalone commit so it can be easily cherry-picked on top of master to
verify the test failure vs the test passing here after the changes.