From 339bba6f2d0c4f73c8d0b04a535b1e19e53482ea Mon Sep 17 00:00:00 2001 From: Armin Sabouri Date: Thu, 11 Sep 2025 15:26:03 -0400 Subject: [PATCH] Enforce that `SessionHistory` is created internally `SessionHistory` should only be created internally. Removing the default impl and replacing it with a pub(crate) new is the simplest way to achieve that. --- payjoin-ffi/src/receive/mod.rs | 2 +- payjoin-ffi/src/send/mod.rs | 2 +- payjoin/src/core/receive/v2/session.rs | 13 +++++++++---- payjoin/src/core/send/v2/session.rs | 13 +++++++++---- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/payjoin-ffi/src/receive/mod.rs b/payjoin-ffi/src/receive/mod.rs index 2aea26b62..c50926137 100644 --- a/payjoin-ffi/src/receive/mod.rs +++ b/payjoin-ffi/src/receive/mod.rs @@ -139,7 +139,7 @@ pub fn replay_receiver_event_log( Ok(ReplayResult { state: state.into(), session_history: session_history.into() }) } -#[derive(Default, Clone, uniffi::Object)] +#[derive(Clone, uniffi::Object)] pub struct SessionHistory(pub payjoin::receive::v2::SessionHistory); impl From for SessionHistory { diff --git a/payjoin-ffi/src/send/mod.rs b/payjoin-ffi/src/send/mod.rs index d9d9c2185..92511810f 100644 --- a/payjoin-ffi/src/send/mod.rs +++ b/payjoin-ffi/src/send/mod.rs @@ -111,7 +111,7 @@ pub fn replay_sender_event_log( Ok(SenderReplayResult { state: state.into(), session_history: session_history.into() }) } -#[derive(uniffi::Object, Default, Clone)] +#[derive(uniffi::Object, Clone)] pub struct SenderSessionHistory(pub payjoin::send::v2::SessionHistory); impl From for SenderSessionHistory { diff --git a/payjoin/src/core/receive/v2/session.rs b/payjoin/src/core/receive/v2/session.rs index 57911a5b2..d1d306883 100644 --- a/payjoin/src/core/receive/v2/session.rs +++ b/payjoin/src/core/receive/v2/session.rs @@ -24,15 +24,14 @@ where .load() .map_err(|e| InternalReplayError::PersistenceFailure(ImplementationError::new(e)))?; - let mut history = SessionHistory::default(); let first_event = logs.next().ok_or(InternalReplayError::NoEvents)?.into(); - history.events.push(first_event.clone()); + let mut session_events = vec![first_event.clone()]; let mut receiver = match first_event { SessionEvent::Created(context) => ReceiveSession::new(context), _ => return Err(InternalReplayError::InvalidEvent(Box::new(first_event), None).into()), }; for event in logs { - history.events.push(event.clone().into()); + session_events.push(event.clone().into()); receiver = receiver.process_event(event.into()).map_err(|e| { if let Err(storage_err) = persister.close() { return InternalReplayError::PersistenceFailure(ImplementationError::new( @@ -44,6 +43,7 @@ where })?; } + let history = SessionHistory::new(session_events); let ctx = history.session_context(); if SystemTime::now() > ctx.expiry { // Session has expired: close the session and persist a fatal error @@ -63,12 +63,17 @@ where /// A collection of events that have occurred during a receiver's session. /// It is obtained by calling [replay_event_log]. -#[derive(Default, Clone)] +#[derive(Clone)] pub struct SessionHistory { events: Vec, } impl SessionHistory { + pub(crate) fn new(events: Vec) -> Self { + debug_assert!(!events.is_empty(), "Session event log must contain at least one event"); + Self { events } + } + /// Receiver session Payjoin URI pub fn pj_uri<'a>(&self) -> PjUri<'a> { self.events diff --git a/payjoin/src/core/send/v2/session.rs b/payjoin/src/core/send/v2/session.rs index 4462146d1..ccbe14c71 100644 --- a/payjoin/src/core/send/v2/session.rs +++ b/payjoin/src/core/send/v2/session.rs @@ -16,9 +16,8 @@ where let mut logs = persister .load() .map_err(|e| InternalReplayError::PersistenceFailure(ImplementationError::new(e)))?; - let mut history = SessionHistory::default(); let first_event = logs.next().ok_or(InternalReplayError::NoEvents)?.into(); - history.events.push(first_event.clone()); + let mut session_events = vec![first_event.clone()]; let mut sender = match first_event { SessionEvent::CreatedReplyKey(reply_key) => SendSession::new(reply_key), _ => return Err(InternalReplayError::InvalidEvent(Box::new(first_event), None).into()), @@ -26,7 +25,7 @@ where for log in logs { let session_event = log.into(); - history.events.push(session_event.clone()); + session_events.push(session_event.clone()); match sender.clone().process_event(session_event) { Ok(next_sender) => sender = next_sender, Err(_e) => { @@ -38,6 +37,7 @@ where } } + let history = SessionHistory::new(session_events); let pj_param = history.pj_param(); if std::time::SystemTime::now() > pj_param.expiration() { // Session has expired: close the session and persist a fatal error @@ -53,12 +53,17 @@ where Ok((sender, history)) } -#[derive(Default, Clone)] +#[derive(Clone)] pub struct SessionHistory { events: Vec, } impl SessionHistory { + pub(crate) fn new(events: Vec) -> Self { + debug_assert!(!events.is_empty(), "Session event log must contain at least one event"); + Self { events } + } + /// Fallback transaction from the session pub fn fallback_tx(&self) -> bitcoin::Transaction { self.events