-
Notifications
You must be signed in to change notification settings - Fork 326
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
Introduce new bdk_coin_select
implementation
#924
Conversation
and add some tooling to enforce this
The script approach looks good to me. I tried my sub-directory idea ( |
#[derive(Debug, Clone)] | ||
pub struct CoinSelector<'a> { | ||
base_weight: u32, | ||
candidates: &'a [Candidate], |
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 it will be much more helpful if we had:
pub struct CoinSelector<'c, C> {
candidates: &'c [C],
/* ... */
}
impl<'c, C: AsRef<Candidate>> CoinSelector<'c, C> {}
This way C
can contain everything required to complete the rest of tx building.
You can even do:
impl AsRef<Candidate> for Candidate {
fn as_ref(&self) -> &Candidate {
self
}
}
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 to do this properly we'd have to make Candidate
a trait. I'd leave it like this for now until we see how tx building turns out and make it more flexible where we find pain points.
on the other hand there's no real harm to doing what you suggested now so maybe I can give it a shot.
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.
Impressive work, thanks for making it a separate crate.
We started poking at using this in Liana (see wizardsardine/liana#560), so i'm curious what's the state of this PR?
nursery/coin_select/src/lib.rs
Outdated
|
||
/// The weight of a TXOUT without the `scriptPubkey` (and script pubkey length field). | ||
/// Just the weight of the value field. | ||
pub const TXOUT_BASE_WEIGHT: u32 = 4 * core::mem::size_of::<u64>() as u32; // just the value |
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.
nit: Technically it's an i64
, but the size is the same anyways.
pub const TXOUT_BASE_WEIGHT: u32 = 4 * core::mem::size_of::<u64>() as u32; // just the value | |
pub const TXOUT_BASE_WEIGHT: u32 = 4 * core::mem::size_of::<i64>() as u32; // just the value |
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 actually didn't know that bitcoin core treated these at i64
internally. I think from a consensus perspective either is correct? i.e. if we changed the definition to be u64
the protocol doesn't change at all.
|
||
pub fn new_tr_keyspend() -> Self { | ||
Self { | ||
weight: TXOUT_BASE_WEIGHT + TR_SPK_WEIGHT, |
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.
You are missing the scriptPubKey length (and therefore 4 weight units)?
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.
Yep well spotted. I added a 1 byte spk length to TXOUT_BASE_WEIGHT
. All this weight stuff was added on a whim while I was trying to use this and is not very well tested. I'm not sure if it's worth having since you should probably get the numbers from rust bitcoin and rust miniscript which do a better job of telling you this stuff now.
it's inconsistent to not include it and I confused myself.
Hey @darosior. Thanks for taking a stab at using this that was really helpful. I think the status is waiting for @evanlinjin to help me finish it. Here's a list of TODOs:
I wonder if @evanlinjin can put this as priority after #1002. |
fn default() -> Self { | ||
Self { | ||
feerate: FeeRate::default_min_relay_fee(), | ||
min_fee: 0, // TODO figure out what the actual network rule is for this |
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.
There is no explicit standardness limit for absolute fee (aside from replacement rules). It's implicitly bounded by the minimum feerate rule since transactions can't be empty.
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.
Yep. I'll leave removing all these TODO
s around the code for @evanlinjin who I think created most of them. Things that need addressing should that when this is merged it's TODO free.
/// The minimum fee the selection must have | ||
pub min_fee: 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.
Since this only needs be specified for RBF, how about making it Optional
?
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 would mean Some(0)
and None
have the same semantics which I think outweighs the better intuition provided by having it as an Option
. Code docs should for sure explain that the only time you need to use this is to comply with RBF rules.
let witness_header_extra_weight = self | ||
.selected() | ||
.find(|(_, wv)| wv.is_segwit) | ||
.map(|_| 2) | ||
.unwrap_or(0); | ||
let vin_count_varint_extra_weight = { | ||
let input_count = self.selected().map(|(_, wv)| wv.input_count).sum::<usize>(); | ||
(varint_size(input_count) - 1) * 4 | ||
}; | ||
|
||
self.selected_weight() + witness_header_extra_weight + vin_count_varint_extra_weight |
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.
For transaction that have at least one Segwit input, you also need to consider the added witness stack size for all inputs that are not spending from a Segwit Script. This is always 1 per legacy input since for them this stack is always empty.
More information about this here: https://github.com/bitcoin/bitcoin/pull/26567/files#diff-6e06b309cd494ef5da4e78aa0929a980767edd12342137f268b9219167064d13R62-R69.
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. I fixed this and added some tests 4e0c907
Interestingly doing this properly broke some prop tests. The prop tester would find edge cases where the branch and bound algorithm found what looked like a good solution with lots of legacy inputs but then it had to add one segwit input and made the weight blow up for the existing inputs. This could probably be fixed but I don't think it's worth the effort and code complexity just to have slightly better selections when you mix legacy and segwit for waste metric etc.
see: bitcoindevkit#924 (comment) Was a PITA since branch and bound is hard to do with this interference between segiwt and legacy weights. It would find solutions that looked good until you add the final input which was segwit and then the solution would be suboptimal and fail the test.
So #1002 was replaced by #1034 which was merged. @evanlinjin do you still have this as a priority? |
@darosior thanks for asking. I'm literally planning to start work on this tomorrow. This will be a priority. I should have a rough timeline of when I think I can get this completed by the end of the week. Also note that I also need to look into a couple of bugs as well. Will keep you posted. |
That's great news! Thanks, please do. |
see: bitcoindevkit#924 (comment) Was a PITA since branch and bound is hard to do with this interference between segiwt and legacy weights. It would find solutions that looked good until you add the final input which was segwit and then the solution would be suboptimal and fail the test.
see: bitcoindevkit#924 (comment) Was a PITA since branch and bound is hard to do with this interference between segiwt and legacy weights. It would find solutions that looked good until you add the final input which was segwit and then the solution would be suboptimal and fail the test.
see: bitcoindevkit#924 (comment) Was a PITA since branch and bound is hard to do with this interference between segiwt and legacy weights. It would find solutions that looked good until you add the final input which was segwit and then the solution would be suboptimal and fail the test.
see: bitcoindevkit#924 (comment) Was a PITA since branch and bound is hard to do with this interference between segiwt and legacy weights. It would find solutions that looked good until you add the final input which was segwit and then the solution would be suboptimal and fail the test.
This is replaced by #1072. |
see: bitcoindevkit#924 (comment) Was a PITA since branch and bound is hard to do with this interference between segiwt and legacy weights. It would find solutions that looked good until you add the final input which was segwit and then the solution would be suboptimal and fail the test.
see: bitcoindevkit#924 (comment) Was a PITA since branch and bound is hard to do with this interference between segiwt and legacy weights. It would find solutions that looked good until you add the final input which was segwit and then the solution would be suboptimal and fail the test.
see: bitcoindevkit#924 (comment) Was a PITA since branch and bound is hard to do with this interference between segiwt and legacy weights. It would find solutions that looked good until you add the final input which was segwit and then the solution would be suboptimal and fail the test.
see: bitcoindevkit#924 (comment) Was a PITA since branch and bound is hard to do with this interference between segiwt and legacy weights. It would find solutions that looked good until you add the final input which was segwit and then the solution would be suboptimal and fail the test.
see: bitcoindevkit#924 (comment) Was a PITA since branch and bound is hard to do with this interference between segiwt and legacy weights. It would find solutions that looked good until you add the final input which was segwit and then the solution would be suboptimal and fail the test.
see: bitcoindevkit#924 (comment) Was a PITA since branch and bound is hard to do with this interference between segiwt and legacy weights. It would find solutions that looked good until you add the final input which was segwit and then the solution would be suboptimal and fail the test.
see: bitcoindevkit#924 (comment) Was a PITA since branch and bound is hard to do with this interference between segiwt and legacy weights. It would find solutions that looked good until you add the final input which was segwit and then the solution would be suboptimal and fail the test.
see: bitcoindevkit#924 (comment) Was a PITA since branch and bound is hard to do with this interference between segiwt and legacy weights. It would find solutions that looked good until you add the final input which was segwit and then the solution would be suboptimal and fail the test.
This is porting over LLFourn/bdk_core_staging#46
I made it compile on 1.48.0 and added a script that we can use in CI to check that all the crates that should be able to compile on 1.48.0 can.
TODO:
I've had a request to do a release of the separately before it is used in
bdk
itself so that other projects can try it out.cc @darosior