-
Notifications
You must be signed in to change notification settings - Fork 77
Make Receiver generic over its typestate
#719
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
Make Receiver generic over its typestate
#719
Conversation
93a5327 to
ca1e333
Compare
Pull Request Test Coverage Report for Build 15353666997Details
💛 - Coveralls |
63d50d7 to
7f296ea
Compare
|
Last push reverts changes made to payjoin-ffi crate. |
|
Hm it seems like the ffi changes are neccecary to make the tests happy -- probably due to the patch in the cargo.toml. Since payjoin-ffi is pinned to 0.23 and excluded from the workspace does it make sense to exclude its test suite from My preference would be to just include it in the workspace (#721) |
DanGould
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.
Easy to follow and mostly drop in as far as all files except for src/receive/v2/mod.rs are concerned.
I request some minor changes that will reduce the diff here by implementing Deref{,Mut} on receiver and some multiparty related visibility tweaks.
payjoin-cli/src/app/v2/mod.rs
Outdated
| session: &mut payjoin::receive::v2::Receiver, | ||
| ) -> Result<payjoin::receive::v2::UncheckedProposal> { | ||
| session: &mut payjoin::receive::v2::Receiver<payjoin::receive::v2::WithContext>, | ||
| ) -> Result<payjoin::receive::v2::Receiver<payjoin::receive::v2::UncheckedProposal>> { |
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.
nonblocking nit comment: imported don't fully qualify the generic (this had to be the first comment didn't it 😛 I promise later comments are more important)
Lots of full qualifications in this file to replace old qualifications, but they're not longer necessary and create noise.
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.
There are a couple more fully qualified receiver types that exist on master. I will import those ones and remove the full qualification in a seperate commit on this PR
| #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] | ||
| pub struct WithContext { |
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.
Since all of the Receiver State types include SessionContext I think we could do better with this name (Initialilzed?) but am happy to leave it for a name audit before our next release
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 would prefer to do a big naming audit before the release if thats an option.
payjoin/src/receive/v2/mod.rs
Outdated
| ) -> Result<(Request, ohttp::ClientResponse), Error> { | ||
| if SystemTime::now() > self.context.expiry { | ||
| return Err(InternalSessionError::Expired(self.context.expiry).into()); | ||
| if SystemTime::now() > self.0.context.expiry { |
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.
These self.0 calls add verbosity without increased clarity since the internal type is unnamed. Let's elide the 0s everywhere with a simple impl Deref.
impl<State> core::ops::Deref for Receiver<State> {
type Target = State;
fn deref(&self) -> &Self::Target {
&self.0
}
}Even the assignments can use this syntax if we impl DerefMut for Receiver for example self.context.e = Some(e);
impl<State> core::ops::DerefMut for Receiver<State> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}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.
Great suggestion. I think this works most cases but if we are trying to move the value out of the struct
i.e self.0.v1.check_broadcast_suitability(min_fee_rate, can_broadcast)?; then we will hit a borrowing / move through deref issue. In those cases we will still need to move with direct access (self.0).
without increased clarity
Should we name the inner type?
Receiver<state> { state: state }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.
Leaving explicit self.0 on those cases is fine with me.
However, there are still a dozen or so instances of self.0 that can now be elided because of the Deref impls that I would like to see included to take advantage of the new impls before this is merged.
self.state is a bit more clear. I'd accept this PR without it but I do think it reads better than self.0.
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.
Did a comb through of all cases where self.0 could be elided. I agree self.state is more clear. Infact that is what I originally had in the sketch branch. I'll make that update
payjoin/src/receive/v2/mod.rs
Outdated
| #[cfg(feature = "_multiparty")] | ||
| pub fn inner(&self) -> &UncheckedProposal { &self.0 } |
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 a weird one that's not super discoverable. Why .inner() instead of making multiparty_proposal.add take a v2::Receiver<UncheckedProposal>? Does this only smell to me?
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.
You're right. The design constraint we are working with is two fold. One inner state should be opaque to the application. Two NS1R typestates don't need to store v2 typestates only the state structs (uncheckedProposal).
The best path forward is:
multiparty_proposal.addto acceptv2::Receiver<UncheckedProposal>- and pub crate() the inner receiver state only if the multiparty flag is on
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 the new change that changes v2::Receiver's visibility with a feature flag is even weirder than a dedicated method. I would prefer leaving it pub(crate) regardless of feature set, or just have the internal v2_proposals store the v2::Receiver struct directly. I think the former is the simplest, to leave the inner state `pub(crate) regardless of feature set.
7f296ea to
35ecbb1
Compare
|
Converting to draft till we fix #721 |
7df3d86 to
06635ad
Compare
|
The last push re-introduces the ffi changes. |
7f12faf to
dcd036c
Compare
|
Drafting till I fix the uniffi build |
dcd036c to
857ac8d
Compare
|
FWIW sender side is drafted here |
857ac8d to
922a224
Compare
This commit updates the `v2::Receiver` to be generic over its typestate (e.g., `UncheckedProposal`, `InputsSeen`, etc.). The motivation: * Improves readability and maintainability: Each typestate now clearly belongs to the receiver session. This makes the code easier to reason about, without runtime cost. It also sets us up to define a shared pattern between v2, NS1R and future state machines. * Decouples typestate data from transition logic: NS1R typesates can now consume state structs without being tied to the transition machinery. This keeps state transitions encapsulated and makes the underlying session data easier to use in other contexts. This commit renames the original `Receiver` struct to `WithContext`. This type now represents the receiver session once the HPKE context has been created. The Receiver name is re-purposed for the new generic wrapper over typestate. For the UniFFI exported receiver we monomorphizes the exported structs over a specific typestate. UniFFI doesn't support exporting generic structs, so we expose a concrete type to the FFI.
The full paths are often hard to read and create noise in this file.
922a224 to
1b42be6
Compare
DanGould
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.
ACK 1b42be6
This commit updates the
v2::Receiverto be generic over its typestate (e.g.,UncheckedProposal,InputsSeen, etc.). The motivation:Improves readability and maintainability: Each typestate now clearly belongs to the receiver session. This makes the code easier to reason about, without runtime cost. It also sets us up to define a shared pattern between v2, NS1R and future state machines.
Decouples typestate data from transition logic: NS1R typesates can now consume state structs without being tied to the transition machinery. This keeps state transitions encapsulated and makes the underlying session data easier to use in other contexts.
This commit renames the original
Receiverstruct toWithContext.WithContexttype represents the receiver session once the HPKE context has been created. The Receiver name is re-purposed for the new generic wrapper over typestate.For the UniFFI exported receiver we monomorphizes the exported structs over a specific typestate. UniFFI doesn't support exporting generic structs, so we expose a concrete type to the FFI.