Skip to content

Commit 401d882

Browse files
committed
Make Receiver generic over its typestate
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.
1 parent 16f0c20 commit 401d882

File tree

8 files changed

+252
-155
lines changed

8 files changed

+252
-155
lines changed

payjoin-cli/src/app/v2/mod.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ use anyhow::{anyhow, Context, Result};
44
use payjoin::bitcoin::consensus::encode::serialize_hex;
55
use payjoin::bitcoin::psbt::Psbt;
66
use payjoin::bitcoin::{Amount, FeeRate};
7-
use payjoin::receive::v2::{NewReceiver, Receiver, UncheckedProposal};
7+
use payjoin::receive::v2::{
8+
NewReceiver, PayjoinProposal, Receiver, UncheckedProposal, WithContext,
9+
};
810
use payjoin::receive::{Error, ReplyableError};
911
use payjoin::send::v2::{Sender, SenderBuilder};
1012
use payjoin::{ImplementationError, Uri};
@@ -151,7 +153,7 @@ impl App {
151153
#[allow(clippy::incompatible_msrv)]
152154
async fn spawn_payjoin_receiver(
153155
&self,
154-
mut session: Receiver,
156+
mut session: Receiver<WithContext>,
155157
amount: Option<Amount>,
156158
) -> Result<()> {
157159
println!("Receive session established");
@@ -241,8 +243,8 @@ impl App {
241243

242244
async fn long_poll_fallback(
243245
&self,
244-
session: &mut payjoin::receive::v2::Receiver,
245-
) -> Result<payjoin::receive::v2::UncheckedProposal> {
246+
session: &mut payjoin::receive::v2::Receiver<WithContext>,
247+
) -> Result<payjoin::receive::v2::Receiver<UncheckedProposal>> {
246248
let ohttp_relay = self.unwrap_relay_or_else_fetch().await?;
247249

248250
loop {
@@ -261,8 +263,8 @@ impl App {
261263

262264
fn process_v2_proposal(
263265
&self,
264-
proposal: payjoin::receive::v2::UncheckedProposal,
265-
) -> Result<payjoin::receive::v2::PayjoinProposal, Error> {
266+
proposal: payjoin::receive::v2::Receiver<UncheckedProposal>,
267+
) -> Result<payjoin::receive::v2::Receiver<PayjoinProposal>, Error> {
266268
let wallet = self.wallet();
267269

268270
// in a payment processor where the sender could go offline, this is where you schedule to broadcast the original_tx
@@ -313,7 +315,7 @@ impl App {
313315
/// Handle request error by sending an error response over the directory
314316
async fn handle_recoverable_error(
315317
e: ReplyableError,
316-
mut receiver: UncheckedProposal,
318+
mut receiver: Receiver<UncheckedProposal>,
317319
ohttp_relay: &payjoin::Url,
318320
) -> anyhow::Error {
319321
let to_return = anyhow!("Replied with error: {}", e);
@@ -340,9 +342,12 @@ async fn handle_recoverable_error(
340342
}
341343

342344
fn try_contributing_inputs(
343-
payjoin: payjoin::receive::v2::WantsInputs,
345+
payjoin: payjoin::receive::v2::Receiver<payjoin::receive::v2::WantsInputs>,
344346
wallet: &BitcoindWallet,
345-
) -> Result<payjoin::receive::v2::ProvisionalProposal, ImplementationError> {
347+
) -> Result<
348+
payjoin::receive::v2::Receiver<payjoin::receive::v2::ProvisionalProposal>,
349+
ImplementationError,
350+
> {
346351
let candidate_inputs = wallet.list_unspent()?;
347352

348353
let selected_input =

payjoin-cli/src/db/v2.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::sync::Arc;
22

33
use bitcoincore_rpc::jsonrpc::serde_json;
44
use payjoin::persist::{Persister, Value};
5-
use payjoin::receive::v2::{Receiver, ReceiverToken};
5+
use payjoin::receive::v2::{Receiver, ReceiverToken, WithContext};
66
use payjoin::send::v2::{Sender, SenderToken};
77
use sled::Tree;
88
use url::Url;
@@ -38,31 +38,35 @@ impl ReceiverPersister {
3838
pub fn new(db: Arc<Database>) -> Self { Self(db) }
3939
}
4040

41-
impl Persister<Receiver> for ReceiverPersister {
41+
impl Persister<Receiver<WithContext>> for ReceiverPersister {
4242
type Token = ReceiverToken;
4343
type Error = crate::db::error::Error;
44-
fn save(&mut self, value: Receiver) -> std::result::Result<ReceiverToken, Self::Error> {
44+
fn save(
45+
&mut self,
46+
value: Receiver<WithContext>,
47+
) -> std::result::Result<ReceiverToken, Self::Error> {
4548
let recv_tree = self.0 .0.open_tree("recv_sessions")?;
4649
let key = value.key();
4750
let value = serde_json::to_vec(&value).map_err(Error::Serialize)?;
4851
recv_tree.insert(key.clone(), value.as_slice())?;
4952
recv_tree.flush()?;
5053
Ok(key)
5154
}
52-
fn load(&self, key: ReceiverToken) -> std::result::Result<Receiver, Self::Error> {
55+
fn load(&self, key: ReceiverToken) -> std::result::Result<Receiver<WithContext>, Self::Error> {
5356
let recv_tree = self.0 .0.open_tree("recv_sessions")?;
5457
let value = recv_tree.get(key.as_ref())?.ok_or(Error::NotFound(key.to_string()))?;
5558
serde_json::from_slice(&value).map_err(Error::Deserialize)
5659
}
5760
}
5861

5962
impl Database {
60-
pub(crate) fn get_recv_sessions(&self) -> Result<Vec<Receiver>> {
63+
pub(crate) fn get_recv_sessions(&self) -> Result<Vec<Receiver<WithContext>>> {
6164
let recv_tree = self.0.open_tree("recv_sessions")?;
6265
let mut sessions = Vec::new();
6366
for item in recv_tree.iter() {
6467
let (_, value) = item?;
65-
let session: Receiver = serde_json::from_slice(&value).map_err(Error::Deserialize)?;
68+
let session: Receiver<WithContext> =
69+
serde_json::from_slice(&value).map_err(Error::Deserialize)?;
6670
sessions.push(session);
6771
}
6872
Ok(sessions)

0 commit comments

Comments
 (0)