Skip to content

Commit 58c4143

Browse files
committed
Close session on fatal error only when specified
Only a subset of fatal errors should result in immediate closure of a receiver session. In many cases an attempt should first be made to respond to the sender with an error as specified in BIP78. Instead of scrutinizing various error types to determine whether it should be replyable or not, the callsite can simply provide the appropriate SessionEvent::HasReplyableError(JsonReply) for replyable errors, or SessionEvent::Closed(Failure) for non-replyable ones.
1 parent ab1ba54 commit 58c4143

3 files changed

Lines changed: 57 additions & 18 deletions

File tree

payjoin/src/core/persist.rs

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -336,14 +336,25 @@ pub enum OptionalTransitionOutcome<NextState, CurrentState> {
336336
Stasis(CurrentState),
337337
}
338338

339+
/// Sealed trait to prevent external implementation of SessionEventTrait.
340+
pub(crate) mod sealed {
341+
pub trait Sealed {}
342+
}
343+
344+
/// Trait for session events that determines if an event represents a session closure.
345+
pub trait SessionEventTrait: sealed::Sealed {
346+
/// Returns true if this event represents a session closure that should close the persister.
347+
fn is_closed_event(&self) -> bool;
348+
}
349+
339350
/// A session that can persist events to an append-only log.
340351
/// A session represents a sequence of events generated by the BIP78 state machine.
341352
/// The events can be replayed from the log to reconstruct the state machine's state.
342353
pub trait SessionPersister {
343354
/// Errors that may arise from implementers storage layer
344355
type InternalStorageError: std::error::Error + Send + Sync + 'static;
345356
/// Session events types that we are persisting
346-
type SessionEvent;
357+
type SessionEvent: SessionEventTrait;
347358

348359
/// Appends to list of session updates, Receives generic events
349360
fn save_event(&self, event: Self::SessionEvent) -> Result<(), Self::InternalStorageError>;
@@ -512,12 +523,14 @@ trait InternalSessionPersister: SessionPersister {
512523
Err: std::error::Error,
513524
{
514525
let RejectFatal(event, error) = reject_fatal;
526+
let should_close = event.is_closed_event();
515527
if let Err(e) = self.save_event(event) {
516528
return InternalPersistedError::Storage(e);
517529
}
518-
// Session is in a terminal state, close it
519-
if let Err(e) = self.close() {
520-
return InternalPersistedError::Storage(e);
530+
if should_close {
531+
if let Err(e) = self.close() {
532+
return InternalPersistedError::Storage(e);
533+
}
521534
}
522535

523536
InternalPersistedError::Fatal(error)
@@ -531,14 +544,20 @@ impl<T: SessionPersister> InternalSessionPersister for T {}
531544
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
532545
pub struct NoopPersisterEvent;
533546

547+
impl sealed::Sealed for NoopPersisterEvent {}
548+
549+
impl SessionEventTrait for NoopPersisterEvent {
550+
fn is_closed_event(&self) -> bool { false }
551+
}
552+
534553
#[derive(Debug, Clone)]
535554
pub struct NoopSessionPersister<E = NoopPersisterEvent>(std::marker::PhantomData<E>);
536555

537556
impl<E> Default for NoopSessionPersister<E> {
538557
fn default() -> Self { Self(std::marker::PhantomData) }
539558
}
540559

541-
impl<E: 'static> SessionPersister for NoopSessionPersister<E> {
560+
impl<E: 'static + SessionEventTrait> SessionPersister for NoopSessionPersister<E> {
542561
type InternalStorageError = std::convert::Infallible;
543562
type SessionEvent = E;
544563

@@ -559,7 +578,7 @@ impl<E: 'static> SessionPersister for NoopSessionPersister<E> {
559578
pub mod test_utils {
560579
use std::sync::{Arc, RwLock};
561580

562-
use crate::persist::SessionPersister;
581+
use crate::persist::{SessionEventTrait, SessionPersister};
563582

564583
#[derive(Clone)]
565584
/// In-memory session persister for testing session replays and introspecting session events
@@ -583,7 +602,7 @@ pub mod test_utils {
583602

584603
impl<V> SessionPersister for InMemoryTestPersister<V>
585604
where
586-
V: Clone + 'static,
605+
V: Clone + 'static + SessionEventTrait,
587606
{
588607
type InternalStorageError = std::convert::Infallible;
589608
type SessionEvent = V;
@@ -625,6 +644,12 @@ mod tests {
625644
#[derive(Debug, Clone, Serialize, Deserialize)]
626645
pub struct InMemoryTestEvent(String);
627646

647+
impl crate::persist::sealed::Sealed for InMemoryTestEvent {}
648+
649+
impl crate::persist::SessionEventTrait for InMemoryTestEvent {
650+
fn is_closed_event(&self) -> bool { self.0 == "error close" }
651+
}
652+
628653
#[derive(Debug, Clone, PartialEq)]
629654
/// Dummy error type for testing
630655
struct InMemoryTestError {}
@@ -783,6 +808,7 @@ mod tests {
783808
#[test]
784809
fn test_maybe_success_transition() {
785810
let event = InMemoryTestEvent("foo".to_string());
811+
let error_event = InMemoryTestEvent("error close".to_string());
786812
let test_cases: Vec<
787813
TestCase<(), PersistedError<InMemoryTestError, std::convert::Infallible>>,
788814
> = vec![
@@ -813,17 +839,14 @@ mod tests {
813839
// Fatal error
814840
TestCase {
815841
expected_result: ExpectedResult {
816-
events: vec![InMemoryTestEvent("error event".to_string())],
842+
events: vec![error_event.clone()],
817843
is_closed: true,
818844
error: Some(InternalPersistedError::Fatal(InMemoryTestError {}).into()),
819845
success: None,
820846
},
821847
test: Box::new(move |persister| {
822-
MaybeSuccessTransition::fatal(
823-
InMemoryTestEvent("error event".to_string()),
824-
InMemoryTestError {},
825-
)
826-
.save(persister)
848+
MaybeSuccessTransition::fatal(error_event.clone(), InMemoryTestError {})
849+
.save(persister)
827850
}),
828851
},
829852
];
@@ -873,7 +896,7 @@ mod tests {
873896
TestCase {
874897
expected_result: ExpectedResult {
875898
events: vec![error_event.clone()],
876-
is_closed: true,
899+
is_closed: false,
877900
error: Some(InternalPersistedError::Fatal(InMemoryTestError {}).into()),
878901
success: None,
879902
},
@@ -893,7 +916,7 @@ mod tests {
893916
#[test]
894917
fn test_maybe_success_transition_with_no_results() {
895918
let event = InMemoryTestEvent("foo".to_string());
896-
let error_event = InMemoryTestEvent("error event".to_string());
919+
let error_event = InMemoryTestEvent("error close".to_string());
897920
let current_state = "Current state".to_string();
898921
let success_value = "Success value".to_string();
899922
let test_cases: Vec<
@@ -1010,7 +1033,7 @@ mod tests {
10101033
TestCase {
10111034
expected_result: ExpectedResult {
10121035
events: vec![error_event.clone()],
1013-
is_closed: true,
1036+
is_closed: false,
10141037
error: Some(InternalPersistedError::Fatal(InMemoryTestError {}).into()),
10151038
success: None,
10161039
},

payjoin/src/core/receive/v2/session.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
33
use super::{ReceiveSession, SessionContext};
44
use crate::error::{InternalReplayError, ReplayError};
55
use crate::output_substitution::OutputSubstitution;
6-
use crate::persist::SessionPersister;
6+
use crate::persist::{SessionEventTrait, SessionPersister};
77
use crate::receive::v2::{extract_err_req, SessionError};
88
use crate::receive::{common, InputPair, JsonReply, OriginalPayload, PsbtContext};
99
use crate::{ImplementationError, IntoUrl, PjUri, Request};
@@ -216,6 +216,12 @@ pub enum SessionOutcome {
216216
Cancel,
217217
}
218218

219+
impl crate::persist::sealed::Sealed for SessionEvent {}
220+
221+
impl SessionEventTrait for SessionEvent {
222+
fn is_closed_event(&self) -> bool { matches!(self, SessionEvent::Closed(_)) }
223+
}
224+
219225
#[cfg(test)]
220226
mod tests {
221227
use std::time::{Duration, SystemTime};

payjoin/src/core/send/v2/session.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::WithReplyKey;
22
use crate::error::{InternalReplayError, ReplayError};
3-
use crate::persist::SessionPersister;
3+
use crate::persist::{SessionEventTrait, SessionPersister};
44
use crate::send::v2::{PollingForProposal, SendSession};
55
use crate::uri::v2::PjParam;
66
use crate::ImplementationError;
@@ -98,6 +98,16 @@ pub enum SessionEvent {
9898
SessionInvalid(String),
9999
}
100100

101+
impl crate::persist::sealed::Sealed for SessionEvent {}
102+
103+
impl SessionEventTrait for SessionEvent {
104+
fn is_closed_event(&self) -> bool {
105+
// Sender doesn't have a Closed variant yet, so always return true for now.
106+
// This maintains current behavior where all fatal events close the session.
107+
true
108+
}
109+
}
110+
101111
#[cfg(test)]
102112
mod tests {
103113
use bitcoin::{FeeRate, ScriptBuf};

0 commit comments

Comments
 (0)