-
Notifications
You must be signed in to change notification settings - Fork 38.7k
refactor: Simplify backtrack logic #25932
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
Conversation
a515e59 to
7827e0b
Compare
7827e0b to
1a2d94a
Compare
|
Concept ACK, Approach ACK |
|
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. ConflictsNo conflicts as of last run. |
src/wallet/coinselection.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.
ACK 1a2d94ac586e2ecb55994205fceec14322a949fd
Since the feerate is loop invariant, that boolean will always evaluate the same across one coin selection attempt, and it would be even better to just pull it out of the loop:
| (curr_waste > best_waste && (utxo_pool.at(0).fee > utxo_pool.at(0).long_term_fee))) { // Don't select things which we know will be more wasteful if the waste is increasing | |
| [before loop] | |
| bool is_high_feerate = utxo_pool.at(0).fee > utxo_pool.at(0).long_term_fee; | |
| […] | |
| (is_high_feerate && curr_waste > best_waste)) { // Don't select things which we know will be more wasteful if the waste is increasing |
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 considered this as well. To my surprise moving the comparison outside of the loop didn't make any benchmark difference. My guess is the compiler must be smart enough to do some optimization, or the benchmark tests aren't sensitive enough to detect a change.
That bieng said, I do think it's more readable outside of the for loop (and doesn't need to be compared every iteration). @xekyo does this look better 8ada8bb?
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.
b04deff to
953ce06
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.
Yeah, looks better outside of the loop, but I think the comment would be better as an amendment of the big description in the function, but I also disagree with how the algorithm is characterized.
src/wallet/coinselection.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.
This is not a good description of what the algorithm does. BnB deterministically searches the combination tree of the UTXO pool and retains the input set with the minimal waste score among the input sets it traverses. It doesn't guarantee to find the minimal waste score, and it doesn't guarantee to minimize the input set at high feerates. The result may end up being the minimal input set at high fees but might not be. E.g. if the feerate were 10.001 sat/vB and the LTFRE 10 sat/vB, the waste score could prefer a signficantly larger input set that results in a smaller excess over a smaller one with large excess, even while this bool classify the coin selection situation as is_feerate_expensive = True.
Meanwhile at low feerates BnB does not guarantee to maximimize the input set—it also keeps the input set with lowest waste score. Since the depth-first search starts with the most valuable UTXOs, input sets with a smaller count are traversed before ones with larger count are visited. Even then, the UTXO types may cause a smaller count to be preferred because the total weight of older output types could be bigger than a smaller count of modern outputs. Depending on the UTXO pool size, it may very well not find the maximal input set within the 100,000 tries:
it doesn't take many UTXOs for the 100,000 tries to get exhausted before all relevant combinations are visited. Depending on the variance of the present values some 400+ UTXOs can suffice already, but wallets may have thousands or even tens of thousands of UTXOs.
If you insist on adding a comment perhaps use something along the lines of:
| // Check if the current feerate is expensive. If it is expensive, we minimize the size of | |
| // the result set and thus minimizing the transaction fee. If the fees are not expensive, | |
| // we maximize the result set to reduce the amount of UTXOs (fragmentation). | |
| // Check if the current feerate is higher than the long-term feerate estimate. | |
| // Since BnB prefers the input set with the lowest waste score, this results in a preference | |
| // for smaller input sets at high feerates and bigger input sets at lower feerates. |
You might want to consider adding this to the comment on the function though, as that point is not well-covered there.
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.
My comment was misleading it looks like. It's true that a true maximum or true min may not be found before the iteration limit. Also, I appreciate you pointing out that how many or how few UTXO's are targeted can depend on how much the longterm and short term fees differ. Perhaps we can just do without a comment here since the statement is self explanatory. I agree that further comment on how the selection process works is better to place on the function.
src/wallet/coinselection.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 don't think "expensive" is the best term to refer to a feerate. Creating a transaction at a high feerate is expensive, but the feerate itself doesn't cost anything. Perhaps:
| bool is_feerate_expensive = utxo_pool.at(0).fee > utxo_pool.at(0).long_term_fee; | |
| bool is_feerate_high = utxo_pool.at(0).fee > utxo_pool.at(0).long_term_fee; |
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.
My thought process was simply that we are checking if the current fee rate is expensive compared to the longterm fee rate. The reason I like expensive is it's a monetary term and "high" can pertain to many things. Since you feel strongly about using "high" I'll change it to your suggestion.
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.
A non-representative survey among my colleagues resulted in three out of three (including one native English speaker with a finance background) agreeing with me: a transaction is expensive when its feerate is high.
I therefore maintain that feerates are “high” or “low”, not “expensive” or “cheap”.
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.
It occurred to me that in the study of computation, expensive is sometimes used to describe a computation that takes a lot of resource, therefore it's not purely a finical term. Also, since we are terming the transaction as expensive, it does make sense to term the feerate as high.
953ce06 to
f4994fe
Compare
f4994fe to
81d4a2b
Compare
|
ACK 81d4a2b |
|
@achow101 friendly request to take a look. It's a small change but I don't see a reason not to include it. |
aureleoules
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.
ACK 81d4a2b
|
ACK 81d4a2b |
81d4a2b refactor: Move feerate comparison invariant outside of the loop (yancy) 365aca4 refactor: Simplify feerate comparison statement (yancy) Pull request description: This is a small nit, however I think it's more understandable to write: `utxo_pool.at(0).fee > utxo_pool.at(0).long_term_fee` vs `(utxo_pool.at(0).fee - utxo_pool.at(0).long_term_fee) > 0` ACKs for top commit: Xekyo: ACK 81d4a2b achow101: ACK 81d4a2b aureleoules: ACK 81d4a2b Tree-SHA512: 3e89377989c36716b53114fe40178261671dde5688075fab1c21ec173ac310f8c84ed6af90354d7c329176cb7262dfcaa7191fd19847d3b7147a9a10c3e31176
|
This was merged. |
81d4a2b refactor: Move feerate comparison invariant outside of the loop (yancy) 365aca4 refactor: Simplify feerate comparison statement (yancy) Pull request description: This is a small nit, however I think it's more understandable to write: `utxo_pool.at(0).fee > utxo_pool.at(0).long_term_fee` vs `(utxo_pool.at(0).fee - utxo_pool.at(0).long_term_fee) > 0` ACKs for top commit: Xekyo: ACK 81d4a2b achow101: ACK 81d4a2b aureleoules: ACK 81d4a2b Tree-SHA512: 3e89377989c36716b53114fe40178261671dde5688075fab1c21ec173ac310f8c84ed6af90354d7c329176cb7262dfcaa7191fd19847d3b7147a9a10c3e31176
This is a small nit, however I think it's more understandable to write:
utxo_pool.at(0).fee > utxo_pool.at(0).long_term_feevs
(utxo_pool.at(0).fee - utxo_pool.at(0).long_term_fee) > 0