-
Notifications
You must be signed in to change notification settings - Fork 76
Abstract common sender functionality, not heirarchy #884
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
Conversation
The PsbtContext is used in both v1 and v2 and can be shared.
The distinction beetween 1s1r async payjoin and ns1r is that the sender checks the proposal before the receiver signs in the first round of communication, so the receiver inputs won't yet be finalized. Therefore, in order to share functionality, the receiver input finalized check needs to be triggered by a condition. This does that.
The Version type exists for this less-prone-to-error purpose.
Pull Request Test Coverage Report for Build 16378664856Details
💛 - Coveralls |
|
Seeking concept N/ACK @nothingmuch |
nothingmuch
left a comment
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.
cACK
This seems like the right approach to me.
I would further suggest separating out the URI parsing, for the sender side and returns an enum with variants for the different versions. This enum should be non-exhaustive, and should map easily to the (feature enabled) senderbuilders in a statically typed way.
This would also address the outstanding comment in the payjoin-cli impl that suggests that the URI should not be stored in an event but should precede the sender state machine altogether, that makes intuitive sense to me.
| ) -> Url { | ||
| let mut url = endpoint; | ||
| url.query_pairs_mut().append_pair("v", version); | ||
| url.query_pairs_mut().append_pair("v", &version.to_string()); |
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, i think we can have &'static strs for this, implementing an as_str() method and then provide an Into<&str> impl that calls that
Introduce PsbtContextBuilder which has no ouptut_substitution field, allowing parent consumer SenderBuilders to handle output_substitution and the creation of types relevant to their independent state machines. Move common v1 logic used in both v1 and v2 flows to the send module.
949a58e to
8ceccaa
Compare
|
rebased so CI passes. Following up with the an enumerated, versioned Uri/SenderBuilder/Sender[] approach. |
| /// Helper function that takes a V1 sender build result and wraps it in a V2 Sender, | ||
| /// returning the appropriate state transition. |
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.
The comment here is incorrect since this hierarchy is removed, but this function going to be completely replaced in a follow-up this week.
nothingmuch
left a comment
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.
Sorry this took me so long to finish reviewing.
FWIW I would have an easier time reviewing incrementally if the commits were a bit smaller, reviewing commit by commit was still a bit tricky since kinda boilerplate-y changes like moving things around were intermixed with more semantic changes like mut self -> self or the output substitution changes, so I felt like I had to reread a lot of stuff to double check that I didn't miss anything, and even then my confidence is not super high that inamongst the boilerplate-y changes I didn't miss anything important
| pub fn always_disable_output_substitution(self) -> Self { | ||
| Self { output_substitution: OutputSubstitution::Disabled, ..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.
in principle, why is this method still needed?
i don't agree that it:
may prevent the receiver from doing advanced operations such as opening LN channels
maybe there's other situations but for the LN case, including concretely with CLN's PSBT based RPC as an example, it's arguably more appropriate for the receiver to initiate the LN side of things after modifying the PSBT and introducing the channel funding output, to obtain the backout tx etc and then respond to the PJ sender
i can only come up with somewhat contrived scenarios that would actually require this, more situations may come up with covenant support in the future where addresses and amounts could be more tightly coupled as a matter of course but for now i think we could consider literally always allowing substitution
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 if the sender disallows funds to go to a channel funding output, not the one in the bip21, then the payjoin would fail.
I don't believe anyone downstream is disabling output substitution everywhere. I think this is vestigial from kix's v1 implementation and agree it can be removed.
| /// Construct serialized V1 Request and Context from a Payjoin Proposal | ||
| pub(crate) fn create_v1_post_request(endpoint: Url, psbt_ctx: PsbtContext) -> (Request, V1Context) { |
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.
it feels a bit off that this is not in the v1 module, which has more to do with feature flags than with functionality
this is not a critique of this PR, just thinking outloud, but this got me thinking that we also don't really have a mechanism for blanket disabling v1 support, e.g. a v2 receiver that ignores v1 senders' requests, or a v2 sender that refuses to send v1 requests. i'm not sure there is sufficient motivation for either behavior, but depending on the answer we might want need to reconsider, currently --features v1 means "compile code that supports sending/receiving only using bip 78" and --features v2 means "compile code that supports sending/receiving both bip 78 and bip 77". v2 could onstensibly mean bip 77 only unless v1 is also enabled, in which case this code would actually belong in the v1 module
| /// Data required to validate the response. | ||
| /// | ||
| /// This type is used to process a BIP78 response. |
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.
| /// Data required to validate the response. | |
| /// | |
| /// This type is used to process a BIP78 response. | |
| /// Data required to validate and process a BIP78 response. |
| let body = serialize_v2_body( | ||
| &self.0.v1.psbt, | ||
| self.0.v1.output_substitution, | ||
| self.0.v1.fee_contribution, | ||
| self.0.v1.min_fee_rate, | ||
| &self.0.state.psbt_ctx.original_psbt, | ||
| self.0.state.psbt_ctx.output_substitution, | ||
| self.0.state.psbt_ctx.fee_contribution, | ||
| self.0.state.psbt_ctx.min_fee_rate, | ||
| )?; |
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 seems like an opportunity to refactor serialize_v2_body and maybe serialize_url to just take a &PsbtContext as argument.
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.
Yes this is in my working branch 😎
{v1,v2,multiparty}::Sender{,Builder}types have been built off of a heirarchy where multiparty:: has a v2:: and v2:: has a v1::, which was done for quick convenience and not because that relationship actually makes functional sense.This PR is the first in a few which I believe are necessary to rectify this distinction. It does so by drawing the separation between the sender's
PsbtContextchecks / response processing and the version-specific serialization for networked messaging. However, I don't think it goes far enough.For one, it only really rectifies this issue between v1 and v2. Multiparty is left abstract over v2. Second, There are still distinct SenderBuilders that can't tell whether or not they're handling a v1 or v2 URIs. Since the information necessary to distinguish between a v1/v2 URI is in the URI itself, it seems that ought to be the first order of business for the sender to do even before calling
SenderBuilder::new. The lack of this distinction leads to a problem with a hacky solution where downstream users need to wait all the way until they attempt to create a v2 request and handle an error there in order to figure out the version. TheSenderBuilderalso ought to behave differently for each version, and I'm not sure our current fix of #847 does this completely (Does a v2 SenderBuilder sending to v1 URI honor pjos? it should). In order to do so I reckon we could create an actualPjUritype, rather than an alias, that enumerates over the versions whencheck_pj_supportedor its replacement is called. In order to do that effectively by making sure the correct parameters are there and we're not just switching on the presence of a fragment, I thinkUrlExtalso needs to check for the parameter presence and validity.The other issue with v1::Sender flow is that it doesn't use the generic typestate machine pattern to match v2, which would be nice as well but out of the scope of this PR.
re: #809