@@ -12,7 +12,7 @@ use engine_core::{
12
12
chain:: { Chain , ChainService , RpcCredentials } ,
13
13
credentials:: SigningCredential ,
14
14
error:: { AlloyRpcErrorToEngineError , EngineError , RpcErrorKind } ,
15
- execution_options:: { aa:: Erc4337ExecutionOptions , WebhookOptions } ,
15
+ execution_options:: { WebhookOptions , aa:: Erc4337ExecutionOptions } ,
16
16
transaction:: InnerTransaction ,
17
17
userop:: UserOpSigner ,
18
18
} ;
@@ -52,7 +52,7 @@ pub struct ExternalBundlerSendJobData {
52
52
pub webhook_options : Option < Vec < WebhookOptions > > ,
53
53
54
54
pub rpc_credentials : RpcCredentials ,
55
-
55
+
56
56
/// Pregenerated nonce for vault signed tokens
57
57
#[ serde( skip_serializing_if = "Option::is_none" ) ]
58
58
pub pregenerated_nonce : Option < U256 > ,
@@ -129,10 +129,7 @@ pub enum ExternalBundlerSendError {
129
129
} ,
130
130
131
131
#[ error( "Policy restriction error: {reason} (Policy ID: {policy_id})" ) ]
132
- PolicyRestriction {
133
- policy_id : String ,
134
- reason : String ,
135
- } ,
132
+ PolicyRestriction { policy_id : String , reason : String } ,
136
133
137
134
#[ error( "Invalid RPC Credentials: {message}" ) ]
138
135
InvalidRpcCredentials { message : String } ,
@@ -279,11 +276,13 @@ where
279
276
let chain = chain. with_new_default_headers ( chain_auth_headers) ;
280
277
281
278
// 2. Parse Account Salt using the helper method
282
- let salt_data = job_data. execution_options . get_salt_data ( )
283
- . map_err ( |e| ExternalBundlerSendError :: InvalidAccountSalt {
284
- message : e. to_string ( ) ,
285
- } )
286
- . map_err_fail ( ) ?;
279
+ let salt_data = job_data
280
+ . execution_options
281
+ . get_salt_data ( )
282
+ . map_err ( |e| ExternalBundlerSendError :: InvalidAccountSalt {
283
+ message : e. to_string ( ) ,
284
+ } )
285
+ . map_err_fail ( ) ?;
287
286
288
287
// 3. Determine Smart Account
289
288
let smart_account = match job_data. execution_options . smart_account_address {
@@ -321,10 +320,28 @@ where
321
320
. deployment_manager ( )
322
321
. check_deployment_status ( job_data. chain_id , & smart_account. address , is_deployed_check)
323
322
. await
324
- . map_err ( |e| ExternalBundlerSendError :: InternalError {
325
- message : format ! ( "Deployment manager error: {}" , e) ,
326
- } )
327
- . map_err_nack ( Some ( Duration :: from_secs ( 10 ) ) , RequeuePosition :: Last ) ?;
323
+ // we could have redis errors, or RPC errors
324
+ // in case of RPC errors we'd want to see if the RPC error is retryable
325
+ . map_err ( |e| {
326
+ tracing:: error!( error = ?e, "Error in deployment manager" ) ;
327
+ match & e {
328
+ EngineError :: RpcError { kind : k, .. } => {
329
+ let mapped_error = ExternalBundlerSendError :: ChainServiceError {
330
+ chain_id : chain. chain_id ( ) ,
331
+ message : format ! ( "Deployment manager error: {}" , e) ,
332
+ } ;
333
+ if is_retryable_rpc_error ( k) {
334
+ mapped_error. nack ( Some ( Duration :: from_secs ( 10 ) ) , RequeuePosition :: Last )
335
+ } else {
336
+ mapped_error. fail ( )
337
+ }
338
+ }
339
+ _ => ExternalBundlerSendError :: InternalError {
340
+ message : format ! ( "Deployment manager error: {}" , e) ,
341
+ }
342
+ . nack ( Some ( Duration :: from_secs ( 10 ) ) , RequeuePosition :: Last ) ,
343
+ }
344
+ } ) ?;
328
345
329
346
let needs_init_code = match deployment_status {
330
347
DeploymentStatus :: Deployed => false ,
@@ -615,7 +632,7 @@ fn map_build_error(
615
632
let stage = match engine_error {
616
633
EngineError :: RpcError { .. } | EngineError :: PaymasterError { .. } => "BUILDING" . to_string ( ) ,
617
634
EngineError :: BundlerError { .. } => "BUNDLING" . to_string ( ) ,
618
- EngineError :: VaultError { .. } => "Signing " . to_string ( ) ,
635
+ EngineError :: VaultError { .. } => "SIGNING " . to_string ( ) ,
619
636
_ => "UNKNOWN" . to_string ( ) ,
620
637
} ;
621
638
@@ -717,6 +734,7 @@ fn contains_revert_data(body: &str) -> bool {
717
734
fn is_non_retryable_rpc_code ( code : i64 ) -> bool {
718
735
match code {
719
736
-32000 => true , // Invalid input / execution error
737
+ -32001 => true , // Chain does not exist / invalid chain
720
738
-32603 => true , // Internal error (often indicates invalid params)
721
739
_ => false ,
722
740
}
@@ -726,46 +744,48 @@ fn is_non_retryable_rpc_code(code: i64) -> bool {
726
744
fn is_bundler_error_retryable ( error_msg : & str ) -> bool {
727
745
// Check for specific AA error codes that should not be retried
728
746
if error_msg. contains ( "AA10" ) || // sender already constructed
729
- error_msg. contains ( "AA13" ) || // initCode failed or OOG
730
- error_msg. contains ( "AA14" ) || // initCode must return sender
731
- error_msg. contains ( "AA15" ) || // initCode must create sender
732
- error_msg. contains ( "AA21" ) || // didn't pay prefund
733
- error_msg. contains ( "AA22" ) || // expired or not due
734
- error_msg. contains ( "AA23" ) || // reverted (or OOG)
735
- error_msg. contains ( "AA24" ) || // signature error
736
- error_msg. contains ( "AA25" ) || // invalid account nonce
737
- error_msg. contains ( "AA31" ) || // paymaster deposit too low
738
- error_msg. contains ( "AA32" ) || // paymaster stake too low
739
- error_msg. contains ( "AA33" ) || // reverted (or OOG)
740
- error_msg. contains ( "AA34" ) || // signature error
741
- error_msg. contains ( "AA40" ) || // over verificationGasLimit
742
- error_msg. contains ( "AA41" ) || // too little verificationGas
743
- error_msg. contains ( "AA50" ) || // postOp reverted
744
- error_msg. contains ( "AA51" ) // prefund below actualGasCost
747
+ error_msg. contains ( "AA13" ) || // initCode failed or OOG
748
+ error_msg. contains ( "AA14" ) || // initCode must return sender
749
+ error_msg. contains ( "AA15" ) || // initCode must create sender
750
+ error_msg. contains ( "AA21" ) || // didn't pay prefund
751
+ error_msg. contains ( "AA22" ) || // expired or not due
752
+ error_msg. contains ( "AA23" ) || // reverted (or OOG)
753
+ error_msg. contains ( "AA24" ) || // signature error
754
+ error_msg. contains ( "AA25" ) || // invalid account nonce
755
+ error_msg. contains ( "AA31" ) || // paymaster deposit too low
756
+ error_msg. contains ( "AA32" ) || // paymaster stake too low
757
+ error_msg. contains ( "AA33" ) || // reverted (or OOG)
758
+ error_msg. contains ( "AA34" ) || // signature error
759
+ error_msg. contains ( "AA40" ) || // over verificationGasLimit
760
+ error_msg. contains ( "AA41" ) || // too little verificationGas
761
+ error_msg. contains ( "AA50" ) || // postOp reverted
762
+ error_msg. contains ( "AA51" )
763
+ // prefund below actualGasCost
745
764
{
746
765
return false ;
747
766
}
748
767
749
768
// Check for revert-related messages that indicate permanent failures
750
- if error_msg. contains ( "execution reverted" ) ||
751
- error_msg. contains ( "UserOperation reverted" ) ||
752
- error_msg. contains ( "reverted during simulation" ) ||
753
- error_msg. contains ( "invalid signature" ) ||
754
- error_msg. contains ( "signature error" ) ||
755
- error_msg. contains ( "nonce too low" ) ||
756
- error_msg. contains ( "nonce too high" ) ||
757
- error_msg. contains ( "insufficient funds" )
769
+ if error_msg. contains ( "execution reverted" )
770
+ || error_msg. contains ( "UserOperation reverted" )
771
+ || error_msg. contains ( "reverted during simulation" )
772
+ || error_msg. contains ( "invalid signature" )
773
+ || error_msg. contains ( "signature error" )
774
+ || error_msg. contains ( "nonce too low" )
775
+ || error_msg. contains ( "nonce too high" )
776
+ || error_msg. contains ( "insufficient funds" )
758
777
{
759
778
return false ;
760
779
}
761
780
762
781
// Check for HTTP status codes that shouldn't be retried (4xx client errors)
763
- if error_msg. contains ( "status: 400" ) ||
764
- error_msg. contains ( "status: 401" ) ||
765
- error_msg. contains ( "status: 403" ) ||
766
- error_msg. contains ( "status: 404" ) ||
767
- error_msg. contains ( "status: 422" ) ||
768
- error_msg. contains ( "status: 429" ) // rate limit - could be retried but often permanent
782
+ if error_msg. contains ( "status: 400" )
783
+ || error_msg. contains ( "status: 401" )
784
+ || error_msg. contains ( "status: 403" )
785
+ || error_msg. contains ( "status: 404" )
786
+ || error_msg. contains ( "status: 422" )
787
+ || error_msg. contains ( "status: 429" )
788
+ // rate limit - could be retried but often permanent
769
789
{
770
790
return false ;
771
791
}
@@ -779,34 +799,36 @@ fn is_external_bundler_error_retryable(e: &ExternalBundlerSendError) -> bool {
779
799
match e {
780
800
// Policy restrictions are never retryable
781
801
ExternalBundlerSendError :: PolicyRestriction { .. } => false ,
782
-
802
+
783
803
// For other errors, check their inner EngineError if present
784
- ExternalBundlerSendError :: UserOpBuildFailed { inner_error : Some ( inner) , .. } => {
785
- is_build_error_retryable ( inner)
786
- }
787
- ExternalBundlerSendError :: BundlerSendFailed { inner_error : Some ( inner) , .. } => {
788
- is_build_error_retryable ( inner)
789
- }
790
-
804
+ ExternalBundlerSendError :: UserOpBuildFailed {
805
+ inner_error : Some ( inner) ,
806
+ ..
807
+ } => is_build_error_retryable ( inner) ,
808
+ ExternalBundlerSendError :: BundlerSendFailed {
809
+ inner_error : Some ( inner) ,
810
+ ..
811
+ } => is_build_error_retryable ( inner) ,
812
+
791
813
// User cancellations are not retryable
792
814
ExternalBundlerSendError :: UserCancelled => false ,
793
-
815
+
794
816
// Account determination failures are generally not retryable (validation errors)
795
817
ExternalBundlerSendError :: AccountDeterminationFailed { .. } => false ,
796
-
818
+
797
819
// Invalid account salt is not retryable (validation error)
798
820
ExternalBundlerSendError :: InvalidAccountSalt { .. } => false ,
799
-
821
+
800
822
// Invalid RPC credentials are not retryable (auth error)
801
823
ExternalBundlerSendError :: InvalidRpcCredentials { .. } => false ,
802
-
824
+
803
825
// Deployment locked and chain service errors can be retried
804
826
ExternalBundlerSendError :: DeploymentLocked { .. } => true ,
805
827
ExternalBundlerSendError :: ChainServiceError { .. } => true ,
806
-
828
+
807
829
// Internal errors can be retried
808
830
ExternalBundlerSendError :: InternalError { .. } => true ,
809
-
831
+
810
832
// Default to not retryable for safety
811
833
_ => false ,
812
834
}
0 commit comments