Skip to content

Commit c3c62e9

Browse files
committed
fail on invalid chain errors
1 parent 6a35f02 commit c3c62e9

File tree

3 files changed

+87
-63
lines changed

3 files changed

+87
-63
lines changed

executors/src/external_bundler/send.rs

Lines changed: 82 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -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 {
717734
fn 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 {
726744
fn 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
}

server/src/main.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ async fn main() -> anyhow::Result<()> {
2323
}));
2424

2525
match config.server.log_format {
26-
config::LogFormat::Json => subscriber.with(tracing_subscriber::fmt::layer().json()).init(),
26+
config::LogFormat::Json => subscriber
27+
.with(tracing_subscriber::fmt::layer().json())
28+
.init(),
2729
config::LogFormat::Pretty => subscriber.with(tracing_subscriber::fmt::layer()).init(),
2830
}
2931

server/src/queue/manager.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ use engine_executors::{
99
deployment::{RedisDeploymentCache, RedisDeploymentLock},
1010
send::ExternalBundlerSendHandler,
1111
},
12-
webhook::{WebhookJobHandler, WebhookRetryConfig},
1312
transaction_registry::TransactionRegistry,
13+
webhook::{WebhookJobHandler, WebhookRetryConfig},
1414
};
1515
use twmq::{Queue, queue::QueueOptions, shutdown::ShutdownHandle};
1616

@@ -46,7 +46,7 @@ impl QueueManager {
4646
) -> Result<Self, EngineError> {
4747
// Create Redis clients
4848
let redis_client = twmq::redis::Client::open(redis_config.url.as_str())?;
49-
49+
5050
// Create transaction registry
5151
let transaction_registry = Arc::new(TransactionRegistry::new(
5252
redis_client.get_connection_manager().await?,

0 commit comments

Comments
 (0)