add ProvisionalProposal struct - #90
Conversation
|
@DanGould actually maybe it would be good to think about the signature of pub fn finalize_proposal(&mut self, w: impl wallet trait, min_feerate_sat_per_vb: Option<u64>) |
|
🏋️♂️🤙 It might make more sense to pass a closure as a parameter than a trait we'd have to define and implement downstream. This is how check methods are done earlier in the module. Take a look at those and let me know which parts you need more clarity on |
fb02857 to
579bbda
Compare
DanGould
left a comment
There was a problem hiding this comment.
This is starting to look beautiful. The attention to detail and effort is paying off
| Ok(false) | ||
| } | ||
| })?; | ||
| let mut provisional_payjoin: ProvisionalProposal = |
There was a problem hiding this comment.
is this explicit type required? Seems redundant since you're using such care with naming.
a nit would be to make check4's result proposal or provisional_payjoin instead of becoming a payjoin and turning into a provisional_payjoin
There was a problem hiding this comment.
not really, removed.
created a new issue for check4 change here #96, will handle that in a new pr as i dont wanna add more changes to this pr
There was a problem hiding this comment.
I'm a bit confused why you chose to make this a new issue, it seems this PR introduces the issue, no? The second part of the comment was just about naming.
b00f73d to
956025e
Compare
|
@DanGould thanks so much for the input. I made the required changes, feel free to review |
DanGould
left a comment
There was a problem hiding this comment.
Overall this is looking like a much less leaky way to handle typestate. I'm impressed.
I left a few more questions about the interface design
| wallet_process_psbt: impl Fn(&Psbt) -> Result<std::string::String, Error>, | ||
| min_feerate_sat_per_vb: Option<u64>, |
There was a problem hiding this comment.
Might we want to return Result<Psbt, Error> to validate the result and min_feerate: Option<FeeRate> for parameters?
There was a problem hiding this comment.
we currently validate the result with Psbt::from_str, if we change the signature we basically leave the validation(or the conversion part from str to psbt) to the user.
when u make a call to finalize_psbt it will look something similar to this:
.finalize_proposal(
|psbt: &Psbt| {
Ok(receiver
.wallet_process_psbt(
&bitcoin::base64::encode(psbt.serialize()),
None,
None,
Some(false),
)
.map(|res: WalletProcessPsbtResult| {
let psbt = Psbt::from_str(&res.psbt).unwrap(); //convert from str to psbt
return psbt;
})
.unwrap())
},
Some(bitcoin::FeeRate::MIN),
)and finalize_proposal would look like this
pub fn finalize_proposal(
mut self,
wallet_process_psbt: impl Fn(&Psbt) -> Result<Psbt, Error>,
min_feerate_sat_per_vb: Option<FeeRate>,
) -> Result<PayjoinProposal, RequestError> {
let psbt = self.apply_fee(min_feerate_sat_per_vb)?;
let psbt = wallet_process_psbt(psbt).map_err(|_| {
log::error!("wallet_process_psbt error");
InternalRequestError::PsbtFinalizingError
})?;
let payjoin_proposal = self.prepare_psbt(psbt).map_err(RequestError::from)?;
Ok(payjoin_proposal)
}I like it. its nicer and makes a bit more sense because the function implementation is the user responsibility so it makes sense that they send the EXACT expected type. we can also do some checks after we get the psbt and do a set of validations from pj perspective (maybe differentiating between psbt version(s) or so..)
| Psbt(bitcoin::psbt::Error), | ||
| PsbtParseError(bitcoin::psbt::PsbtParseError), | ||
| Base64(bitcoin::base64::DecodeError), | ||
| PsbtFinalizingError(&'static str), |
There was a problem hiding this comment.
This always returns the same kind of string, so it doesn't need to take a value at all
InternalRequestError::PsbtFinalize would be fine
| let payjoin_proposal_psbt = match wallet_process_psbt(payjoin_proposal_psbt) { | ||
| Ok(p) => p, | ||
| Err(_) => { | ||
| return Err(RequestError::from(InternalRequestError::PsbtFinalizingError( | ||
| "wallet_process_psbt failed", | ||
| ))); | ||
| } | ||
| }; |
There was a problem hiding this comment.
| let payjoin_proposal_psbt = match wallet_process_psbt(payjoin_proposal_psbt) { | |
| Ok(p) => p, | |
| Err(_) => { | |
| return Err(RequestError::from(InternalRequestError::PsbtFinalizingError( | |
| "wallet_process_psbt failed", | |
| ))); | |
| } | |
| }; | |
| let psbt = wallet_process_psbt(payjoin_proposal_psbt) | |
| .map_err(|_| InternalRequestError::PsbtFinalizingError.into())?; |
I think it's fine to call it plain psbt in this context, we know it's a payjoin_proposal method function.
| .map(|res| res.psbt) | ||
| .map_err(|e| Error::Server(e.into())) | ||
| }, | ||
| Some(1), |
There was a problem hiding this comment.
magic numbers usually mean something has gone wrong somewhere. I think we should accept a FeeRate here instead of u64
| Some(1), | ||
| ) | ||
| .unwrap(); | ||
| let psbt = payjoin_proposal.get_payjoin_psbt(); |
There was a problem hiding this comment.
Is there a good reason to return a PayjoinProposal to hang onto instead of just the psbt straight away? there might be.
There was a problem hiding this comment.
yea we might need to return other stuff in the future so In that case it wouldnt be a breaking change
There was a problem hiding this comment.
That makes sense. It might make more sense to name this psbt() since we know it's in the PayjoinProposal and get_ is associated with returning from an index in rust by convention https://rust-lang.github.io/api-guidelines/naming.html#getter-names-follow-rust-convention-c-getter
| } | ||
|
|
||
| /// A mutable checked proposal that the receiver may contribute inputs to to make a payjoin. | ||
|
|
| bitcoind = { version = "0.31.1", features = ["0_21_2"] } | ||
|
|
||
| [package.metadata.docs.rs] | ||
| features = ["send", "receive"] No newline at end of file |
There was a problem hiding this comment.
so weird, cant find this change locally
25eac79 to
e26ad17
Compare
|
hey @DanGould thanks for the review. |
|
|
| None, | ||
| Some(false), | ||
| ) | ||
| .map(|res| Psbt::from_str(&res.psbt).unwrap()) |
There was a problem hiding this comment.
@DanGould been trying to tackle this unwrap without success. how would you approach it?
There was a problem hiding this comment.
This article gives a few different options for returning errors from iterators: https://doc.rust-lang.org/rust-by-example/error/iter_result.html
I think the main issue is that it it doesn't make sense for finalize_proposal to return a RequestError, because the request has already been checked by this point. The error is more of a receiver::Error::Server and could be returned inside that Box.
There was a problem hiding this comment.
ah changing that to Error made things so easier! thanks
d025713 to
ac4f10a
Compare
| min_feerate_sat_per_vb: Option<FeeRate>, | ||
| ) -> Result<PayjoinProposal, RequestError> { | ||
| let psbt = self.apply_fee(min_feerate_sat_per_vb)?; | ||
| let psbt = wallet_process_psbt(psbt).map_err(|_| { |
There was a problem hiding this comment.
better to handle the specific error instead of discarding it by returning Error from finalize_proposal instead of RequestError. Then we don't have to convert after prepare_psbt either
| let payjoin = proposal.check_no_inputs_seen_before(|input| { | ||
| Ok(!self.insert_input_seen_before(*input).map_err(|e| Error::Server(e.into()))?) | ||
| })?; | ||
| let mut provisional_payjoin = proposal.check_no_inputs_seen_before( | ||
| |input| { | ||
| Ok(!self.insert_input_seen_before(*input).map_err(|e| Error::Server(e.into()))?) | ||
| }, | ||
| |output_script| { | ||
| if let Ok(address) = bitcoin::Address::from_script(output_script, network) { | ||
| self.bitcoind | ||
| .get_address_info(&address) | ||
| .map(|info| info.is_mine.unwrap_or(false)) | ||
| .map_err(|e| Error::Server(e.into())) | ||
| } else { | ||
| Ok(false) | ||
| } | ||
| }, | ||
| )?; |
There was a problem hiding this comment.
identify_receiver_outputs does not seem directly related to the check, so I think we lose some semantics by merging them into one step. Either way it's unrelated to the ProvisionalProposal struct and would be better suited to its own PR
There was a problem hiding this comment.
yea I see what u mean
maybe we can change the parent function name
There was a problem hiding this comment.
I don't think it should be renamed rather not combined. It's unrelated to this PR.
| is_known: impl Fn(&OutPoint) -> Result<bool, Error>, | ||
| ) -> Result<OutputsUnknown, Error> { | ||
| is_receiver_output: impl Fn(&Script) -> Result<bool, Error>, | ||
| ) -> Result<ProvisionalProposal, Error> { |
There was a problem hiding this comment.
I can see why these might be merged, but perhaps the checks should return RequestError (since if they fail, the request is bad) while the closure returns Error, since that's a problem with the server implementation. Either way this is separate from the ProvisionalProposal change
aaf5b99 to
84e8ac9
Compare
|
I'm not sure my comments re: return type errors and the merging of 2 functions into one have been addressed. Addressing them both will help this API deliver on its purpose. I think And for the most detail oriented changes, the commit messages would benefit from these semantics so we have a neat history to read later down the line https://cbea.ms/git-commit/ |
84e8ac9 to
88d37e7
Compare
64602c3 to
209c153
Compare
CI is failing because flate2 released new version 1.0.27 that is not passing rustc 1.57. Reference: rust-lang/flate2-rs#370
Motivaiton is to create a struct that is used until we finalize the proposal and return PayjoinProposal so we dont leak data out of PayjoinProposal. Changed apply_fee and prepare_psbt to private functions and they are now invoked from finalize_proposal
17da43c to
be353ab
Compare
|
I've re-ordered the commits so that each one passes tests and changed the apply_fee param to @jbesraa you've made this receiver interface 10x cleaner at least ⭐️ |
resolves #73