Introduce new bdk_coin_select implementation#924
Introduce new bdk_coin_select implementation#924LLFourn wants to merge 8 commits intobitcoindevkit:masterfrom
bdk_coin_select implementation#924Conversation
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.
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.
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.
darosior
left a comment
There was a problem hiding this comment.
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?
|
|
||
| /// 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.
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.
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.
You are missing the scriptPubKey length (and therefore 4 weight units)?
There was a problem hiding this comment.
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.
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.
Yep. I'll leave removing all these TODOs 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.
Since this only needs be specified for RBF, how about making it Optional?
There was a problem hiding this comment.
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.
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.
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
bdkitself so that other projects can try it out.cc @darosior