Skip to content

Commit ca5da9c

Browse files
committed
Introduce state transitions to HasReplyableError
Only a subset of fatal errors should result in immediate closure of areceiver session. In many cases an attempt should first be made torespond to the sender with an error as specified in BIP78. Instead of scrutinizing various error types to determine whether itshould be replyable or not, the callsite can simply call the appropriate state transition method: `replyable_error()` for replyable errors or `fatal()` for non-replyable ones. For replyable errors, the resulting HasReplyableError typestate can be obtained from the resulting `PersistedError` with the `error_state()` method.
1 parent 5e28e3e commit ca5da9c

3 files changed

Lines changed: 154 additions & 63 deletions

File tree

payjoin-ffi/src/receive/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@ pub struct UncheckedOriginalPayloadTransition(
398398
payjoin::receive::v2::SessionEvent,
399399
payjoin::receive::v2::Receiver<payjoin::receive::v2::MaybeInputsOwned>,
400400
payjoin::receive::Error,
401+
payjoin::receive::v2::Receiver<payjoin::receive::v2::HasReplyableError>,
401402
>,
402403
>,
403404
>,
@@ -480,6 +481,7 @@ pub struct MaybeInputsOwnedTransition(
480481
payjoin::receive::v2::SessionEvent,
481482
payjoin::receive::v2::Receiver<payjoin::receive::v2::MaybeInputsSeen>,
482483
payjoin::receive::Error,
484+
payjoin::receive::v2::Receiver<payjoin::receive::v2::HasReplyableError>,
483485
>,
484486
>,
485487
>,
@@ -534,6 +536,7 @@ pub struct MaybeInputsSeenTransition(
534536
payjoin::receive::v2::SessionEvent,
535537
payjoin::receive::v2::Receiver<payjoin::receive::v2::OutputsUnknown>,
536538
payjoin::receive::Error,
539+
payjoin::receive::v2::Receiver<payjoin::receive::v2::HasReplyableError>,
537540
>,
538541
>,
539542
>,
@@ -586,6 +589,7 @@ pub struct OutputsUnknownTransition(
586589
payjoin::receive::v2::SessionEvent,
587590
payjoin::receive::v2::Receiver<payjoin::receive::v2::WantsOutputs>,
588591
payjoin::receive::Error,
592+
payjoin::receive::v2::Receiver<payjoin::receive::v2::HasReplyableError>,
589593
>,
590594
>,
591595
>,

payjoin/src/core/persist.rs

Lines changed: 96 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,14 @@ impl<Event, NextState, CurrentState, Err>
9191
}
9292

9393
/// A transition that can be either fatal, transient, or a state transition.
94-
pub struct MaybeFatalTransition<Event, NextState, Err>(
95-
pub(crate) Result<AcceptNextState<Event, NextState>, Rejection<Event, Err>>,
94+
pub struct MaybeFatalTransition<Event, NextState, Err, ErrorState = ()>(
95+
pub(crate) Result<AcceptNextState<Event, NextState>, Rejection<Event, Err, ErrorState>>,
9696
);
9797

98-
impl<Event, NextState, Err> MaybeFatalTransition<Event, NextState, Err> {
98+
impl<Event, NextState, Err, ErrorState> MaybeFatalTransition<Event, NextState, Err, ErrorState>
99+
where
100+
ErrorState: fmt::Debug,
101+
{
99102
#[inline]
100103
pub(crate) fn fatal(event: Event, error: Err) -> Self {
101104
MaybeFatalTransition(Err(Rejection::fatal(event, error)))
@@ -111,10 +114,15 @@ impl<Event, NextState, Err> MaybeFatalTransition<Event, NextState, Err> {
111114
MaybeFatalTransition(Ok(AcceptNextState(event, next_state)))
112115
}
113116

117+
#[inline]
118+
pub(crate) fn replyable_error(event: Event, error_state: ErrorState, error: Err) -> Self {
119+
MaybeFatalTransition(Err(Rejection::replyable_error(event, error_state, error)))
120+
}
121+
114122
pub fn save<P>(
115123
self,
116124
persister: &P,
117-
) -> Result<NextState, PersistedError<Err, P::InternalStorageError>>
125+
) -> Result<NextState, PersistedError<Err, P::InternalStorageError, ErrorState>>
118126
where
119127
P: SessionPersister<SessionEvent = Event>,
120128
Err: std::error::Error,
@@ -217,16 +225,21 @@ pub enum AcceptOptionalTransition<Event, NextState, CurrentState> {
217225
}
218226

219227
/// Wrapper representing a fatal or transient rejection of a state transition.
220-
pub enum Rejection<Event, Err> {
228+
pub enum Rejection<Event, Err, ErrorState = ()> {
221229
Fatal(RejectFatal<Event, Err>),
222230
Transient(RejectTransient<Err>),
231+
ReplyableError(RejectReplyableError<Event, ErrorState, Err>),
223232
}
224233

225-
impl<Event, Err> Rejection<Event, Err> {
234+
impl<Event, Err, ErrorState> Rejection<Event, Err, ErrorState> {
226235
#[inline]
227236
pub fn fatal(event: Event, error: Err) -> Self { Rejection::Fatal(RejectFatal(event, error)) }
228237
#[inline]
229238
pub fn transient(error: Err) -> Self { Rejection::Transient(RejectTransient(error)) }
239+
#[inline]
240+
pub fn replyable_error(event: Event, error_state: ErrorState, error: Err) -> Self {
241+
Rejection::ReplyableError(RejectReplyableError(event, error_state, error))
242+
}
230243
}
231244

232245
/// Represents a fatal rejection of a state transition.
@@ -235,6 +248,13 @@ pub struct RejectFatal<Event, Err>(pub(crate) Event, pub(crate) Err);
235248
/// Represents a transient rejection of a state transition.
236249
/// When this error occurs, the session should resume from its current state.
237250
pub struct RejectTransient<Err>(pub(crate) Err);
251+
/// Represents a replyable error that transitions to an error state but keeps the session open.
252+
/// When this error occurs, the session transitions to the ErrorState.
253+
pub struct RejectReplyableError<Event, ErrorState, Err>(
254+
pub(crate) Event,
255+
pub(crate) ErrorState,
256+
pub(crate) Err,
257+
);
238258
/// Represents a bad initial inputs to the state machine.
239259
/// When this error occurs, the session cannot be created.
240260
/// The wrapper contains the error and should be returned to the caller.
@@ -248,15 +268,18 @@ impl<Err: std::error::Error> fmt::Display for RejectTransient<Err> {
248268
}
249269

250270
/// Error type that represents all possible errors that can be returned when processing a state transition
251-
#[derive(Debug, Clone, PartialEq, Eq)]
252-
pub struct PersistedError<ApiError: std::error::Error, StorageError: std::error::Error>(
253-
InternalPersistedError<ApiError, StorageError>,
254-
);
255-
256-
impl<ApiErr, StorageErr> PersistedError<ApiErr, StorageErr>
271+
#[derive(Debug)]
272+
pub struct PersistedError<
273+
ApiError: std::error::Error,
274+
StorageError: std::error::Error,
275+
ErrorState: fmt::Debug = (),
276+
>(InternalPersistedError<ApiError, StorageError, ErrorState>);
277+
278+
impl<ApiErr, StorageErr, ErrorState> PersistedError<ApiErr, StorageErr, ErrorState>
257279
where
258280
StorageErr: std::error::Error,
259281
ApiErr: std::error::Error,
282+
ErrorState: fmt::Debug,
260283
{
261284
#[allow(dead_code)]
262285
pub fn storage_error(self) -> Option<StorageErr> {
@@ -268,7 +291,9 @@ where
268291

269292
pub fn api_error(self) -> Option<ApiErr> {
270293
match self.0 {
271-
InternalPersistedError::Fatal(e) | InternalPersistedError::Transient(e) => Some(e),
294+
InternalPersistedError::Fatal(e)
295+
| InternalPersistedError::Transient(e)
296+
| InternalPersistedError::FatalWithState(e, _) => Some(e),
272297
_ => None,
273298
}
274299
}
@@ -282,46 +307,61 @@ where
282307

283308
pub fn api_error_ref(&self) -> Option<&ApiErr> {
284309
match &self.0 {
285-
InternalPersistedError::Fatal(e) | InternalPersistedError::Transient(e) => Some(e),
310+
InternalPersistedError::Fatal(e)
311+
| InternalPersistedError::Transient(e)
312+
| InternalPersistedError::FatalWithState(e, _) => Some(e),
313+
_ => None,
314+
}
315+
}
316+
317+
pub fn error_state(self) -> Option<ErrorState> {
318+
match self.0 {
319+
InternalPersistedError::FatalWithState(_, state) => Some(state),
286320
_ => None,
287321
}
288322
}
289323
}
290324

291-
impl<ApiError: std::error::Error, StorageError: std::error::Error>
292-
From<InternalPersistedError<ApiError, StorageError>>
293-
for PersistedError<ApiError, StorageError>
325+
impl<ApiError: std::error::Error, StorageError: std::error::Error, ErrorState: fmt::Debug>
326+
From<InternalPersistedError<ApiError, StorageError, ErrorState>>
327+
for PersistedError<ApiError, StorageError, ErrorState>
294328
{
295-
fn from(value: InternalPersistedError<ApiError, StorageError>) -> Self { PersistedError(value) }
329+
fn from(value: InternalPersistedError<ApiError, StorageError, ErrorState>) -> Self {
330+
PersistedError(value)
331+
}
296332
}
297333

298-
impl<ApiError: std::error::Error, StorageError: std::error::Error> std::error::Error
299-
for PersistedError<ApiError, StorageError>
334+
impl<ApiError: std::error::Error, StorageError: std::error::Error, ErrorState: fmt::Debug>
335+
std::error::Error for PersistedError<ApiError, StorageError, ErrorState>
300336
{
301337
}
302338

303-
impl<ApiError: std::error::Error, StorageError: std::error::Error> fmt::Display
304-
for PersistedError<ApiError, StorageError>
339+
impl<ApiError: std::error::Error, StorageError: std::error::Error, ErrorState: fmt::Debug>
340+
fmt::Display for PersistedError<ApiError, StorageError, ErrorState>
305341
{
306342
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
307343
match &self.0 {
308344
InternalPersistedError::Transient(err) => write!(f, "Transient error: {err}"),
309-
InternalPersistedError::Fatal(err) => write!(f, "Fatal error: {err}"),
345+
InternalPersistedError::Fatal(err) | InternalPersistedError::FatalWithState(err, _) =>
346+
write!(f, "Fatal error: {err}"),
310347
InternalPersistedError::Storage(err) => write!(f, "Storage error: {err}"),
311348
}
312349
}
313350
}
314351

315-
#[derive(Debug, Clone, PartialEq, Eq)]
316-
pub(crate) enum InternalPersistedError<InternalApiError, StorageErr>
352+
#[derive(Debug)]
353+
pub(crate) enum InternalPersistedError<InternalApiError, StorageErr, ErrorState = ()>
317354
where
318355
InternalApiError: std::error::Error,
319356
StorageErr: std::error::Error,
357+
ErrorState: fmt::Debug,
320358
{
321359
/// Error indicating that the session should be retried from the same state
322360
Transient(InternalApiError),
323361
/// Error indicating that the session is terminally closed
324362
Fatal(InternalApiError),
363+
/// Fatal error that results in a state transition to ErrorState
364+
FatalWithState(InternalApiError, ErrorState),
325365
/// Error indicating that application failed to save the session event. This should be treated as a transient error
326366
/// but is represented as a separate error because this error is propagated from the application's storage layer
327367
Storage(StorageErr),
@@ -390,6 +430,8 @@ trait InternalSessionPersister: SessionPersister {
390430
Err(InternalPersistedError::Transient(err).into()),
391431
Err(Rejection::Fatal(RejectFatal(event, err))) =>
392432
Err(self.handle_fatal_reject(RejectFatal(event, err)).into()),
433+
Err(Rejection::ReplyableError(reject_replyable_error)) =>
434+
Err(self.handle_replyable_error_reject(reject_replyable_error).into()),
393435
}
394436
}
395437

@@ -425,6 +467,8 @@ trait InternalSessionPersister: SessionPersister {
425467
Err(self.handle_fatal_reject(reject_fatal).into()),
426468
Err(Rejection::Transient(RejectTransient(err))) =>
427469
Err(InternalPersistedError::Transient(err).into()),
470+
Err(Rejection::ReplyableError(reject_replyable_error)) =>
471+
Err(self.handle_replyable_error_reject(reject_replyable_error).into()),
428472
}
429473
}
430474
/// Save a transition that can result in:
@@ -458,6 +502,8 @@ trait InternalSessionPersister: SessionPersister {
458502
Err(self.handle_fatal_reject(reject_fatal).into()),
459503
Err(Rejection::Transient(RejectTransient(err))) =>
460504
Err(InternalPersistedError::Transient(err).into()),
505+
Err(Rejection::ReplyableError(reject_replyable_error)) =>
506+
Err(self.handle_replyable_error_reject(reject_replyable_error).into()),
461507
}
462508
}
463509

@@ -479,12 +525,13 @@ trait InternalSessionPersister: SessionPersister {
479525
}
480526

481527
/// Save a transition that can be a fatal error, transient error or a state transition
482-
fn save_maybe_fatal_error_transition<NextState, Err>(
528+
fn save_maybe_fatal_error_transition<NextState, Err, ErrorState>(
483529
&self,
484-
state_transition: MaybeFatalTransition<Self::SessionEvent, NextState, Err>,
485-
) -> Result<NextState, PersistedError<Err, Self::InternalStorageError>>
530+
state_transition: MaybeFatalTransition<Self::SessionEvent, NextState, Err, ErrorState>,
531+
) -> Result<NextState, PersistedError<Err, Self::InternalStorageError, ErrorState>>
486532
where
487533
Err: std::error::Error,
534+
ErrorState: fmt::Debug,
488535
{
489536
match state_transition.0 {
490537
Ok(AcceptNextState(event, next_state)) => {
@@ -499,17 +546,20 @@ trait InternalSessionPersister: SessionPersister {
499546
// No event to store for transient errors
500547
Err(InternalPersistedError::Transient(err).into())
501548
}
549+
Rejection::ReplyableError(reject_replyable_error) =>
550+
Err(self.handle_replyable_error_reject(reject_replyable_error).into()),
502551
}
503552
}
504553
}
505554
}
506555

507-
fn handle_fatal_reject<Err>(
556+
fn handle_fatal_reject<Err, ErrorState>(
508557
&self,
509558
reject_fatal: RejectFatal<Self::SessionEvent, Err>,
510-
) -> InternalPersistedError<Err, Self::InternalStorageError>
559+
) -> InternalPersistedError<Err, Self::InternalStorageError, ErrorState>
511560
where
512561
Err: std::error::Error,
562+
ErrorState: fmt::Debug,
513563
{
514564
let RejectFatal(event, error) = reject_fatal;
515565
if let Err(e) = self.save_event(event) {
@@ -522,6 +572,22 @@ trait InternalSessionPersister: SessionPersister {
522572

523573
InternalPersistedError::Fatal(error)
524574
}
575+
576+
fn handle_replyable_error_reject<Err, ErrorState>(
577+
&self,
578+
reject_replyable_error: RejectReplyableError<Self::SessionEvent, ErrorState, Err>,
579+
) -> InternalPersistedError<Err, Self::InternalStorageError, ErrorState>
580+
where
581+
Err: std::error::Error,
582+
ErrorState: fmt::Debug,
583+
{
584+
let RejectReplyableError(event, error_state, error) = reject_replyable_error;
585+
if let Err(e) = self.save_event(event) {
586+
return InternalPersistedError::Storage(e);
587+
}
588+
// For replyable errors, don't close the session - keep it open for error response
589+
InternalPersistedError::FatalWithState(error, error_state)
590+
}
525591
}
526592

527593
impl<T: SessionPersister> InternalSessionPersister for T {}

0 commit comments

Comments
 (0)