@@ -12,7 +12,7 @@ use engine_core::{
1212 chain:: { Chain , ChainService , RpcCredentials } ,
1313 credentials:: SigningCredential ,
1414 error:: { AlloyRpcErrorToEngineError , EngineError , RpcErrorKind } ,
15- execution_options:: { aa:: Erc4337ExecutionOptions , WebhookOptions } ,
15+ execution_options:: { WebhookOptions , aa:: Erc4337ExecutionOptions } ,
1616 transaction:: InnerTransaction ,
1717 userop:: UserOpSigner ,
1818} ;
@@ -52,7 +52,7 @@ pub struct ExternalBundlerSendJobData {
5252 pub webhook_options : Option < Vec < WebhookOptions > > ,
5353
5454 pub rpc_credentials : RpcCredentials ,
55-
55+
5656 /// Pregenerated nonce for vault signed tokens
5757 #[ serde( skip_serializing_if = "Option::is_none" ) ]
5858 pub pregenerated_nonce : Option < U256 > ,
@@ -129,10 +129,7 @@ pub enum ExternalBundlerSendError {
129129 } ,
130130
131131 #[ 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 } ,
136133
137134 #[ error( "Invalid RPC Credentials: {message}" ) ]
138135 InvalidRpcCredentials { message : String } ,
@@ -279,11 +276,13 @@ where
279276 let chain = chain. with_new_default_headers ( chain_auth_headers) ;
280277
281278 // 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 ( ) ?;
287286
288287 // 3. Determine Smart Account
289288 let smart_account = match job_data. execution_options . smart_account_address {
@@ -321,10 +320,28 @@ where
321320 . deployment_manager ( )
322321 . check_deployment_status ( job_data. chain_id , & smart_account. address , is_deployed_check)
323322 . 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+ } ) ?;
328345
329346 let needs_init_code = match deployment_status {
330347 DeploymentStatus :: Deployed => false ,
@@ -615,7 +632,7 @@ fn map_build_error(
615632 let stage = match engine_error {
616633 EngineError :: RpcError { .. } | EngineError :: PaymasterError { .. } => "BUILDING" . to_string ( ) ,
617634 EngineError :: BundlerError { .. } => "BUNDLING" . to_string ( ) ,
618- EngineError :: VaultError { .. } => "Signing " . to_string ( ) ,
635+ EngineError :: VaultError { .. } => "SIGNING " . to_string ( ) ,
619636 _ => "UNKNOWN" . to_string ( ) ,
620637 } ;
621638
@@ -717,6 +734,7 @@ fn contains_revert_data(body: &str) -> bool {
717734fn is_non_retryable_rpc_code ( code : i64 ) -> bool {
718735 match code {
719736 -32000 => true , // Invalid input / execution error
737+ -32001 => true , // Chain does not exist / invalid chain
720738 -32603 => true , // Internal error (often indicates invalid params)
721739 _ => false ,
722740 }
@@ -726,46 +744,48 @@ fn is_non_retryable_rpc_code(code: i64) -> bool {
726744fn is_bundler_error_retryable ( error_msg : & str ) -> bool {
727745 // Check for specific AA error codes that should not be retried
728746 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
745764 {
746765 return false ;
747766 }
748767
749768 // 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" )
758777 {
759778 return false ;
760779 }
761780
762781 // 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
769789 {
770790 return false ;
771791 }
@@ -779,34 +799,36 @@ fn is_external_bundler_error_retryable(e: &ExternalBundlerSendError) -> bool {
779799 match e {
780800 // Policy restrictions are never retryable
781801 ExternalBundlerSendError :: PolicyRestriction { .. } => false ,
782-
802+
783803 // 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+
791813 // User cancellations are not retryable
792814 ExternalBundlerSendError :: UserCancelled => false ,
793-
815+
794816 // Account determination failures are generally not retryable (validation errors)
795817 ExternalBundlerSendError :: AccountDeterminationFailed { .. } => false ,
796-
818+
797819 // Invalid account salt is not retryable (validation error)
798820 ExternalBundlerSendError :: InvalidAccountSalt { .. } => false ,
799-
821+
800822 // Invalid RPC credentials are not retryable (auth error)
801823 ExternalBundlerSendError :: InvalidRpcCredentials { .. } => false ,
802-
824+
803825 // Deployment locked and chain service errors can be retried
804826 ExternalBundlerSendError :: DeploymentLocked { .. } => true ,
805827 ExternalBundlerSendError :: ChainServiceError { .. } => true ,
806-
828+
807829 // Internal errors can be retried
808830 ExternalBundlerSendError :: InternalError { .. } => true ,
809-
831+
810832 // Default to not retryable for safety
811833 _ => false ,
812834 }
0 commit comments