@@ -99,8 +99,17 @@ impl ActionExecutor for SendFiberOutgoingPaymentExecutor {
9999
100100impl SendFiberOutgoingPaymentExecutor {
101101 fn is_permanent_error ( err : & str ) -> bool {
102- // TODO: replace string matching with structured error codes from NetworkActor.
103- err. contains ( "InvalidParameter" )
102+ // InvalidParameter errors are permanent validation errors
103+ if err. contains ( "InvalidParameter" ) {
104+ return true ;
105+ }
106+
107+ // Additional permanent errors that won't be fixed by retrying
108+ let err_lower = err. to_lowercase ( ) ;
109+ err_lower. contains ( "invalid payment request" )
110+ || err_lower. contains ( "invoice expired" )
111+ || err_lower. contains ( "payment hash mismatch" )
112+ || err_lower. contains ( "no path found" )
104113 }
105114}
106115
@@ -143,14 +152,18 @@ impl ActionExecutor for SendLightningOutgoingPaymentExecutor {
143152 Some ( Err ( err) ) => {
144153 let failure_reason =
145154 format ! ( "SendLightningOutgoingPaymentExecutor failure: {:?}" , err) ;
146- if Self :: is_permanent_error ( err) {
155+ if Self :: is_permanent_error ( & err) {
147156 CchTrackingEvent :: PaymentChanged {
148157 payment_hash : self . payment_hash ,
149158 payment_preimage : None ,
150159 status : PaymentStatus :: Failed ,
151160 failure_reason : Some ( failure_reason) ,
152161 }
153162 } else {
163+ tracing:: warn!(
164+ "SendLightningOutgoingPaymentExecutor transient error, will retry. code: {}, error: {}" ,
165+ err. code( ) , err. message( )
166+ ) ;
154167 return Err ( anyhow ! ( failure_reason) ) ;
155168 }
156169 }
@@ -167,8 +180,27 @@ impl ActionExecutor for SendLightningOutgoingPaymentExecutor {
167180}
168181
169182impl SendLightningOutgoingPaymentExecutor {
170- fn is_permanent_error ( status : tonic:: Status ) -> bool {
171- matches ! ( status. code( ) , tonic:: Code :: InvalidArgument )
183+ fn is_permanent_error ( status : & tonic:: Status ) -> bool {
184+ // Check for explicit invalid argument errors
185+ if matches ! ( status. code( ) , tonic:: Code :: InvalidArgument ) {
186+ return true ;
187+ }
188+
189+ // LND often returns Unknown status for validation errors that are permanent.
190+ // Check the error message to identify these cases.
191+ if matches ! ( status. code( ) , tonic:: Code :: Unknown ) {
192+ let msg = status. message ( ) . to_lowercase ( ) ;
193+ // These are validation/policy errors that won't be fixed by retrying
194+ return msg. contains ( "self-payments not allowed" )
195+ || msg. contains ( "invoice is already paid" )
196+ || msg. contains ( "invoice expired" )
197+ || msg. contains ( "incorrect payment amount" )
198+ || msg. contains ( "payment hash mismatch" )
199+ || msg. contains ( "no route" )
200+ || msg. contains ( "unable to find a path to destination" ) ;
201+ }
202+
203+ false
172204 }
173205}
174206
0 commit comments