Decoupling receiver typestates with common abstractions
Currently, our receiver typestates enforce a strict hierarchy where V2 typestates wrap V1 typestates. This design reduces code duplication, but it's becoming a liability as the V2 API evolves.
For example:
- We must map to V1-specific errors that don’t make sense in the V2 context.
- Unit tests require excessive boilerplate. For example, testing V2 fee application logic means instantiating a full receiver state machine and progressing it step by step just to reach the fee application state.
- It is also bloating our V2 session events in some cases. More on this below
let unchecked = v1::test::unchecked_proposal_from_test_vector();
let wants_outputs = unchecked
.assume_interactive_receiver()
.check_inputs_not_owned(&mut |_| Ok(false))
.expect("No inputs should be owned")
.check_no_inputs_seen_before(&mut |_| Ok(false))
.expect("No inputs should be seen before")
.identify_receiver_outputs(&mut |_| Ok(true))
.expect("Receiver output should be identified");
let wants_inputs = wants_outputs.commit_outputs();
let v1_wants_fee_range = wants_inputs.commit_inputs();
Toward a Decoupled Design
We need to introduce shared abstractions that cleanly separate session logic from typestate progression. An initial attempt is available here.
For the UncheckedProposal to OutputsUnknown states, we can introduce a Proposal { psbt, params } struct that implements shared checks -- such as input seen before or broadcast suitability -- without tying them to V1/V2 typestates. Typestates still encode state machine semantics but we can remove the hierarchy. Note that the shared abstractions are session and protocol version agnostic.
We could apply this pattern incrementally to other typestates. i.e Replace inner V1 states with independent session agnostic structs -- unless there is a compelling reason to wrap v1?
Lastly, this decoupling also reduces the payload size for V2 session events. Recall, session events represent new information discovered during state progression. We shouldn't need to clone the entire PSBT and params for read-only receiver check states.
A WIP PoC is available here. We can incrementally migrate to this design to ease review, but full decoupling should be completed before a release.
Related ticket: #924 This ticket was created out of this discussion
- [x] decouple typestates
- [ ] decouple errors
- [x] minimize SessionEvent variants