-
Notifications
You must be signed in to change notification settings - Fork 77
Sender generic over typestate #728
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
Sender generic over typestate #728
Conversation
Pull Request Test Coverage Report for Build 15381296070Details
💛 - Coveralls |
b5a0378 to
eaf5b48
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.
The one change I'll insist on is that Sender gets defined before the states, otherwise my comments are very similar to the #719 PR.
I see that send::v2::State has no explicit definition, and so the Sender here does not require explicit trait State {} type constraint. I noticed that wasn't done for Receiver either, and ask you to consider doing so or otherwise stating rationale for straying from the pattern.
Lastly, review of this brought into question our use of v1::Sender context in the state vs on the Sender struct, since each state contains that data, either as v1::Sender OR as endpoint and psbt_context. I wonder if even v1::Sender should have the psbt_context after noticing this distinction.
We could follow up with
- elision of fully qualified names in ffi
- better names for the states
Please let me know which of these changes you think should be made here besides the Sender/typestate definition reorder.
|
|
||
| #[derive(Clone)] | ||
| pub struct Sender(payjoin::send::v2::Sender); | ||
| pub struct WithReplyKey(payjoin::send::v2::Sender<payjoin::send::v2::WithReplyKey>); |
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.
Similar to my comment about WithContext in the receiver PR, I'd rather use a unique identifier for this state, especially as it gets its own type here in FFI, where it's not SenderWithReplyKey but just tacit WithReplyKey.
What do you think of Uninitialized/Initialized as NewSender/WithReplyKey renames?
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 not exactly sure what you mean by "unique identifier for this state". The reason I went WithReplyKey is that is describes the diff between the current and last state.
FWIW we will need to introduce a Uninitalizsed sender state. Its used during session log replay.
payjoin/src/send/v2/mod.rs
Outdated
| pub struct Sender<State> { | ||
| pub(crate) 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.
State Appears not to be defined as a trait, so any type can Be State rather than requiring explicit impl State for V2PostContext {} etc. I think this was missed for Receiver as well. It's not strictly necessary but it would prevent the next engineer from making a Receiver<State> for any arbitrary State without explicitly marking it so.
I'd also definitely like to see it defined before the first typestate NewSender.
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'd also definitely like to see it defined before the first typestate NewSender.
I assuming you mean the Sender<state> def. If so can do
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.
State Appears not to be defined as a trait, so any type can Be State rather than requiring explicit impl State for V2PostContext {} etc. I think this was missed for Receiver as well. It's not strictly necessary but it would prevent the next engineer from making a Receiver for any arbitrary State without explicitly marking it so.
This offers slightly better type safety and fool-proof. I also notice that it is defined as part of the pattern defined here. Happy to make this change. I'll make it a seperate commit for both sender and receiver in 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.
As a side note: the state trait will unfortuantely need to be pub due to Sender being pub. However if we use sealed traits they can be pub however the caller cannot impl
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 of course it has to be pub if the type is pub. A sealed trait is appropriate in these cases.
| pub struct Sender { | ||
| pub struct WithReplyKey { | ||
| /// The v1 Sender. | ||
| pub(crate) v1: v1::Sender, |
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.
v1: fields are duplicated v1::Senders for two states, unlike receiver.state.v1 fields which are unique to each state. Might it make more sense to make v1: v1::Sender a field of Sender, or does that complicate the FFI translation or other machinery?
It makes me wonder if v1::sender should have its own PsbtContext versus the odd separation starting with V2PostContext where psbt_context and endpoint are separated out of v1::Sender, but all the information is still there.
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.
No it shouldn't complicate FFI at all. FFI doesnt read v1 fields so FFI shouldn't need to 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.
It would look something like this.
I would like to create an issue and catagorize this as techdebt and focus of the core functionality for now. Lmk if you agree
This commit updates the `v2::Sender` to be generic over its typestate (e.g., `V2GetContext`, `V2PostContext`, etc.). The motivation is outlined in c8b860d This commit renames the original `Sender` struct to `WithReplyKey`. This type now represents the v2::sender session once the HPKE context has been created. The `Sender` name is re-purposed for the new generic wrapper over typestate. For the UniFFI exported receiver we monomorphize the exported structs over a specific typestate. UniFFI doesn't support exporting generic structs, so we expose a concrete type to the FFI.
To prevent any struct from becoming a typestate we enforce that all type states implement a blank trait. Each "State" trait is unique to a state machine to prevent accidentaly crosswiring. i.e a reciever type state should never be used for a sender state machine.
eaf5b48 to
722bfa9
Compare
I added a blank trait 722bfa9 to address this for both the receiver and sender typestates.
I implemented this suggestion here. I would like to save this item for a follow up PR and create an issue labeled as tech debt.
Note: Sender type reorder is also pushed. Follow up items are:
|
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 722bfa9
I think x:State more readable than x:XStateandmore readable than<State: XState> so I'd like to see that name change in a follow up as well
| pub trait ReceiverState {} | ||
|
|
||
| #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] | ||
| pub struct Receiver<State> { | ||
| pub struct Receiver<State: ReceiverState> { |
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.
prefer receiver::State to receiver::ReceiverState, and then Receiver<State> remains valid. Can wait for rename.
payjoin/src/send/v2/mod.rs
Outdated
| pub struct Sender<State> { | ||
| pub(crate) 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.
Yes of course it has to be pub if the type is pub. A sealed trait is appropriate in these cases.
|
Additionally, I find your recommendation to use sealed traits for these State traits appropriate |
This commit updates the
v2::Senderto be generic over itstypestate (e.g.,
V2GetContext,V2PostContext, etc.). Themotivation is outlined in arminsabouri@c8b860d
This commit renames the original
Senderstruct toWithReplyKey.This type now represents the v2::sender session once the HPKE context has been
created. The
Sendername is re-purposed for the new generic wrapperover typestate.
For the UniFFI exported receiver we monomorphize the exported structs over a
specific typestate. UniFFI doesn't support exporting generic structs, so
we expose a concrete type to the FFI.