@@ -35,6 +35,10 @@ const W_ID: usize = 36;
3535const W_ROLE : usize = 25 ;
3636const W_STATUS : usize = 15 ;
3737
38+ /// Delay before retrying a transiently failed state transition, so a
39+ /// misbehaving directory or relay is not hammered in a tight loop.
40+ const TRANSIENT_RETRY_DELAY : std:: time:: Duration = std:: time:: Duration :: from_secs ( 5 ) ;
41+
3842#[ derive( Clone ) ]
3943pub ( crate ) struct App {
4044 config : Config ,
@@ -697,27 +701,37 @@ impl App {
697701
698702 async fn post_original_proposal (
699703 & self ,
700- sender : Sender < WithReplyKey > ,
704+ mut sender : Sender < WithReplyKey > ,
701705 persister : & SenderPersister ,
702706 ) -> Result < ( ) > {
703- let ( response, ctx) =
704- self . post_via_relay ( |relay| sender. create_v2_post_request ( relay) ) . await ?;
705- let sender = sender. process_response ( & response. bytes ( ) . await ?, ctx) . save ( persister) ?;
706- println ! ( "Posted Original PSBT..." ) ;
707- self . get_proposed_payjoin_psbt ( sender, persister) . await
707+ loop {
708+ let ( response, ctx) =
709+ self . post_via_relay ( |relay| sender. create_v2_post_request ( relay) ) . await ?;
710+ match sender. process_response ( & response. bytes ( ) . await ?, ctx) . save ( persister) {
711+ Ok ( sender) => {
712+ println ! ( "Posted Original PSBT..." ) ;
713+ return self . get_proposed_payjoin_psbt ( sender, persister) . await ;
714+ }
715+ Err ( e) if e. is_transient ( ) => {
716+ tracing:: debug!( "Transient error posting original proposal, retrying: {e:?}" ) ;
717+ sender = e. transient_state ( ) . expect ( "transient error carries current state" ) ;
718+ tokio:: time:: sleep ( TRANSIENT_RETRY_DELAY ) . await ;
719+ }
720+ Err ( e) => return Err ( e. into ( ) ) ,
721+ }
722+ }
708723 }
709724
710725 async fn get_proposed_payjoin_psbt (
711726 & self ,
712- sender : Sender < PollingForProposal > ,
727+ mut sender : Sender < PollingForProposal > ,
713728 persister : & SenderPersister ,
714729 ) -> Result < ( ) > {
715- let mut session = sender. clone ( ) ;
716730 // Long poll until we get a response
717731 loop {
718732 let ( response, ctx) =
719- self . post_via_relay ( |relay| session . create_poll_request ( relay) ) . await ?;
720- let res = session . process_response ( & response. bytes ( ) . await ?, ctx) . save ( persister) ;
733+ self . post_via_relay ( |relay| sender . create_poll_request ( relay) ) . await ?;
734+ let res = sender . process_response ( & response. bytes ( ) . await ?, ctx) . save ( persister) ;
721735 match res {
722736 Ok ( OptionalTransitionOutcome :: Progress ( psbt) ) => {
723737 println ! ( "Proposal received. Processing..." ) ;
@@ -726,8 +740,12 @@ impl App {
726740 }
727741 Ok ( OptionalTransitionOutcome :: Stasis ( current_state) ) => {
728742 println ! ( "No response yet." ) ;
729- session = current_state;
730- continue ;
743+ sender = current_state;
744+ }
745+ Err ( e) if e. is_transient ( ) => {
746+ tracing:: debug!( "Transient error polling for proposal, retrying: {e:?}" ) ;
747+ sender = e. transient_state ( ) . expect ( "transient error carries current state" ) ;
748+ tokio:: time:: sleep ( TRANSIENT_RETRY_DELAY ) . await ;
731749 }
732750 Err ( re) => {
733751 println ! ( "{re}" ) ;
@@ -740,10 +758,9 @@ impl App {
740758
741759 async fn long_poll_fallback (
742760 & self ,
743- session : Receiver < Initialized > ,
761+ mut session : Receiver < Initialized > ,
744762 persister : & ReceiverPersister ,
745763 ) -> Result < Receiver < UncheckedOriginalPayload > > {
746- let mut session = session;
747764 loop {
748765 println ! ( "Polling receive request..." ) ;
749766 let ( ohttp_response, context) =
@@ -758,7 +775,11 @@ impl App {
758775 }
759776 Ok ( OptionalTransitionOutcome :: Stasis ( current_state) ) => {
760777 session = current_state;
761- continue ;
778+ }
779+ Err ( e) if e. is_transient ( ) => {
780+ tracing:: debug!( "Transient error polling for request, retrying: {e:?}" ) ;
781+ session = e. transient_state ( ) . expect ( "transient error carries current state" ) ;
782+ tokio:: time:: sleep ( TRANSIENT_RETRY_DELAY ) . await ;
762783 }
763784 Err ( e) => return Err ( e. into ( ) ) ,
764785 }
@@ -946,24 +967,34 @@ impl App {
946967
947968 async fn send_payjoin_proposal (
948969 & self ,
949- proposal : Receiver < PayjoinProposal > ,
970+ mut proposal : Receiver < PayjoinProposal > ,
950971 persister : & ReceiverPersister ,
951972 ) -> Result < ( ) > {
952- let ( res, ohttp_ctx) = self
953- . post_via_relay ( |relay| {
954- proposal
955- . create_post_request ( relay)
956- . map_err ( |e| anyhow ! ( "v2 req extraction failed {}" , e) )
957- } )
958- . await ?;
959- let payjoin_psbt = proposal. psbt ( ) . clone ( ) ;
960- let session = proposal. process_response ( & res. bytes ( ) . await ?, ohttp_ctx) . save ( persister) ?;
961- println ! (
962- "Response successful. Watch mempool for successful Payjoin. TXID: {}" ,
963- payjoin_psbt. extract_tx_unchecked_fee_rate( ) . compute_txid( )
964- ) ;
965-
966- return self . monitor_payjoin_proposal ( session, persister) . await ;
973+ loop {
974+ let ( res, ohttp_ctx) = self
975+ . post_via_relay ( |relay| {
976+ proposal
977+ . create_post_request ( relay)
978+ . map_err ( |e| anyhow ! ( "v2 req extraction failed {}" , e) )
979+ } )
980+ . await ?;
981+ let payjoin_psbt = proposal. psbt ( ) . clone ( ) ;
982+ match proposal. process_response ( & res. bytes ( ) . await ?, ohttp_ctx) . save ( persister) {
983+ Ok ( session) => {
984+ println ! (
985+ "Response successful. Watch mempool for successful Payjoin. TXID: {}" ,
986+ payjoin_psbt. extract_tx_unchecked_fee_rate( ) . compute_txid( )
987+ ) ;
988+ return self . monitor_payjoin_proposal ( session, persister) . await ;
989+ }
990+ Err ( e) if e. is_transient ( ) => {
991+ tracing:: debug!( "Transient error sending payjoin proposal, retrying: {e:?}" ) ;
992+ proposal = e. transient_state ( ) . expect ( "transient error carries current state" ) ;
993+ tokio:: time:: sleep ( TRANSIENT_RETRY_DELAY ) . await ;
994+ }
995+ Err ( e) => return Err ( e. into ( ) ) ,
996+ }
997+ }
967998 }
968999
9691000 async fn monitor_payjoin_proposal (
@@ -1014,38 +1045,47 @@ impl App {
10141045 /// Handle error by attempting to send an error response over the directory
10151046 async fn handle_error (
10161047 & self ,
1017- session : Receiver < HasReplyableError > ,
1048+ mut session : Receiver < HasReplyableError > ,
10181049 persister : & ReceiverPersister ,
10191050 ) -> Result < ( ) > {
1020- let ( err_response, err_ctx) = self
1021- . post_via_relay ( |relay| {
1022- session
1023- . create_error_request ( relay)
1024- . map_err ( |e| anyhow ! ( "Failed to post error request: {}" , e) )
1025- } )
1026- . await ?;
1027-
1028- let err_bytes = match err_response. bytes ( ) . await {
1029- Ok ( bytes) => bytes,
1030- Err ( e) => return Err ( anyhow ! ( "Failed to get error response bytes: {}" , e) ) ,
1031- } ;
1051+ loop {
1052+ let ( err_response, err_ctx) = self
1053+ . post_via_relay ( |relay| {
1054+ session
1055+ . create_error_request ( relay)
1056+ . map_err ( |e| anyhow ! ( "Failed to post error request: {}" , e) )
1057+ } )
1058+ . await ?;
1059+
1060+ let err_bytes = match err_response. bytes ( ) . await {
1061+ Ok ( bytes) => bytes,
1062+ Err ( e) => return Err ( anyhow ! ( "Failed to get error response bytes: {}" , e) ) ,
1063+ } ;
10321064
1033- if let Err ( e) = session. process_error_response ( & err_bytes, err_ctx) . save ( persister) {
1034- if let Some ( api_err) = e. api_error_ref ( ) {
1035- tracing:: warn!( "Failed to confirm error response delivery: {api_err}" ) ;
1036- }
1037- match e. error_state ( ) {
1038- Some ( _) => {
1039- let id = persister. session_id ( ) ;
1040- println ! (
1041- "Session {id} failed. Run `payjoin-cli cancel {id}` to cancel and broadcast the fallback transaction."
1042- ) ;
1065+ match session. process_error_response ( & err_bytes, err_ctx) . save ( persister) {
1066+ Ok ( _) => return Ok ( ( ) ) ,
1067+ Err ( e) if e. is_transient ( ) => {
1068+ tracing:: debug!( "Transient error posting error response, retrying: {e:?}" ) ;
1069+ session = e. transient_state ( ) . expect ( "transient error carries current state" ) ;
1070+ tokio:: time:: sleep ( TRANSIENT_RETRY_DELAY ) . await ;
1071+ }
1072+ Err ( e) => {
1073+ if let Some ( api_err) = e. api_error_ref ( ) {
1074+ tracing:: warn!( "Failed to confirm error response delivery: {api_err}" ) ;
1075+ }
1076+ match e. error_state ( ) {
1077+ Some ( _) => {
1078+ let id = persister. session_id ( ) ;
1079+ println ! (
1080+ "Session {id} failed. Run `payjoin-cli cancel {id}` to cancel and broadcast the fallback transaction."
1081+ ) ;
1082+ return Ok ( ( ) ) ;
1083+ }
1084+ None => return Err ( anyhow ! ( "Failed to process error response" ) ) ,
1085+ }
10431086 }
1044- None => return Err ( anyhow ! ( "Failed to process error response" ) ) ,
10451087 }
10461088 }
1047-
1048- Ok ( ( ) )
10491089 }
10501090
10511091 async fn post_request ( & self , req : payjoin:: Request ) -> Result < reqwest:: Response > {
0 commit comments