-
Notifications
You must be signed in to change notification settings - Fork 36
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 ProvisionalProposal struct #90
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
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 starting to look beautiful. The attention to detail and effort is paying off
payjoin-cli/src/app.rs
Outdated
Ok(false) | ||
} | ||
})?; | ||
let mut provisional_payjoin: ProvisionalProposal = |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
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.
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
payjoin/src/receive/mod.rs
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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..)
payjoin/src/receive/error.rs
Outdated
Base64(bitcoin::base64::DecodeError), | ||
PsbtFinalizingError(&'static str), |
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 always returns the same kind of string, so it doesn't need to take a value at all
InternalRequestError::PsbtFinalize
would be fine
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.
fixed!
payjoin/src/receive/mod.rs
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
payjoin/tests/integration.rs
Outdated
.map(|res| res.psbt) | ||
.map_err(|e| Error::Server(e.into())) | ||
}, | ||
Some(1), |
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.
magic numbers usually mean something has gone wrong somewhere. I think we should accept a FeeRate here instead of u64
payjoin/tests/integration.rs
Outdated
Some(1), | ||
) | ||
.unwrap(); | ||
let psbt = payjoin_proposal.get_payjoin_psbt(); |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
payjoin/src/receive/mod.rs
Outdated
@@ -550,8 +551,8 @@ impl OutputsUnknown { | |||
} | |||
|
|||
/// A mutable checked proposal that the receiver may contribute inputs to to make a payjoin. | |||
|
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.
stray line
payjoin/Cargo.toml
Outdated
@@ -28,4 +28,4 @@ env_logger = "0.9.0" | |||
bitcoind = { version = "0.31.1", features = ["0_21_2"] } | |||
|
|||
[package.metadata.docs.rs] | |||
features = ["send", "receive"] |
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.
stray change here
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.
so weird, cant find this change locally
25eac79
to
e26ad17
Compare
hey @DanGould thanks for the review. |
|
payjoin-cli/src/app.rs
Outdated
None, | ||
Some(false), | ||
) | ||
.map(|res| Psbt::from_str(&res.psbt).unwrap()) |
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.
@DanGould been trying to tackle this unwrap
without success. how would you approach 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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah changing that to Error made things so easier! thanks
d025713
to
ac4f10a
Compare
payjoin/src/receive/mod.rs
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
payjoin-cli/src/app.rs
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yea I see what u mean
maybe we can change the parent function name
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 don't think it should be renamed rather not combined. It's unrelated to this PR.
payjoin/src/receive/mod.rs
Outdated
@@ -491,7 +491,8 @@ impl MaybeInputsSeen { | |||
pub fn check_no_inputs_seen_before( | |||
self, | |||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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