11use std:: sync:: Arc ;
22
3+ use payjoin:: error:: ReplayErrorVariant as CoreReplayErrorVariant ;
4+ use payjoin:: persist:: PersistedErrorVariant ;
35use payjoin:: receive;
46
5- use crate :: error:: { FfiValidationError , ImplementationError } ;
7+ use crate :: error:: {
8+ FfiValidationError , ImplementationError , ReplayErrorKind as FfiReplayErrorKind ,
9+ ReplayInvalidEventKind as FfiReplayInvalidEventKind ,
10+ } ;
11+ use crate :: receive:: HasReplyableError ;
612use crate :: uri:: error:: IntoUrlError ;
713
814/// The top-level error type for the payjoin receiver
@@ -40,20 +46,36 @@ impl From<receive::Error> for ReceiverError {
4046
4147/// Error that may occur during state machine transitions
4248#[ derive( Debug , thiserror:: Error , uniffi:: Error ) ]
43- #[ error( transparent) ]
4449pub enum ReceiverPersistedError {
45- /// rust-payjoin receiver error
46- #[ error( transparent) ]
47- Receiver ( ReceiverError ) ,
48- /// Storage error that could occur at application storage layer
50+ /// Storage failure while persisting session state.
4951 #[ error( transparent) ]
5052 Storage ( Arc < ImplementationError > ) ,
53+ /// Retry the same receiver transition from the current session state.
54+ #[ error( "Transient receiver error: {error}" ) ]
55+ Transient { error : ReceiverError } ,
56+ /// The receiver session terminated and should not be resumed.
57+ #[ error( "Fatal receiver error: {error}" ) ]
58+ Fatal { error : ReceiverError } ,
59+ /// The receiver transitioned into a replyable error state.
60+ ///
61+ /// Continue the protocol with the returned `state` to reply to the sender.
62+ #[ error( "Fatal receiver error with state: {error}" ) ]
63+ FatalWithState { error : ReceiverError , state : Arc < HasReplyableError > } ,
64+ /// Unexpected error shape that should not occur for receiver transitions.
65+ #[ error( "An unexpected error occurred" ) ]
66+ Unexpected ,
5167}
5268
5369impl From < ImplementationError > for ReceiverPersistedError {
5470 fn from ( value : ImplementationError ) -> Self { ReceiverPersistedError :: Storage ( Arc :: new ( value) ) }
5571}
5672
73+ impl From < crate :: error:: ForeignError > for ReceiverPersistedError {
74+ fn from ( value : crate :: error:: ForeignError ) -> Self {
75+ ReceiverPersistedError :: from ( ImplementationError :: new ( value) )
76+ }
77+ }
78+
5779macro_rules! impl_persisted_error_from {
5880 (
5981 $api_error_ty: ty,
@@ -64,16 +86,16 @@ macro_rules! impl_persisted_error_from {
6486 S : std:: error:: Error + Send + Sync + ' static ,
6587 {
6688 fn from( err: payjoin:: persist:: PersistedError <$api_error_ty, S >) -> Self {
67- if err. storage_error_ref( ) . is_some( ) {
68- if let Some ( storage_err) = err. storage_error( ) {
69- return ReceiverPersistedError :: from( ImplementationError :: new( storage_err) ) ;
70- }
71- return ReceiverPersistedError :: Receiver ( ReceiverError :: Unexpected ) ;
72- }
73- if let Some ( api_err) = err. api_error( ) {
74- return ReceiverPersistedError :: Receiver ( $receiver_arm( api_err) ) ;
89+ match err. into_variant( ) {
90+ PersistedErrorVariant :: Storage ( storage_err) =>
91+ ReceiverPersistedError :: from( ImplementationError :: new( storage_err) ) ,
92+ PersistedErrorVariant :: Transient ( api_err) =>
93+ ReceiverPersistedError :: Transient { error: $receiver_arm( api_err) } ,
94+ PersistedErrorVariant :: Fatal ( api_err) =>
95+ ReceiverPersistedError :: Fatal { error: $receiver_arm( api_err) } ,
96+ PersistedErrorVariant :: FatalWithState ( _, _) =>
97+ ReceiverPersistedError :: Unexpected ,
7598 }
76- ReceiverPersistedError :: Receiver ( ReceiverError :: Unexpected )
7799 }
78100 }
79101 } ;
@@ -89,6 +111,47 @@ impl_persisted_error_from!(payjoin::IntoUrlError, |api_err: payjoin::IntoUrlErro
89111 ReceiverError :: IntoUrl ( Arc :: new( api_err. into( ) ) )
90112} ) ;
91113
114+ impl_persisted_error_from ! (
115+ payjoin:: ImplementationError ,
116+ |api_err: payjoin:: ImplementationError | {
117+ ReceiverError :: Implementation ( Arc :: new( api_err. into( ) ) )
118+ }
119+ ) ;
120+
121+ impl < S >
122+ From <
123+ payjoin:: persist:: PersistedError <
124+ receive:: Error ,
125+ S ,
126+ payjoin:: receive:: v2:: Receiver < payjoin:: receive:: v2:: HasReplyableError > ,
127+ > ,
128+ > for ReceiverPersistedError
129+ where
130+ S : std:: error:: Error + Send + Sync + ' static ,
131+ {
132+ fn from (
133+ err : payjoin:: persist:: PersistedError <
134+ receive:: Error ,
135+ S ,
136+ payjoin:: receive:: v2:: Receiver < payjoin:: receive:: v2:: HasReplyableError > ,
137+ > ,
138+ ) -> Self {
139+ match err. into_variant ( ) {
140+ PersistedErrorVariant :: Storage ( storage_err) =>
141+ ReceiverPersistedError :: from ( ImplementationError :: new ( storage_err) ) ,
142+ PersistedErrorVariant :: Transient ( api_err) =>
143+ ReceiverPersistedError :: Transient { error : api_err. into ( ) } ,
144+ PersistedErrorVariant :: Fatal ( api_err) =>
145+ ReceiverPersistedError :: Fatal { error : api_err. into ( ) } ,
146+ PersistedErrorVariant :: FatalWithState ( api_err, state) =>
147+ ReceiverPersistedError :: FatalWithState {
148+ error : api_err. into ( ) ,
149+ state : Arc :: new ( state. into ( ) ) ,
150+ } ,
151+ }
152+ }
153+ }
154+
92155/// Error that may occur when building a receiver session.
93156#[ derive( Debug , thiserror:: Error , uniffi:: Error ) ]
94157#[ non_exhaustive]
@@ -231,9 +294,233 @@ impl From<FfiValidationError> for InputPairError {
231294 fn from ( value : FfiValidationError ) -> Self { InputPairError :: FfiValidation ( value) }
232295}
233296
234- /// Error that may occur when a receiver event log is replayed
297+ /// Error that may occur when a receiver event log is replayed.
235298#[ derive( Debug , thiserror:: Error , uniffi:: Object ) ]
236- #[ error( transparent) ]
237- pub struct ReceiverReplayError (
238- #[ from] payjoin:: error:: ReplayError < receive:: v2:: ReceiveSession , receive:: v2:: SessionEvent > ,
239- ) ;
299+ #[ error( "{message}" ) ]
300+ pub struct ReceiverReplayError {
301+ kind : FfiReplayErrorKind ,
302+ message : String ,
303+ invalid_event_kind : Option < FfiReplayInvalidEventKind > ,
304+ expired_at_unix_seconds : Option < u32 > ,
305+ persistence_failure : Option < Arc < ImplementationError > > ,
306+ }
307+
308+ impl From < payjoin:: error:: ReplayError < receive:: v2:: ReceiveSession , receive:: v2:: SessionEvent > >
309+ for ReceiverReplayError
310+ {
311+ fn from (
312+ value : payjoin:: error:: ReplayError < receive:: v2:: ReceiveSession , receive:: v2:: SessionEvent > ,
313+ ) -> Self {
314+ let message = value. to_string ( ) ;
315+ match value. into_variant ( ) {
316+ CoreReplayErrorVariant :: NoEvents => Self {
317+ kind : FfiReplayErrorKind :: NoEvents ,
318+ message,
319+ invalid_event_kind : None ,
320+ expired_at_unix_seconds : None ,
321+ persistence_failure : None ,
322+ } ,
323+ CoreReplayErrorVariant :: InvalidFirstEvent => Self {
324+ kind : FfiReplayErrorKind :: InvalidEvent ,
325+ message,
326+ invalid_event_kind : Some ( FfiReplayInvalidEventKind :: InitialEvent ) ,
327+ expired_at_unix_seconds : None ,
328+ persistence_failure : None ,
329+ } ,
330+ CoreReplayErrorVariant :: InvalidEventForState => Self {
331+ kind : FfiReplayErrorKind :: InvalidEvent ,
332+ message,
333+ invalid_event_kind : Some ( FfiReplayInvalidEventKind :: SessionTransition ) ,
334+ expired_at_unix_seconds : None ,
335+ persistence_failure : None ,
336+ } ,
337+ CoreReplayErrorVariant :: Expired { expired_at_unix_seconds } => Self {
338+ kind : FfiReplayErrorKind :: Expired ,
339+ message,
340+ invalid_event_kind : None ,
341+ expired_at_unix_seconds : Some ( expired_at_unix_seconds) ,
342+ persistence_failure : None ,
343+ } ,
344+ CoreReplayErrorVariant :: PersistenceFailure ( error) => Self {
345+ kind : FfiReplayErrorKind :: PersistenceFailure ,
346+ message,
347+ invalid_event_kind : None ,
348+ expired_at_unix_seconds : None ,
349+ persistence_failure : Some ( Arc :: new ( error. into ( ) ) ) ,
350+ } ,
351+ }
352+ }
353+ }
354+
355+ #[ uniffi:: export]
356+ impl ReceiverReplayError {
357+ pub fn kind ( & self ) -> FfiReplayErrorKind { self . kind }
358+
359+ pub fn message ( & self ) -> String { self . message . clone ( ) }
360+
361+ pub fn invalid_event_kind ( & self ) -> Option < FfiReplayInvalidEventKind > {
362+ self . invalid_event_kind
363+ }
364+
365+ pub fn expired_at_unix_seconds ( & self ) -> Option < u32 > { self . expired_at_unix_seconds }
366+
367+ pub fn persistence_failure ( & self ) -> Option < Arc < ImplementationError > > {
368+ self . persistence_failure . clone ( )
369+ }
370+ }
371+
372+ #[ cfg( all( test, feature = "_test-utils" ) ) ]
373+ mod tests {
374+ use std:: sync:: { Arc , Mutex } ;
375+ use std:: time:: Duration ;
376+
377+ use payjoin_test_utils:: TestServices ;
378+ use tokio:: time:: sleep;
379+
380+ use super :: ReceiverPersistedError ;
381+ use crate :: error:: { ForeignError , ReplayErrorKind } ;
382+ use crate :: receive:: {
383+ InitializedTransitionOutcome , JsonReceiverSessionPersister , ReceiverBuilder ,
384+ } ;
385+ use crate :: send:: { JsonSenderSessionPersister , SenderBuilder } ;
386+
387+ #[ derive( Default ) ]
388+ struct InMemoryReceiverPersister {
389+ events : Mutex < Vec < String > > ,
390+ }
391+
392+ impl JsonReceiverSessionPersister for InMemoryReceiverPersister {
393+ fn save ( & self , event : String ) -> Result < ( ) , ForeignError > {
394+ self . events . lock ( ) . expect ( "lock" ) . push ( event) ;
395+ Ok ( ( ) )
396+ }
397+
398+ fn load ( & self ) -> Result < Vec < String > , ForeignError > {
399+ Ok ( self . events . lock ( ) . expect ( "lock" ) . clone ( ) )
400+ }
401+
402+ fn close ( & self ) -> Result < ( ) , ForeignError > { Ok ( ( ) ) }
403+ }
404+
405+ #[ derive( Default ) ]
406+ struct InMemorySenderPersister {
407+ events : Mutex < Vec < String > > ,
408+ }
409+
410+ impl JsonSenderSessionPersister for InMemorySenderPersister {
411+ fn save ( & self , event : String ) -> Result < ( ) , ForeignError > {
412+ self . events . lock ( ) . expect ( "lock" ) . push ( event) ;
413+ Ok ( ( ) )
414+ }
415+
416+ fn load ( & self ) -> Result < Vec < String > , ForeignError > {
417+ Ok ( self . events . lock ( ) . expect ( "lock" ) . clone ( ) )
418+ }
419+
420+ fn close ( & self ) -> Result < ( ) , ForeignError > { Ok ( ( ) ) }
421+ }
422+
423+ #[ derive( Default ) ]
424+ struct EmptyReceiverPersister ;
425+
426+ impl JsonReceiverSessionPersister for EmptyReceiverPersister {
427+ fn save ( & self , _: String ) -> Result < ( ) , ForeignError > { Ok ( ( ) ) }
428+
429+ fn load ( & self ) -> Result < Vec < String > , ForeignError > { Ok ( Vec :: new ( ) ) }
430+
431+ fn close ( & self ) -> Result < ( ) , ForeignError > { Ok ( ( ) ) }
432+ }
433+
434+ struct RejectBroadcast ;
435+
436+ impl crate :: receive:: CanBroadcast for RejectBroadcast {
437+ fn callback ( & self , _: Vec < u8 > ) -> Result < bool , ForeignError > { Ok ( false ) }
438+ }
439+
440+ async fn post_request ( services : & TestServices , request : crate :: Request ) -> Vec < u8 > {
441+ let response = services
442+ . http_agent ( )
443+ . post ( request. url )
444+ . header ( "Content-Type" , request. content_type )
445+ . body ( request. body )
446+ . send ( )
447+ . await
448+ . expect ( "request should succeed" ) ;
449+ response. bytes ( ) . await . expect ( "response bytes" ) . to_vec ( )
450+ }
451+
452+ #[ tokio:: test]
453+ async fn test_receiver_persisted_error_preserves_fatal_with_state ( ) {
454+ let services = TestServices :: initialize ( ) . await . expect ( "services initialize" ) ;
455+ services. wait_for_services_ready ( ) . await . expect ( "services ready" ) ;
456+
457+ let receiver_persister = Arc :: new ( InMemoryReceiverPersister :: default ( ) ) ;
458+ let initialized = ReceiverBuilder :: new (
459+ "2N47mmrWXsNBvQR6k78hWJoTji57zXwNcU7" . to_string ( ) ,
460+ services. directory_url ( ) ,
461+ Arc :: new ( services. fetch_ohttp_keys ( ) . await . expect ( "fetch ohttp keys" ) . into ( ) ) ,
462+ )
463+ . expect ( "receiver builder" )
464+ . build ( )
465+ . save ( receiver_persister. clone ( ) )
466+ . expect ( "save initialized receiver" ) ;
467+
468+ let sender_persister = Arc :: new ( InMemorySenderPersister :: default ( ) ) ;
469+ let with_reply_key =
470+ SenderBuilder :: new ( crate :: test_utils:: original_psbt ( ) , Arc :: new ( initialized. pj_uri ( ) ) )
471+ . expect ( "sender builder" )
472+ . build_recommended ( 1000 )
473+ . expect ( "build sender" )
474+ . save ( sender_persister)
475+ . expect ( "save sender" ) ;
476+
477+ let sender_request =
478+ with_reply_key. create_v2_post_request ( services. ohttp_relay_url ( ) ) . unwrap ( ) ;
479+ let _ = post_request ( & services, sender_request. request ) . await ;
480+
481+ let mut initialized = initialized;
482+ let unchecked = loop {
483+ let poll = initialized. create_poll_request ( services. ohttp_relay_url ( ) ) . unwrap ( ) ;
484+ let response = post_request ( & services, poll. request ) . await ;
485+ let outcome = initialized
486+ . process_response ( & response, poll. client_response . as_ref ( ) )
487+ . save ( receiver_persister. clone ( ) )
488+ . expect ( "persist initialized transition" ) ;
489+ match outcome {
490+ InitializedTransitionOutcome :: Progress { inner } => break inner,
491+ InitializedTransitionOutcome :: Stasis { inner } => {
492+ initialized = Arc :: unwrap_or_clone ( inner) ;
493+ sleep ( Duration :: from_millis ( 20 ) ) . await ;
494+ }
495+ }
496+ } ;
497+
498+ let error = unchecked
499+ . check_broadcast_suitability ( None , Arc :: new ( RejectBroadcast ) )
500+ . expect ( "validation inputs" )
501+ . save ( receiver_persister) ;
502+ let error = match error {
503+ Ok ( _) => panic ! ( "non-broadcastable original should produce replyable error state" ) ,
504+ Err ( error) => error,
505+ } ;
506+
507+ match error {
508+ ReceiverPersistedError :: FatalWithState { state, .. } => {
509+ state. create_error_request ( services. ohttp_relay_url ( ) ) . expect ( "state preserved" ) ;
510+ }
511+ other => panic ! ( "unexpected receiver persisted error: {other:?}" ) ,
512+ }
513+ }
514+
515+ #[ test]
516+ fn test_receiver_replay_error_exposes_no_events_kind ( ) {
517+ let error =
518+ match crate :: receive:: replay_receiver_event_log ( Arc :: new ( EmptyReceiverPersister ) ) {
519+ Ok ( _) => panic ! ( "empty event log should fail" ) ,
520+ Err ( error) => error,
521+ } ;
522+ assert_eq ! ( error. kind( ) , ReplayErrorKind :: NoEvents ) ;
523+ assert ! ( error. persistence_failure( ) . is_none( ) ) ;
524+ assert ! ( error. expired_at_unix_seconds( ) . is_none( ) ) ;
525+ }
526+ }
0 commit comments