Use native rust-bitcoin methods for weight calculations#360
Use native rust-bitcoin methods for weight calculations#360DanGould merged 8 commits intopayjoin:masterfrom
Conversation
|
pretty massive delete! eager to follow |
|
I'm partial to allow mixed input types, and that opinion appears to be consensus. In addition, allow by default is more permissive and should have fewer failed attempts, I'm not sure I'd even want to expose the option unless it were strictly necessary for a downstream usecase if the logic weren't already in the codebase for v1. |
20c1273 to
f580454
Compare
Use upstream fee and weight methods from the `psbt` crate instead of manually calculating everything.
f9ff6ad to
500894a
Compare
500894a to
08f3d9e
Compare
payjoin/src/input_type.rs
Outdated
| // witness: <signature> <pubkey> = 72, 33 bytes | ||
| // https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#p2wpkh-nested-in-bip16-p2sh | ||
| InputWeightPrediction::new(23, &[72, 33]), | ||
| SegWitV0 { ty: SegWitV0Type::Script, nested: _ } => unimplemented!(), |
There was a problem hiding this comment.
The suggestion for P2SH-P2WPKH listed as 91 in the fee output section of BIP 78, is this estimate removed because we can't tell wether the nested script is P2WPKH or not?
There was a problem hiding this comment.
This clause applies to P2SH-P2WSH. The suggestion for P2SH-P2WPKH is implemented just above it with InputWeightPrediction::new(23, &[72, 33]) which corresponds to the suggested 91 vsize after adding the txid, index, and sequence bytes.
payjoin/src/psbt.rs
Outdated
| // input script: 0x160014{20-byte-key-hash} = 23 bytes | ||
| // witness: <signature> <pubkey> = 72, 33 bytes | ||
| // https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#p2wpkh-nested-in-bip16-p2sh | ||
| InputWeightPrediction::new(23, &[72, 33]), |
There was a problem hiding this comment.
Seems like this is a constant value shared with the input_type match block. Perhaps it should be declared as a static value in our lib or even upstreamed as const into rust-bitcoin.
There was a problem hiding this comment.
Opened a PR in rust-bitcoin to upstream it: rust-bitcoin/rust-bitcoin#3443
payjoin/src/send/mod.rs
Outdated
| let input_pairs = self.psbt.input_pairs().collect::<Vec<InputPair>>(); | ||
|
|
||
| let first_input_pair = | ||
| input_pairs.first().ok_or(InternalCreateRequestError::NoInputs)?; | ||
| let input_weight = if input_pairs | ||
| .iter() | ||
| .try_fold(true, |_, input_pair| -> Result<bool, crate::psbt::AddressTypeError> { | ||
| Ok(input_pair.address_type()? == first_input_pair.address_type()?) | ||
| }) |
There was a problem hiding this comment.
Two things here
- I believe the
try_foldimplementation is incorrect unless the accumulator is 'folded' into the condition. In the current implementation, if all of the addresses were mismatches except for the first and last, thentrueis still returned. Folding in theboolchecked against each other using the accumulator. - I think there's a slight performance improvement by using
next rather thanfirst.collect` the input_pairs, since once the first_input_pair is retrieved the iterator can proceed rather than iterating from the beginning
1 fixes correctness 2 is just a performance improvement.
| let input_pairs = self.psbt.input_pairs().collect::<Vec<InputPair>>(); | |
| let first_input_pair = | |
| input_pairs.first().ok_or(InternalCreateRequestError::NoInputs)?; | |
| let input_weight = if input_pairs | |
| .iter() | |
| .try_fold(true, |_, input_pair| -> Result<bool, crate::psbt::AddressTypeError> { | |
| Ok(input_pair.address_type()? == first_input_pair.address_type()?) | |
| }) | |
| let mut input_pairs = self.psbt.input_pairs().collect::<Vec<InputPair>>().into_iter(); | |
| let first_input_pair = | |
| input_pairs.next().ok_or(InternalCreateRequestError::NoInputs)?; | |
| let input_weight = if input_pairs | |
| .try_fold(true, |acc, input_pair| -> Result<bool, crate::psbt::AddressTypeError> { | |
| Ok(acc && input_pair.address_type()? == first_input_pair.address_type()?) | |
| }) |
There was a problem hiding this comment.
Good catch. I rewrote this as a simple while loop which breaks if an input is not the same type as the first one. That seems easier to reason about.
There was a problem hiding this comment.
The new while loop is way easier to reason about
I can't recall where the rule to // use cheapest default if mixed input types` (P2TR) estimation method comes from when mixed input types are present. Wouldn't it make more sense to use the cheapest of the scripts proposed? As long as we make it easy for another compatible implementation to come along, I don't think it so much matters either way.
08f3d9e to
ddcaa05
Compare
|
@DanGould Summary of changes since your last review:
|
I haven't checked the PSBT values in the known-weight tests, so I'm on that tomorrow. I think that's the last substantive check to make. |
252381a to
2cef4ea
Compare
There was a problem hiding this comment.
I verified the additional input weight comes from scripts of the stated address types. The UTXO locking scripts are as follows and seem to be the the specified AddressType
p2pkh_proposaladdsvout: 0having p2pkh scriptOP_DUP OP_HASH160 OP_PUSHBYTES_20 3d786edc8e83f51d30529bd3bef526f9518d70db OP_EQUALVERIFY OP_CHECKSIGnested_p2wpkh_proposaladdsvout: 0having p2sh scriptOP_HASH160 OP_PUSHBYTES_20 c7ff991c104167ee84fd44b537639eec80c8b578 OP_EQUALp2wpkh_proposaladdsvout: 0having p2wpkh scriptOP_0 OP_PUSHBYTES_20 916d91f1d3b77cb510331a37a0858e3356b69091p2tr_proposaladdsvout: 0having p2tr scriptOP_PUSHNUM_1 OP_PUSHBYTES_32 9f6db5fe14725bcc2ef578414bb0c8c3a2e3096eef2881e47478da0dc4c33d0b
The only nits I'd ask for are for the warnings of unused variables to be removed from
- secp256k1 dependencies in send/mod.rs
let sequence = original_psbt.unsigned_tx.input[0].sequence;in send/mod.rs
run cargo clippy and the warnings should become apparent, as well as using a for loop in send/mod.rs:136 and dropping the & reference on [72,33] at psbt.rs:201.
and for the input_weight_calculations test name to be additional_input_matches_known_weight or similar boolean statement
The only field that's necessary to keep is contributed_fee, which is now returned as standalone bitcoin::Amount in check_outputs()
…tion Instead of hardcoded values, use InputWeightPredictions to calculate expected input weight.
bitcoin::AddressType can act as a substitute for InputType, and we can use that to compute expected input weights. InputPair contains all the context necessary to derive those properties, so input_type.rs is obsolete.
Code cleanup: deletes input_type.rs, output_type.rs and weight.rs which are no longer needed.
2cef4ea to
77e567c
Compare
|
Thanks - fixed the clippy warnings and test name. |
payjoin/src/receive/mod.rs
Outdated
| .scan(&mut err, |err, input| match Ok(input.address_type()) { | ||
| Ok(address_type) => Some(address_type), |
There was a problem hiding this comment.
Seems like this will never reach the Err(e) branch since match is directly called on an Ok value?
There was a problem hiding this comment.
Yes - this was a temporary workaround before I had address_type() return a Result. It gets updated in 6938b70#diff-5fba6fc39c59b07e5980d87da92537138068364d192a2b63eee5e57af01a07bb
payjoin/src/send/mod.rs
Outdated
| .original_psbt | ||
| .input_pairs() | ||
| .next() | ||
| .expect("original PSBT should have an input"); |
There was a problem hiding this comment.
Can we handle this error? Or you think it's so extremely unlikely it doesn't matter?
There was a problem hiding this comment.
It would mean the original PSBT had no inputs, which seems improbable at this stage since the receiver would've accepted it and responded with a payjoin proposal. I could add a proper error type for it though.
There was a problem hiding this comment.
Added proper handling for this error in 68fc635
Input type and sequence can be computed at little cost from the original PSBT, which is already included in RequestContext/ContextV1. Remove those fields from those structs and compute them when needed.
I used payjoin-cli with `RUST_LOG=trace` to generate and obtain the original and payjoin PSBT values, and a new bitcoin-cli wallet for each address type.
77e567c to
3bb3df3
Compare
See #353
This deletes the custom
weight.rstraits andinput_type.rshelpers in favor of out-of-the-box rust-bitcoin methods. It also removes the unusedoutput_type.rs.A question that may be relevant to this PR: do we want to commit to allowing mixed input types (despite it being a violation of the BIP78 spec) to better enable batching use cases? Maybe we could introduce a
allow_mixed_input_typesparameter that can be set by the sender and defaults to false if not set?