-
Notifications
You must be signed in to change notification settings - Fork 368
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
Add waste metric for coin selection #558
Add waste metric for coin selection #558
Conversation
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
Tests are failing, can you please update the PR? :) |
Yes, you are right! I will try to rebase my branch in top of master to get all last changes (including #515) and then apply the correction. Let me know if you have any doubt about this. |
544fc21
to
082f1b8
Compare
It seems that the only failing test remaining are related to missing documentation. I don't know if you want to discuss the implementation further, but I'm willing to add documentation to pass the test and then iterate again, in case something is missing. |
I'll look into the code ASAP, sorry for the waiting :) |
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, the approach could be improved:
get_waste
should be dropped altogether, it's not really useful to cache the Waste (you just want to calculate it once at the end of the coin selection)- instead of having a
pub fn calculate_waste
, better to go with:
struct Waste {
value: i64,
}
impl Waste {
pub fn calculate (...) -> Result<Waste, Error> {...the whole calcualte_waste function is pasted here}
}
This is just a first look at it, I still haven't tested
Thanks for the review! Let me explain myself. I made a few assumptioms before
|
Some things that I've experienced while trying to make
All options seem cumbersome IMHO. I reviewed the
Although all of that it's still a work in progress, because the values of the waste metric are hardcoded for |
Yes, because custom coin selection algorithms should also be able to calculate waste
I don't really understand this, if you want you could also implement I haven't reviewed the whole code yet, but I did spend some time trying to remove some useless cloning in the code: I pushed my code here, feel free to take the two top commits and apply them to your PR. |
b6cd728
to
d2bf84a
Compare
I didn't know about
Thanks for this! There are things that I'm still learning about Rust and made some clunky decisions. |
23f6e7c
to
6564c96
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.
This is looking really good! 😄
A couple of comments, small stuff. I still need to review tests 😭 Sorry!
Also, can you please squash commits? I'd say you just squash in one commit, putting Alekos and Ben as co-authors.
I don't know if it's better to merge with all the TODOs
around (for calculating the waste and the cost of change) or not. @afilini wdyt?
dba785e
to
a6ad7dd
Compare
Do you think that this is ready to start documenting it? |
Hi, please rebase to pickup changes in #596. Thanks! |
Once it compiles, I think it's ready 😄 |
5666434
to
c5fa6a0
Compare
I added some |
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.
Cool that you're working on this. I really like your description of the waste metric as the composite of a "timing cost" plus a "creation cost". I have a few questions and comments, I hope not too many are just misunderstandings owed to my low Rust reading proficiency.
bceb683
to
89f30c5
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.
Read it over quickly again. Good changes!
I have couple more optional nits which you can feel free to ignore, but I think there may have slipped a sign error into the tests.
89f30c5
to
69f7e5a
Compare
I don't feel like I'm familiar enough to "ACK" here, but my prior comments appear to have been all addressed. So, I guess "Concept ACK"? :) |
src/wallet/coin_selection.rs
Outdated
@@ -157,6 +260,11 @@ pub trait CoinSelectionAlgorithm<D: Database>: std::fmt::Debug { | |||
/// - `amount_needed`: the amount in satoshi to select | |||
/// - `fee_amount`: the amount of fees in satoshi already accumulated from adding outputs and | |||
/// the transaction's header | |||
/// - `_cost_of_change`: the cost of creating change and spending it in the future | |||
/// if there is change, it must be a positive number | |||
/// must be None if there is no change |
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.
The problem here is that you don't know if there's going to be a change or not before calling the coin_selection
method...
In fact, at the moment we don't even calculate if we're going to have the change in coin_select
! We do it later:
Lines 768 to 781 in fbd98b4
let mut drain_output = { | |
let script_pubkey = match params.drain_to { | |
Some(ref drain_recipient) => drain_recipient.clone(), | |
None => self | |
.get_internal_address(AddressIndex::New)? | |
.address | |
.script_pubkey(), | |
}; | |
TxOut { | |
script_pubkey, | |
value: 0, | |
} | |
}; |
We obviously can't calculate the waste properly before knowing if there's a change or not, so we should move the change creation inside the coin_select method. See #147
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.
To clarify: here, we should just take a u64
(we can't know if we're going to have a change or not before the coin_select
).
When we calculate the waste we should decide between the cost_of_change value or None based on whether we have the change or not
src/wallet/coin_selection.rs
Outdated
/// - `fee_rate`: fee rate to use | ||
pub fn calculate( | ||
selected: &[WeightedUtxo], | ||
cost_of_change: Option<u64>, |
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.
Really picky nit: I'd stay consistent between change_cost
and cost_of_change
, as far as I understand they're the same thing, so we should either use one name or the other in the code (whichever you find more clear)
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.
Btw, probably the cost of change should be calculated inside the Waste::calculate
and not outside.
change_cost = current_fee_rate * change_output_size + long_term_feerate * change_spend_size
This means we need the following:
current_fee_rate
-> we have it already
change_output_size
-> we need it to have it as a param
long_term_feerate
-> we have it, it's LONG_TERM_FEE_RATE
change_spend_size
-> we need it to have it as a param
Which means we should either add two new parameters to the function (maybe it's better to merge those in a struct)
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.
As of exactly how to calculate the change_output_size
and the change_spend_size
, I'm figuring it out, and I'll get back to you
Co-authored-by: csralvall <[email protected]> Co-authored-by: afilini <[email protected]>
69f7e5a
to
e2fba7a
Compare
@@ -749,13 +749,22 @@ where | |||
params.bumping_fee.is_some(), // we mandate confirmed transactions if we're bumping the fee | |||
)?; | |||
|
|||
let weighted_drain_txout = WeightedTxOut { |
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 needs to be updated? Do you need help figuring out how to do 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.
No, or yes :) Changes with this may collide with the solution for #147 . I don't know if I should address that in this same PR or not.
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 think you should fork from bdk master and try to implement #147 first (you can of course go back and look at this code if you feel like there are problems that you solved here already). In this way you'll come up with a solution that doesn't depend on the waste metric.
e2fba7a
to
3fa8a47
Compare
I realized that I should return the fee amount related to the change output pub enum Excess {
NoChange(Error::InsufficientFunds),
// Change(change_output, change_output_fee)
Change(TxOut, u64),
} Then the signature of pub fn calculate(
selected: &[WeightedUtxo],
weighted_drain_output: WeightedTxOut,
target: u64,
fee_rate: FeeRate,
) -> Result<(Waste, Excess), Error> And we can decide later if we join the change output with the other outputs pub struct CoinSelectionResult {
/// List of outputs selected for use as inputs
pub selected: Vec<Utxo>,
/// Total fee amount in satoshi
pub fee_amount: u64,
/// Waste value of current coin selection
pub waste: Waste,
/// Remaining amount after coin_select
pub excess: Excess,
} Through the use of |
Also, returning the total resulting fee with the coin selection result will make it easier to handle spending unconfirmed inputs when the parent transaction has a lower feerate than what you intend for the new transaction you're building to have. I mean, if you e.g. want to automatically bump the parent transaction to the same feerate so you achieve the intended feerate, and if you want to account for that additional in the waste metric later. |
I'm not sure that I've understood what you said correctly. You were thinking in
The original transaction that generated UTXO_B is I'm unsure because As a side note, I'm planning to propose a modification for the fee amount |
Written like this, |
Yeah, if you spend an unconfirmed input from a transaction that paid a lower feerate than the new transaction you're creating, you need to add more fees to the new transaction so that the transaction package in total will have the intended feerate. I.e. in your example: Tx_A— vsize: 200 vB, fee: 1000 sats, feerate: 5 sat/vB ↦ UTXO_A If Tx_C is spending UTXO_A and UTXO_B and has a vsize of 200 vB, how much fees should it pay to reach an effective feerate of 5 sat/vB? |
I realized that I can't do that. The idea came up because I was to tied up to the code in |
Hey, we are in the process of releasing BDK 1.0, which will under the hood work quite differently from the current BDK. For this reason, I'm closing all the PRs that don't really apply anymore. If you think this is a mistake, feel free to rebase on master and re-open! (Specifically, the coin selection is being rewritten from scratch) |
Description
Waste is a metric introduced by the BnB algorithm as part of its bounding
procedure. Later, it was included as a high level function to use in comparison
of different coin selection algorithms in bitcoin/bitcoin#22009.
This implementations considers waste as the sum of two values:
Timing cost is the cost associated to the current fee rate and some long
term fee rate used as a treshold to consolidate UTXOs.
Timing cost can be negative if the
current_fee_rate
is cheaper than thelong_term_fee_rate
, or zero if they are equal.The name of this cost is derived from the action of making bets against some
kind of market (in this case the fee market) to gain profits using the
interpretation of the future behaviour of that market based on a partial
information game.
Creation cost is the cost associated to the surplus of coins besides the
transaction amount and transaction fees. It can happen in the form of a change
output or in the form of excess fees paid to the miner.
Change cost is derived from the cost of adding the extra output to the
transaction and spending that output in the future.
Excess happens when there is not change, and the surplus of coins is spend as
part of the fees to the miner:
Where target is the desired amount to send.
Creation cost can be zero if there is a perfect match as result of the coin
selection algorithm.
So, waste can be zero or negative if the creation cost is zero and the timing
cost is less than or equal to zero
Later this can be used in the wallet to select the most optimal coin selection
algorithm.
This PR continues the work initiated by @benthecarman in #435 and address some
of the features described in #483.
The current PR is based mainly in the following sources:
Notes to the reviewers
While running
cargo clippy
some warnings may arise due to the lack ofdocumentation of the new functions, those will be resolved once the code
changes are finished. The same applies for the
CHANGELOG.md
file.Checklists
All Submissions:
cargo fmt
andcargo clippy
before committingNew Features:
CHANGELOG.md