From 323e4cb035802392dd3f0da0e9e7069bf03142a9 Mon Sep 17 00:00:00 2001 From: Rayyan Alam Date: Wed, 3 Dec 2025 10:50:40 -0500 Subject: [PATCH 01/24] feat: start scafold user ops queue --- Cargo.lock | 1 + crates/audit/src/reader.rs | 2 +- crates/core/src/kafka.rs | 1 + crates/core/src/user_ops_types.rs | 139 ------------------------------ crates/ingress-rpc/Cargo.toml | 1 + crates/ingress-rpc/src/lib.rs | 3 +- crates/ingress-rpc/src/queue2.rs | 111 ++++++++++++++++++++++++ crates/ingress-rpc/src/service.rs | 13 +-- 8 files changed, 125 insertions(+), 146 deletions(-) delete mode 100644 crates/core/src/user_ops_types.rs create mode 100644 crates/ingress-rpc/src/queue2.rs diff --git a/Cargo.lock b/Cargo.lock index 2db75e84..b07bbf82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7108,6 +7108,7 @@ dependencies = [ "reth-optimism-evm", "reth-rpc-eth-types", "revm-context-interface", + "serde", "serde_json", "tips-audit", "tips-core", diff --git a/crates/audit/src/reader.rs b/crates/audit/src/reader.rs index 329dd9c5..945c6e3f 100644 --- a/crates/audit/src/reader.rs +++ b/crates/audit/src/reader.rs @@ -13,7 +13,7 @@ use tokio::time::sleep; use tracing::{debug, error}; pub fn create_kafka_consumer(kafka_properties_file: &str) -> Result { - let client_config = + let client_config: ClientConfig = ClientConfig::from_iter(load_kafka_config_from_file(kafka_properties_file)?); let consumer: StreamConsumer = client_config.create()?; Ok(consumer) diff --git a/crates/core/src/kafka.rs b/crates/core/src/kafka.rs index a5230eee..29f7b338 100644 --- a/crates/core/src/kafka.rs +++ b/crates/core/src/kafka.rs @@ -20,3 +20,4 @@ pub fn load_kafka_config_from_file( Ok(config) } + diff --git a/crates/core/src/user_ops_types.rs b/crates/core/src/user_ops_types.rs deleted file mode 100644 index 95780a60..00000000 --- a/crates/core/src/user_ops_types.rs +++ /dev/null @@ -1,139 +0,0 @@ -use alloy_rpc_types::erc4337; -use serde::{Deserialize, Serialize}; - -// Re-export SendUserOperationResponse -pub use alloy_rpc_types::erc4337::SendUserOperationResponse; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -#[serde(tag = "type")] -pub enum UserOperationRequest { - EntryPointV06(erc4337::UserOperation), - EntryPointV07(erc4337::PackedUserOperation), -} - -// Tests -#[cfg(test)] -mod tests { - use std::str::FromStr; - - use super::*; - use alloy_primitives::{Address, Bytes, Uint}; - #[test] - fn should_throw_error_when_deserializing_invalid() { - const TEST_INVALID_USER_OPERATION: &str = r#" - { - "type": "EntryPointV06", - "sender": "0x1111111111111111111111111111111111111111", - "nonce": "0x0", - "callGasLimit": "0x5208" - } - "#; - let user_operation: Result = - serde_json::from_str::(TEST_INVALID_USER_OPERATION); - assert!(user_operation.is_err()); - } - - #[test] - fn should_deserialize_v06() { - const TEST_USER_OPERATION: &str = r#" - { - "type": "EntryPointV06", - "sender": "0x1111111111111111111111111111111111111111", - "nonce": "0x0", - "initCode": "0x", - "callData": "0x", - "callGasLimit": "0x5208", - "verificationGasLimit": "0x100000", - "preVerificationGas": "0x10000", - "maxFeePerGas": "0x59682f10", - "maxPriorityFeePerGas": "0x3b9aca00", - "paymasterAndData": "0x", - "signature": "0x01" - } - "#; - let user_operation: Result = - serde_json::from_str::(TEST_USER_OPERATION); - if user_operation.is_err() { - panic!("Error: {:?}", user_operation.err()); - } - let user_operation = user_operation.unwrap(); - match user_operation { - UserOperationRequest::EntryPointV06(user_operation) => { - assert_eq!( - user_operation.sender, - Address::from_str("0x1111111111111111111111111111111111111111").unwrap() - ); - assert_eq!(user_operation.nonce, Uint::from(0)); - assert_eq!(user_operation.init_code, Bytes::from_str("0x").unwrap()); - assert_eq!(user_operation.call_data, Bytes::from_str("0x").unwrap()); - assert_eq!(user_operation.call_gas_limit, Uint::from(0x5208)); - assert_eq!(user_operation.verification_gas_limit, Uint::from(0x100000)); - assert_eq!(user_operation.pre_verification_gas, Uint::from(0x10000)); - assert_eq!(user_operation.max_fee_per_gas, Uint::from(0x59682f10)); - assert_eq!( - user_operation.max_priority_fee_per_gas, - Uint::from(0x3b9aca00) - ); - assert_eq!( - user_operation.paymaster_and_data, - Bytes::from_str("0x").unwrap() - ); - assert_eq!(user_operation.signature, Bytes::from_str("0x01").unwrap()); - } - _ => { - panic!("Expected EntryPointV06, got {user_operation:?}"); - } - } - } - - #[test] - fn should_deserialize_v07() { - const TEST_PACKED_USER_OPERATION: &str = r#" - { - "type": "EntryPointV07", - "sender": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", - "nonce": "0x1", - "factory": "0x2222222222222222222222222222222222222222", - "factoryData": "0xabcdef1234560000000000000000000000000000000000000000000000000000", - "callData": "0xb61d27f600000000000000000000000000000000000000000000000000000000000000c8", - "callGasLimit": "0x2dc6c0", - "verificationGasLimit": "0x1e8480", - "preVerificationGas": "0x186a0", - "maxFeePerGas": "0x77359400", - "maxPriorityFeePerGas": "0x3b9aca00", - "paymaster": "0x3333333333333333333333333333333333333333", - "paymasterVerificationGasLimit": "0x186a0", - "paymasterPostOpGasLimit": "0x27100", - "paymasterData": "0xfafb00000000000000000000000000000000000000000000000000000000000064", - "signature": "0xa3c5f1b90014e68abbbdc42e4b77b9accc0b7e1c5d0b5bcde1a47ba8faba00ff55c9a7de12e98b731766e35f6c51ab25c9b58cc0e7c4a33f25e75c51c6ad3c3a" - } - "#; - let user_operation: Result = - serde_json::from_str::(TEST_PACKED_USER_OPERATION); - if user_operation.is_err() { - panic!("Error: {:?}", user_operation.err()); - } - let user_operation = user_operation.unwrap(); - match user_operation { - UserOperationRequest::EntryPointV07(user_operation) => { - assert_eq!( - user_operation.sender, - Address::from_str("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48").unwrap() - ); - assert_eq!(user_operation.nonce, Uint::from(1)); - assert_eq!( - user_operation.call_data, - alloy_primitives::bytes!( - "0xb61d27f600000000000000000000000000000000000000000000000000000000000000c8" - ) - ); - assert_eq!(user_operation.call_gas_limit, Uint::from(0x2dc6c0)); - assert_eq!(user_operation.verification_gas_limit, Uint::from(0x1e8480)); - assert_eq!(user_operation.pre_verification_gas, Uint::from(0x186a0)); - } - _ => { - panic!("Expected EntryPointV07, got {user_operation:?}"); - } - } - } -} diff --git a/crates/ingress-rpc/Cargo.toml b/crates/ingress-rpc/Cargo.toml index 462ef281..d9b2ccdd 100644 --- a/crates/ingress-rpc/Cargo.toml +++ b/crates/ingress-rpc/Cargo.toml @@ -40,3 +40,4 @@ metrics.workspace = true metrics-derive.workspace = true metrics-exporter-prometheus.workspace = true axum.workspace = true +serde.workspace = true diff --git a/crates/ingress-rpc/src/lib.rs b/crates/ingress-rpc/src/lib.rs index 94014001..4afbc609 100644 --- a/crates/ingress-rpc/src/lib.rs +++ b/crates/ingress-rpc/src/lib.rs @@ -3,7 +3,7 @@ pub mod metrics; pub mod queue; pub mod service; pub mod validation; - +pub mod queue2; use alloy_primitives::TxHash; use alloy_provider::{Provider, ProviderBuilder, RootProvider}; use clap::Parser; @@ -15,6 +15,7 @@ use tokio::sync::broadcast; use tracing::error; use url::Url; + #[derive(Debug, Clone, Copy)] pub enum TxSubmissionMethod { Mempool, diff --git a/crates/ingress-rpc/src/queue2.rs b/crates/ingress-rpc/src/queue2.rs new file mode 100644 index 00000000..8cdecef8 --- /dev/null +++ b/crates/ingress-rpc/src/queue2.rs @@ -0,0 +1,111 @@ +use alloy_primitives::B256; +use anyhow::Result; +use async_trait::async_trait; +use backon::{ExponentialBuilder, Retryable}; +use rdkafka::producer::{FutureProducer, FutureRecord}; +use tips_core::AcceptedBundle; +use tokio::time::Duration; +use tracing::{error, info}; +use account_abstraction_core::types::UserOperationRequest; + +#[async_trait] +pub trait MessageQueue: Send + Sync { + async fn publish_raw( + &self, + topic: &str, + key: &str, + payload: &[u8], + ) -> Result<()>; +} + +pub struct KafkaMessageQueue { + producer: FutureProducer, +} + +impl KafkaMessageQueue { + pub fn new(producer: FutureProducer) -> Self { + Self { producer } + } +} + +#[async_trait] +impl MessageQueue for KafkaMessageQueue { + async fn publish_raw(&self, topic: &str, key: &str, payload: &[u8]) -> Result<()> { + + let enqueue = || async { + let record = FutureRecord::to(topic).key(key).payload(payload); + + match self.producer.send(record, Duration::from_secs(5)).await { + Ok((partition, offset)) => { + info!( + key = %key, + partition = partition, + offset = offset, + topic = %topic, + "Successfully enqueued message" + ); + Ok(()) + } + Err((err, _)) => { + error!( + key = key, + error = %err, + topic = topic, + "Failed to enqueue message" + ); + Err(anyhow::anyhow!("Failed to enqueue bundle: {err}")) + } + } + }; + + enqueue + .retry( + &ExponentialBuilder::default() + .with_min_delay(Duration::from_millis(100)) + .with_max_delay(Duration::from_secs(5)) + .with_max_times(3), + ) + .notify(|err: &anyhow::Error, dur: Duration| { + info!("retrying to enqueue message {:?} after {:?}", err, dur); + }) + .await + } +} + + +pub struct UserOpQueuePublisher { + queue: std::sync::Arc, + topic: String, +} + +impl UserOpQueuePublisher { + pub fn new(queue: std::sync::Arc, topic: String) -> Self { + Self { queue, topic } + } + + pub async fn publish(&self, user_op: &UserOperationRequest, hash: &B256) -> Result<()> { + let key = hash.to_string(); + let payload = serde_json::to_vec(user_op)?; + self.queue.publish_raw(&self.topic, &key, &payload).await + } +} + + + +pub struct BundleQueuePublisher { + queue: std::sync::Arc, + topic: String, +} + +impl BundleQueuePublisher { + pub fn new(queue: std::sync::Arc, topic: String) -> Self { + Self { queue, topic } + } + + pub async fn publish(&self, bundle: &AcceptedBundle, hash: &B256) -> Result<()> { + let key = hash.to_string(); + let payload = serde_json::to_vec(bundle)?; + self.queue.publish_raw(&self.topic, &key, &payload).await + } +} + diff --git a/crates/ingress-rpc/src/service.rs b/crates/ingress-rpc/src/service.rs index 0755e358..a66309bf 100644 --- a/crates/ingress-rpc/src/service.rs +++ b/crates/ingress-rpc/src/service.rs @@ -23,10 +23,10 @@ use crate::metrics::{Metrics, record_histogram}; use crate::queue::QueuePublisher; use crate::validation::validate_bundle; use crate::{Config, TxSubmissionMethod}; +use crate::queue2::{BundleQueuePublisher, UserOpQueuePublisher, KafkaMessageQueue}; use account_abstraction_core::types::{SendUserOperationResponse, UserOperationRequest}; use account_abstraction_core::{AccountAbstractionService, AccountAbstractionServiceImpl}; use std::sync::Arc; - #[rpc(server, namespace = "eth")] pub trait IngressApi { /// `eth_sendBundle` can be used to send your bundles to the builder. @@ -57,7 +57,8 @@ pub struct IngressService { simulation_provider: Arc>, account_abstraction_service: AccountAbstractionServiceImpl, tx_submission_method: TxSubmissionMethod, - bundle_queue: Queue, + bundle_queue_publisher: BundleQueuePublisher, + user_op_queue_publisher: UserOpQueuePublisher, audit_channel: mpsc::UnboundedSender, send_transaction_default_lifetime_seconds: u64, metrics: Metrics, @@ -72,7 +73,7 @@ impl IngressService { pub fn new( provider: RootProvider, simulation_provider: RootProvider, - queue: Queue, + queue: KafkaMessageQueue, audit_channel: mpsc::UnboundedSender, builder_tx: broadcast::Sender, builder_backrun_tx: broadcast::Sender, @@ -85,12 +86,14 @@ impl IngressService { simulation_provider.clone(), config.validate_user_operation_timeout_ms, ); + let queueConnection = Arc::new(queue); Self { provider, simulation_provider, account_abstraction_service, tx_submission_method: config.tx_submission_method, - bundle_queue: queue, + user_op_queue_publisher: UserOpQueuePublisher::new(queueConnection.clone(), "TODO: Change topic to user_op_topic".into()), // TODO: Change topic to user_op_topic + bundle_queue_publisher: BundleQueuePublisher::new(queueConnection.clone(), config.ingress_topic), audit_channel, send_transaction_default_lifetime_seconds: config .send_transaction_default_lifetime_seconds, @@ -157,7 +160,7 @@ where // publish the bundle to the queue if let Err(e) = self .bundle_queue - .publish(&accepted_bundle, &bundle_hash) + .publish_raw(&self.bundle_queue.topic, &bundle_hash.to_string(), &accepted_bundle.to_vec()) .await { warn!(message = "Failed to publish bundle to queue", bundle_hash = %bundle_hash, error = %e); From 5f61f0e60ccc842b946740a2f401b5d1afc6adde Mon Sep 17 00:00:00 2001 From: Rayyan Alam Date: Wed, 3 Dec 2025 11:53:50 -0500 Subject: [PATCH 02/24] chore: refactor out queue dep --- crates/ingress-rpc/src/service.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/crates/ingress-rpc/src/service.rs b/crates/ingress-rpc/src/service.rs index a66309bf..2f627acd 100644 --- a/crates/ingress-rpc/src/service.rs +++ b/crates/ingress-rpc/src/service.rs @@ -20,7 +20,6 @@ use tokio::time::{Duration, Instant, timeout}; use tracing::{info, warn}; use crate::metrics::{Metrics, record_histogram}; -use crate::queue::QueuePublisher; use crate::validation::validate_bundle; use crate::{Config, TxSubmissionMethod}; use crate::queue2::{BundleQueuePublisher, UserOpQueuePublisher, KafkaMessageQueue}; @@ -52,7 +51,7 @@ pub trait IngressApi { ) -> RpcResult; } -pub struct IngressService { +pub struct IngressService { provider: Arc>, simulation_provider: Arc>, account_abstraction_service: AccountAbstractionServiceImpl, @@ -69,7 +68,7 @@ pub struct IngressService { builder_backrun_tx: broadcast::Sender, } -impl IngressService { +impl IngressService { pub fn new( provider: RootProvider, simulation_provider: RootProvider, @@ -108,9 +107,7 @@ impl IngressService { } #[async_trait] -impl IngressApiServer for IngressService -where - Queue: QueuePublisher + Sync + Send + 'static, +impl IngressApiServer for IngressService { async fn send_backrun_bundle(&self, bundle: Bundle) -> RpcResult { if !self.backrun_enabled { @@ -159,8 +156,8 @@ where // publish the bundle to the queue if let Err(e) = self - .bundle_queue - .publish_raw(&self.bundle_queue.topic, &bundle_hash.to_string(), &accepted_bundle.to_vec()) + .bundle_queue_publisher + .publish(&accepted_bundle, &bundle_hash) .await { warn!(message = "Failed to publish bundle to queue", bundle_hash = %bundle_hash, error = %e); @@ -227,7 +224,7 @@ where if send_to_kafka { if let Err(e) = self - .bundle_queue + .bundle_queue_publisher .publish(&accepted_bundle, bundle_hash) .await { @@ -281,9 +278,7 @@ where } } -impl IngressService -where - Queue: QueuePublisher + Sync + Send + 'static, +impl IngressService { async fn get_tx(&self, data: &Bytes) -> RpcResult> { if data.is_empty() { From b8a9d4dbf0081054fcad782b32233f7b29336111 Mon Sep 17 00:00:00 2001 From: Rayyan Alam Date: Wed, 3 Dec 2025 11:54:50 -0500 Subject: [PATCH 03/24] chore: fix build --- crates/ingress-rpc/src/bin/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ingress-rpc/src/bin/main.rs b/crates/ingress-rpc/src/bin/main.rs index d5fd5430..fecae9db 100644 --- a/crates/ingress-rpc/src/bin/main.rs +++ b/crates/ingress-rpc/src/bin/main.rs @@ -12,7 +12,7 @@ use tips_ingress_rpc::Config; use tips_ingress_rpc::connect_ingress_to_builder; use tips_ingress_rpc::health::bind_health_server; use tips_ingress_rpc::metrics::init_prometheus_exporter; -use tips_ingress_rpc::queue::KafkaQueuePublisher; +use tips_ingress_rpc::queue2::KafkaMessageQueue; use tips_ingress_rpc::service::{IngressApiServer, IngressService}; use tokio::sync::{broadcast, mpsc}; use tracing::info; @@ -55,7 +55,7 @@ async fn main() -> anyhow::Result<()> { let queue_producer: FutureProducer = ingress_client_config.create()?; - let queue = KafkaQueuePublisher::new(queue_producer, config.ingress_topic); + let queue = KafkaMessageQueue::new(queue_producer); let audit_client_config = ClientConfig::from_iter(load_kafka_config_from_file(&config.audit_kafka_properties)?); From 8f2d34d389a818201f91e207da46ece893853ccc Mon Sep 17 00:00:00 2001 From: Rayyan Alam Date: Wed, 3 Dec 2025 14:08:39 -0500 Subject: [PATCH 04/24] chore: update types of user op --- .../core/src/account_abstraction_service.rs | 18 +++++----- .../account-abstraction-core/core/src/lib.rs | 2 +- .../core/src/types.rs | 22 ++++++------ crates/core/src/kafka.rs | 1 - crates/ingress-rpc/src/lib.rs | 3 +- crates/ingress-rpc/src/queue2.rs | 20 +++-------- crates/ingress-rpc/src/service.rs | 34 +++++++++++++------ 7 files changed, 51 insertions(+), 49 deletions(-) diff --git a/crates/account-abstraction-core/core/src/account_abstraction_service.rs b/crates/account-abstraction-core/core/src/account_abstraction_service.rs index 0cba40dd..72cd9174 100644 --- a/crates/account-abstraction-core/core/src/account_abstraction_service.rs +++ b/crates/account-abstraction-core/core/src/account_abstraction_service.rs @@ -1,4 +1,4 @@ -use crate::types::{UserOperationRequest, UserOperationRequestValidationResult}; +use crate::types::{VersionedUserOperation, UserOperationRequestValidationResult}; use alloy_provider::{Provider, RootProvider}; use async_trait::async_trait; use jsonrpsee::core::RpcResult; @@ -10,7 +10,7 @@ use tokio::time::{Duration, timeout}; pub trait AccountAbstractionService: Send + Sync { async fn validate_user_operation( &self, - user_operation: UserOperationRequest, + user_operation: &VersionedUserOperation, ) -> RpcResult; } @@ -24,7 +24,7 @@ pub struct AccountAbstractionServiceImpl { impl AccountAbstractionService for AccountAbstractionServiceImpl { async fn validate_user_operation( &self, - user_operation: UserOperationRequest, + user_operation: &VersionedUserOperation, ) -> RpcResult { // Steps: Reputation Service Validate // Steps: Base Node Validate User Operation @@ -45,7 +45,7 @@ impl AccountAbstractionServiceImpl { pub async fn base_node_validate_user_operation( &self, - user_operation: UserOperationRequest, + user_operation: &VersionedUserOperation, ) -> RpcResult { let result = timeout( Duration::from_secs(self.validate_user_operation_timeout), @@ -88,8 +88,8 @@ mod tests { MockServer::start().await } - fn new_test_user_operation_v06() -> UserOperationRequest { - UserOperationRequest::EntryPointV06(UserOperation { + fn new_test_user_operation_v06() -> VersionedUserOperation { + VersionedUserOperation::EntryPointV06(UserOperation { sender: Address::ZERO, nonce: U256::from(0), init_code: Bytes::default(), @@ -126,7 +126,7 @@ mod tests { let user_operation = new_test_user_operation_v06(); let result = service - .base_node_validate_user_operation(user_operation) + .base_node_validate_user_operation(&user_operation) .await; assert!(result.is_err()); @@ -153,7 +153,7 @@ mod tests { let user_operation = new_test_user_operation_v06(); let result = service - .base_node_validate_user_operation(user_operation) + .base_node_validate_user_operation(&user_operation) .await; assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("Internal error")); @@ -178,7 +178,7 @@ mod tests { let user_operation = new_test_user_operation_v06(); let result = service - .base_node_validate_user_operation(user_operation) + .base_node_validate_user_operation(&user_operation) .await .unwrap(); diff --git a/crates/account-abstraction-core/core/src/lib.rs b/crates/account-abstraction-core/core/src/lib.rs index bb2abc37..70b2abec 100644 --- a/crates/account-abstraction-core/core/src/lib.rs +++ b/crates/account-abstraction-core/core/src/lib.rs @@ -2,4 +2,4 @@ pub mod account_abstraction_service; pub mod types; pub use account_abstraction_service::{AccountAbstractionService, AccountAbstractionServiceImpl}; -pub use types::{SendUserOperationResponse, UserOperationRequest}; +pub use types::{SendUserOperationResponse, VersionedUserOperation}; diff --git a/crates/account-abstraction-core/core/src/types.rs b/crates/account-abstraction-core/core/src/types.rs index 27e6303f..d2bfeff9 100644 --- a/crates/account-abstraction-core/core/src/types.rs +++ b/crates/account-abstraction-core/core/src/types.rs @@ -1,13 +1,14 @@ -use alloy_primitives::U256; +use alloy_primitives::{B256, U256}; use alloy_rpc_types::erc4337; use serde::{Deserialize, Serialize}; + // Re-export SendUserOperationResponse pub use alloy_rpc_types::erc4337::SendUserOperationResponse; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(tag = "type")] -pub enum UserOperationRequest { +pub enum VersionedUserOperation { EntryPointV06(erc4337::UserOperation), EntryPointV07(erc4337::PackedUserOperation), } @@ -16,6 +17,7 @@ pub enum UserOperationRequest { #[serde(rename_all = "camelCase")] pub struct UserOperationRequestValidationResult { pub expiration_timestamp: u64, + pub hash: B256, pub gas_used: U256, } @@ -36,8 +38,8 @@ mod tests { "callGasLimit": "0x5208" } "#; - let user_operation: Result = - serde_json::from_str::(TEST_INVALID_USER_OPERATION); + let user_operation: Result = + serde_json::from_str::(TEST_INVALID_USER_OPERATION); assert!(user_operation.is_err()); } @@ -59,14 +61,14 @@ mod tests { "signature": "0x01" } "#; - let user_operation: Result = - serde_json::from_str::(TEST_USER_OPERATION); + let user_operation: Result = + serde_json::from_str::(TEST_USER_OPERATION); if user_operation.is_err() { panic!("Error: {:?}", user_operation.err()); } let user_operation = user_operation.unwrap(); match user_operation { - UserOperationRequest::EntryPointV06(user_operation) => { + VersionedUserOperation::EntryPointV06(user_operation) => { assert_eq!( user_operation.sender, Address::from_str("0x1111111111111111111111111111111111111111").unwrap() @@ -116,14 +118,14 @@ mod tests { "signature": "0xa3c5f1b90014e68abbbdc42e4b77b9accc0b7e1c5d0b5bcde1a47ba8faba00ff55c9a7de12e98b731766e35f6c51ab25c9b58cc0e7c4a33f25e75c51c6ad3c3a" } "#; - let user_operation: Result = - serde_json::from_str::(TEST_PACKED_USER_OPERATION); + let user_operation: Result = + serde_json::from_str::(TEST_PACKED_USER_OPERATION); if user_operation.is_err() { panic!("Error: {:?}", user_operation.err()); } let user_operation = user_operation.unwrap(); match user_operation { - UserOperationRequest::EntryPointV07(user_operation) => { + VersionedUserOperation::EntryPointV07(user_operation) => { assert_eq!( user_operation.sender, Address::from_str("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48").unwrap() diff --git a/crates/core/src/kafka.rs b/crates/core/src/kafka.rs index 29f7b338..a5230eee 100644 --- a/crates/core/src/kafka.rs +++ b/crates/core/src/kafka.rs @@ -20,4 +20,3 @@ pub fn load_kafka_config_from_file( Ok(config) } - diff --git a/crates/ingress-rpc/src/lib.rs b/crates/ingress-rpc/src/lib.rs index 4afbc609..3d7fc6f4 100644 --- a/crates/ingress-rpc/src/lib.rs +++ b/crates/ingress-rpc/src/lib.rs @@ -1,9 +1,9 @@ pub mod health; pub mod metrics; pub mod queue; +pub mod queue2; pub mod service; pub mod validation; -pub mod queue2; use alloy_primitives::TxHash; use alloy_provider::{Provider, ProviderBuilder, RootProvider}; use clap::Parser; @@ -15,7 +15,6 @@ use tokio::sync::broadcast; use tracing::error; use url::Url; - #[derive(Debug, Clone, Copy)] pub enum TxSubmissionMethod { Mempool, diff --git a/crates/ingress-rpc/src/queue2.rs b/crates/ingress-rpc/src/queue2.rs index 8cdecef8..a6bd205b 100644 --- a/crates/ingress-rpc/src/queue2.rs +++ b/crates/ingress-rpc/src/queue2.rs @@ -1,3 +1,4 @@ +use account_abstraction_core::types::VersionedUserOperation; use alloy_primitives::B256; use anyhow::Result; use async_trait::async_trait; @@ -6,16 +7,10 @@ use rdkafka::producer::{FutureProducer, FutureRecord}; use tips_core::AcceptedBundle; use tokio::time::Duration; use tracing::{error, info}; -use account_abstraction_core::types::UserOperationRequest; #[async_trait] pub trait MessageQueue: Send + Sync { - async fn publish_raw( - &self, - topic: &str, - key: &str, - payload: &[u8], - ) -> Result<()>; + async fn publish_raw(&self, topic: &str, key: &str, payload: &[u8]) -> Result<()>; } pub struct KafkaMessageQueue { @@ -31,7 +26,6 @@ impl KafkaMessageQueue { #[async_trait] impl MessageQueue for KafkaMessageQueue { async fn publish_raw(&self, topic: &str, key: &str, payload: &[u8]) -> Result<()> { - let enqueue = || async { let record = FutureRecord::to(topic).key(key).payload(payload); @@ -72,7 +66,6 @@ impl MessageQueue for KafkaMessageQueue { } } - pub struct UserOpQueuePublisher { queue: std::sync::Arc, topic: String, @@ -83,15 +76,13 @@ impl UserOpQueuePublisher { Self { queue, topic } } - pub async fn publish(&self, user_op: &UserOperationRequest, hash: &B256) -> Result<()> { - let key = hash.to_string(); - let payload = serde_json::to_vec(user_op)?; + pub async fn publish(&self, user_op: &VersionedUserOperation, hash: &String) -> Result<()> { + let key = hash; + let payload = serde_json::to_vec(&user_op)?; self.queue.publish_raw(&self.topic, &key, &payload).await } } - - pub struct BundleQueuePublisher { queue: std::sync::Arc, topic: String, @@ -108,4 +99,3 @@ impl BundleQueuePublisher { self.queue.publish_raw(&self.topic, &key, &payload).await } } - diff --git a/crates/ingress-rpc/src/service.rs b/crates/ingress-rpc/src/service.rs index 2f627acd..92d8a134 100644 --- a/crates/ingress-rpc/src/service.rs +++ b/crates/ingress-rpc/src/service.rs @@ -20,10 +20,10 @@ use tokio::time::{Duration, Instant, timeout}; use tracing::{info, warn}; use crate::metrics::{Metrics, record_histogram}; +use crate::queue2::{BundleQueuePublisher, KafkaMessageQueue, UserOpQueuePublisher}; use crate::validation::validate_bundle; use crate::{Config, TxSubmissionMethod}; -use crate::queue2::{BundleQueuePublisher, UserOpQueuePublisher, KafkaMessageQueue}; -use account_abstraction_core::types::{SendUserOperationResponse, UserOperationRequest}; +use account_abstraction_core::types::{SendUserOperationResponse, VersionedUserOperation}; use account_abstraction_core::{AccountAbstractionService, AccountAbstractionServiceImpl}; use std::sync::Arc; #[rpc(server, namespace = "eth")] @@ -47,7 +47,7 @@ pub trait IngressApi { #[method(name = "sendUserOperation")] async fn send_user_operation( &self, - user_operation: UserOperationRequest, + user_operation: VersionedUserOperation, ) -> RpcResult; } @@ -91,8 +91,14 @@ impl IngressService { simulation_provider, account_abstraction_service, tx_submission_method: config.tx_submission_method, - user_op_queue_publisher: UserOpQueuePublisher::new(queueConnection.clone(), "TODO: Change topic to user_op_topic".into()), // TODO: Change topic to user_op_topic - bundle_queue_publisher: BundleQueuePublisher::new(queueConnection.clone(), config.ingress_topic), + user_op_queue_publisher: UserOpQueuePublisher::new( + queueConnection.clone(), + "TODO: Change topic to user_op_topic".into(), + ), // TODO: Change topic to user_op_topic + bundle_queue_publisher: BundleQueuePublisher::new( + queueConnection.clone(), + config.ingress_topic, + ), audit_channel, send_transaction_default_lifetime_seconds: config .send_transaction_default_lifetime_seconds, @@ -107,8 +113,7 @@ impl IngressService { } #[async_trait] -impl IngressApiServer for IngressService -{ +impl IngressApiServer for IngressService { async fn send_backrun_bundle(&self, bundle: Bundle) -> RpcResult { if !self.backrun_enabled { info!( @@ -260,7 +265,7 @@ impl IngressApiServer for IngressService async fn send_user_operation( &self, - user_operation: UserOperationRequest, + user_operation: VersionedUserOperation, ) -> RpcResult { dbg!(&user_operation); @@ -269,17 +274,24 @@ impl IngressApiServer for IngressService // 2. Base Node Validate User Operation let _ = self .account_abstraction_service - .validate_user_operation(user_operation) + .validate_user_operation(&user_operation) .await?; // 3. Send to Kafka // Send Hash // todo!("not yet implemented send_user_operation"); + if let Err(e) = self + .user_op_queue_publisher + .publish(&user_operation, &"TODO: GET USER OPERATION HASH".to_string()) + .await + { + warn!(message = "Failed to publish user operation to queue", user_operation_hash = %"SOME:HASH TO REPLACE ONE DAY", error = %e); + return Err(EthApiError::InvalidParams("Failed to queue user operation".into()).into_rpc_err()); + } todo!("not yet implemented send_user_operation"); } } -impl IngressService -{ +impl IngressService { async fn get_tx(&self, data: &Bytes) -> RpcResult> { if data.is_empty() { return Err(EthApiError::EmptyRawTransactionData.into_rpc_err()); From 1621e943546640e15989cb8a7185a0ff2ebe2f8f Mon Sep 17 00:00:00 2001 From: Rayyan Alam Date: Wed, 3 Dec 2025 14:18:24 -0500 Subject: [PATCH 05/24] feat: create queue service --- .../core/src/types.rs | 16 +++++++++++++- crates/ingress-rpc/src/queue2.rs | 4 ++-- crates/ingress-rpc/src/service.rs | 21 +++++++------------ 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/crates/account-abstraction-core/core/src/types.rs b/crates/account-abstraction-core/core/src/types.rs index d2bfeff9..fff7f36f 100644 --- a/crates/account-abstraction-core/core/src/types.rs +++ b/crates/account-abstraction-core/core/src/types.rs @@ -1,4 +1,4 @@ -use alloy_primitives::{B256, U256}; +use alloy_primitives::{B256, U256, Address}; use alloy_rpc_types::erc4337; use serde::{Deserialize, Serialize}; @@ -12,6 +12,20 @@ pub enum VersionedUserOperation { EntryPointV06(erc4337::UserOperation), EntryPointV07(erc4337::PackedUserOperation), } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] + +pub struct UserOperationRequest { + pub user_operation: VersionedUserOperation, + pub entry_point: Address, +} + +impl UserOperationRequest { + pub fn hash(&self) -> B256 { + let payload =self.entry_point.to_string(); + let hash = alloy_primitives::keccak256(&payload); + B256::from(hash) + } +} #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] diff --git a/crates/ingress-rpc/src/queue2.rs b/crates/ingress-rpc/src/queue2.rs index a6bd205b..d8028ac2 100644 --- a/crates/ingress-rpc/src/queue2.rs +++ b/crates/ingress-rpc/src/queue2.rs @@ -76,8 +76,8 @@ impl UserOpQueuePublisher { Self { queue, topic } } - pub async fn publish(&self, user_op: &VersionedUserOperation, hash: &String) -> Result<()> { - let key = hash; + pub async fn publish(&self, user_op: &VersionedUserOperation, hash: &B256) -> Result<()> { + let key = hash.to_string(); let payload = serde_json::to_vec(&user_op)?; self.queue.publish_raw(&self.topic, &key, &payload).await } diff --git a/crates/ingress-rpc/src/service.rs b/crates/ingress-rpc/src/service.rs index 92d8a134..1a0fa522 100644 --- a/crates/ingress-rpc/src/service.rs +++ b/crates/ingress-rpc/src/service.rs @@ -23,7 +23,7 @@ use crate::metrics::{Metrics, record_histogram}; use crate::queue2::{BundleQueuePublisher, KafkaMessageQueue, UserOpQueuePublisher}; use crate::validation::validate_bundle; use crate::{Config, TxSubmissionMethod}; -use account_abstraction_core::types::{SendUserOperationResponse, VersionedUserOperation}; +use account_abstraction_core::types::{SendUserOperationResponse, UserOperationRequest, VersionedUserOperation}; use account_abstraction_core::{AccountAbstractionService, AccountAbstractionServiceImpl}; use std::sync::Arc; #[rpc(server, namespace = "eth")] @@ -47,7 +47,7 @@ pub trait IngressApi { #[method(name = "sendUserOperation")] async fn send_user_operation( &self, - user_operation: VersionedUserOperation, + user_operation: UserOperationRequest, ) -> RpcResult; } @@ -265,26 +265,19 @@ impl IngressApiServer for IngressService { async fn send_user_operation( &self, - user_operation: VersionedUserOperation, + user_operation_request: UserOperationRequest, ) -> RpcResult { - dbg!(&user_operation); - - // STEPS: - // 1. Reputation Service Validate - // 2. Base Node Validate User Operation + dbg!(&user_operation_request); let _ = self .account_abstraction_service - .validate_user_operation(&user_operation) + .validate_user_operation(&user_operation_request.user_operation) .await?; - // 3. Send to Kafka - // Send Hash - // todo!("not yet implemented send_user_operation"); if let Err(e) = self .user_op_queue_publisher - .publish(&user_operation, &"TODO: GET USER OPERATION HASH".to_string()) + .publish(&user_operation_request.user_operation, &user_operation_request.hash()) .await { - warn!(message = "Failed to publish user operation to queue", user_operation_hash = %"SOME:HASH TO REPLACE ONE DAY", error = %e); + warn!(message = "Failed to publish user operation to queue", user_operation_hash = %user_operation_request.hash(), error = %e); return Err(EthApiError::InvalidParams("Failed to queue user operation".into()).into_rpc_err()); } todo!("not yet implemented send_user_operation"); From 58f7d2303ba7a151ba8a0f991ff909931e461cba Mon Sep 17 00:00:00 2001 From: Rayyan Alam Date: Wed, 3 Dec 2025 14:21:53 -0500 Subject: [PATCH 06/24] chore: docker service for libs --- crates/ingress-rpc/src/lib.rs | 8 ++++++++ crates/ingress-rpc/src/service.rs | 4 ++-- docker-compose.yml | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/crates/ingress-rpc/src/lib.rs b/crates/ingress-rpc/src/lib.rs index 3d7fc6f4..c91d731a 100644 --- a/crates/ingress-rpc/src/lib.rs +++ b/crates/ingress-rpc/src/lib.rs @@ -84,6 +84,14 @@ pub struct Config { )] pub audit_topic: String, + /// User operation topic for pushing valid user operations + #[arg( + long, + env = "TIPS_INGRESS_KAFKA_USER_OPERATION_TOPIC", + default_value = "tips-user-operation" + )] + pub user_operation_topic: String, + #[arg(long, env = "TIPS_INGRESS_LOG_LEVEL", default_value = "info")] pub log_level: String, diff --git a/crates/ingress-rpc/src/service.rs b/crates/ingress-rpc/src/service.rs index 1a0fa522..38b1bf8e 100644 --- a/crates/ingress-rpc/src/service.rs +++ b/crates/ingress-rpc/src/service.rs @@ -93,8 +93,8 @@ impl IngressService { tx_submission_method: config.tx_submission_method, user_op_queue_publisher: UserOpQueuePublisher::new( queueConnection.clone(), - "TODO: Change topic to user_op_topic".into(), - ), // TODO: Change topic to user_op_topic + config.user_operation_topic, + ), bundle_queue_publisher: BundleQueuePublisher::new( queueConnection.clone(), config.ingress_topic, diff --git a/docker-compose.yml b/docker-compose.yml index 57914265..2cadfbf9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,6 +39,7 @@ services: sh -c " kafka-topics --create --if-not-exists --topic tips-audit --bootstrap-server kafka:29092 --partitions 3 --replication-factor 1 kafka-topics --create --if-not-exists --topic tips-ingress --bootstrap-server kafka:29092 --partitions 3 --replication-factor 1 + kafka-topics --create --if-not-exists --topic tips-user-operation --bootstrap-server kafka:29092 --partitions 3 --replication-factor 1 kafka-topics --list --bootstrap-server kafka:29092 " From 74d9428a983b0e1621fbfa18e395a8d94d7b6f7d Mon Sep 17 00:00:00 2001 From: Rayyan Alam Date: Wed, 3 Dec 2025 14:26:15 -0500 Subject: [PATCH 07/24] chore: create service for account abstraction --- .../core/src/account_abstraction_service.rs | 2 +- .../account-abstraction-core/core/src/types.rs | 5 ++--- crates/ingress-rpc/src/service.rs | 17 +++++++++++------ 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/crates/account-abstraction-core/core/src/account_abstraction_service.rs b/crates/account-abstraction-core/core/src/account_abstraction_service.rs index 72cd9174..186096f2 100644 --- a/crates/account-abstraction-core/core/src/account_abstraction_service.rs +++ b/crates/account-abstraction-core/core/src/account_abstraction_service.rs @@ -1,4 +1,4 @@ -use crate::types::{VersionedUserOperation, UserOperationRequestValidationResult}; +use crate::types::{UserOperationRequestValidationResult, VersionedUserOperation}; use alloy_provider::{Provider, RootProvider}; use async_trait::async_trait; use jsonrpsee::core::RpcResult; diff --git a/crates/account-abstraction-core/core/src/types.rs b/crates/account-abstraction-core/core/src/types.rs index fff7f36f..e36576b2 100644 --- a/crates/account-abstraction-core/core/src/types.rs +++ b/crates/account-abstraction-core/core/src/types.rs @@ -1,8 +1,7 @@ -use alloy_primitives::{B256, U256, Address}; +use alloy_primitives::{Address, B256, U256}; use alloy_rpc_types::erc4337; use serde::{Deserialize, Serialize}; - // Re-export SendUserOperationResponse pub use alloy_rpc_types::erc4337::SendUserOperationResponse; @@ -21,7 +20,7 @@ pub struct UserOperationRequest { impl UserOperationRequest { pub fn hash(&self) -> B256 { - let payload =self.entry_point.to_string(); + let payload = self.entry_point.to_string(); let hash = alloy_primitives::keccak256(&payload); B256::from(hash) } diff --git a/crates/ingress-rpc/src/service.rs b/crates/ingress-rpc/src/service.rs index 38b1bf8e..ebb53757 100644 --- a/crates/ingress-rpc/src/service.rs +++ b/crates/ingress-rpc/src/service.rs @@ -23,7 +23,7 @@ use crate::metrics::{Metrics, record_histogram}; use crate::queue2::{BundleQueuePublisher, KafkaMessageQueue, UserOpQueuePublisher}; use crate::validation::validate_bundle; use crate::{Config, TxSubmissionMethod}; -use account_abstraction_core::types::{SendUserOperationResponse, UserOperationRequest, VersionedUserOperation}; +use account_abstraction_core::types::{SendUserOperationResponse, UserOperationRequest}; use account_abstraction_core::{AccountAbstractionService, AccountAbstractionServiceImpl}; use std::sync::Arc; #[rpc(server, namespace = "eth")] @@ -85,18 +85,18 @@ impl IngressService { simulation_provider.clone(), config.validate_user_operation_timeout_ms, ); - let queueConnection = Arc::new(queue); + let queue_connection = Arc::new(queue); Self { provider, simulation_provider, account_abstraction_service, tx_submission_method: config.tx_submission_method, user_op_queue_publisher: UserOpQueuePublisher::new( - queueConnection.clone(), + queue_connection.clone(), config.user_operation_topic, ), bundle_queue_publisher: BundleQueuePublisher::new( - queueConnection.clone(), + queue_connection.clone(), config.ingress_topic, ), audit_channel, @@ -274,11 +274,16 @@ impl IngressApiServer for IngressService { .await?; if let Err(e) = self .user_op_queue_publisher - .publish(&user_operation_request.user_operation, &user_operation_request.hash()) + .publish( + &user_operation_request.user_operation, + &user_operation_request.hash(), + ) .await { warn!(message = "Failed to publish user operation to queue", user_operation_hash = %user_operation_request.hash(), error = %e); - return Err(EthApiError::InvalidParams("Failed to queue user operation".into()).into_rpc_err()); + return Err( + EthApiError::InvalidParams("Failed to queue user operation".into()).into_rpc_err(), + ); } todo!("not yet implemented send_user_operation"); } From f8427a962b14ae6818549b1b4e5a3be5c8c784ee Mon Sep 17 00:00:00 2001 From: Rayyan Alam Date: Thu, 4 Dec 2025 15:06:58 -0500 Subject: [PATCH 08/24] chore: initial logic of user operation --- Cargo.lock | 23 +++ Cargo.toml | 3 + crates/account-abstraction-core/Cargo.toml | 6 +- .../account-abstraction-core/core/src/lib.rs | 1 + .../core/src/types.rs | 48 +++++- .../core/src/userop.rs | 153 ++++++++++++++++++ 6 files changed, 224 insertions(+), 10 deletions(-) create mode 100644 crates/account-abstraction-core/core/src/userop.rs diff --git a/Cargo.lock b/Cargo.lock index b07bbf82..8bfbca56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,10 +6,13 @@ version = 4 name = "account-abstraction-core" version = "0.1.0" dependencies = [ + "alloy-abi", + "alloy-dyn-abi", "alloy-primitives", "alloy-provider", "alloy-rpc-types", "alloy-serde", + "alloy-sol-types", "async-trait", "jsonrpsee", "op-alloy-network", @@ -54,6 +57,12 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "alloy-abi" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95d80fbdae433a4c98109023eada56d864e49750d131824bad158f59d79a95d2" + [[package]] name = "alloy-chains" version = "0.2.18" @@ -108,6 +117,20 @@ dependencies = [ "serde", ] +[[package]] +name = "alloy-dyn-abi" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdff496dd4e98a81f4861e66f7eaf5f2488971848bb42d9c892f871730245c8" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-type-parser", + "alloy-sol-types", + "itoa", + "winnow", +] + [[package]] name = "alloy-eip2124" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 3a85042f..c057b2f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,9 @@ alloy-consensus = { version = "1.0.41" } alloy-provider = { version = "1.0.41" } alloy-serde = "1.0.41" alloy-rpc-types = "1.1.2" +alloy-dyn-abi = { version = "1.2.0", default-features = false } +alloy-sol-types = { version = "1.4.1", default-features = false } +alloy-abi = { version = "0.1.0", default-features = false } # op-alloy op-alloy-network = { version = "0.22.0", default-features = false } diff --git a/crates/account-abstraction-core/Cargo.toml b/crates/account-abstraction-core/Cargo.toml index a16a79db..67ab7ef6 100644 --- a/crates/account-abstraction-core/Cargo.toml +++ b/crates/account-abstraction-core/Cargo.toml @@ -9,7 +9,6 @@ edition.workspace = true [lib] path = "core/src/lib.rs" - [dependencies] alloy-serde = { version = "1.0.41", default-features = false } serde.workspace = true @@ -21,9 +20,12 @@ reth-rpc-eth-types.workspace = true tokio.workspace = true jsonrpsee.workspace = true async-trait = { workspace = true } +alloy-dyn-abi = {workspace = true } +alloy-sol-types.workspace= true + [dev-dependencies] alloy-primitives.workspace = true serde_json.workspace = true wiremock.workspace = true - +alloy-abi.workspace = true diff --git a/crates/account-abstraction-core/core/src/lib.rs b/crates/account-abstraction-core/core/src/lib.rs index 70b2abec..921843ec 100644 --- a/crates/account-abstraction-core/core/src/lib.rs +++ b/crates/account-abstraction-core/core/src/lib.rs @@ -1,5 +1,6 @@ pub mod account_abstraction_service; pub mod types; +pub mod userop; pub use account_abstraction_service::{AccountAbstractionService, AccountAbstractionServiceImpl}; pub use types::{SendUserOperationResponse, VersionedUserOperation}; diff --git a/crates/account-abstraction-core/core/src/types.rs b/crates/account-abstraction-core/core/src/types.rs index e36576b2..77c261b2 100644 --- a/crates/account-abstraction-core/core/src/types.rs +++ b/crates/account-abstraction-core/core/src/types.rs @@ -1,10 +1,11 @@ -use alloy_primitives::{Address, B256, U256}; +use crate::userop::{ + compute_domain_separator, encode_packed_user_operation, encode_user_operation, + to_typed_data_hash, +}; +use alloy_primitives::{Address, B256, U256, keccak256}; use alloy_rpc_types::erc4337; -use serde::{Deserialize, Serialize}; - -// Re-export SendUserOperationResponse pub use alloy_rpc_types::erc4337::SendUserOperationResponse; - +use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(tag = "type")] pub enum VersionedUserOperation { @@ -20,9 +21,23 @@ pub struct UserOperationRequest { impl UserOperationRequest { pub fn hash(&self) -> B256 { - let payload = self.entry_point.to_string(); - let hash = alloy_primitives::keccak256(&payload); - B256::from(hash) + let chain_id = 0x2105; // TODO: LOGIC TO GET CHAIN ID + let abiEncoded = self.encode_user_operation(&self.user_operation); + let hashedUserOperation = keccak256(&abiEncoded); + let domainSeparator = compute_domain_separator(chain_id, self.entry_point); + to_typed_data_hash(domainSeparator, hashedUserOperation) + } + + pub fn encode_user_operation(&self, op: &VersionedUserOperation) -> Vec { + match op { + VersionedUserOperation::EntryPointV06(user_op) => { + encode_user_operation(user_op).to_vec() + } + VersionedUserOperation::EntryPointV07(user_op) => { + let overrideInitCodeHash = B256::ZERO; // TODO: LOGIC TO GET OVERRIDE INIT CODE HASH + encode_packed_user_operation(user_op, overrideInitCodeHash).to_vec() + } + } } } @@ -159,4 +174,21 @@ mod tests { } } } + + #[test] + fn should_compute_user_op_hash() { + let user_operation = VersionedUserOperation::EntryPointV07(PackedUserOperation { + sender: Address::from_str("0x6A84A0cF27291eF29042305547a898D3861F05f3").unwrap(), + nonce: U256::from(34478152104024148605889140946955407093471678057486667436843787709996345589760), + init_code: Bytes::from_str("0x").unwrap(), + call_data: Bytes::from_str("0x34fcd5be000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a6c9ba866992cfd7fd6460ba912bfa405ada9df00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a4712ee07a000000000000000000000000000000000000000000000000000000003bad06880000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000204a6f6220313030303432303134302064656c697665727920616363657074656400000000000000000000000000000000000000000000000000000000").unwrap(), + call_gas_limit: Uint::from(0x5208), + verification_gas_limit: Uint::from(0x100000), + pre_verification_gas: Uint::from(0x10000), + max_fee_per_gas: Uint::from(0x59682f10), + max_priority_fee_per_gas: Uint::from(0x3b9aca00), + paymaster_and_data: Bytes::from_str("0x2cc0c7981d846b9f2a16276556f6e8cb52bfb63300000000000000000000000000007f720000000000000000000000000000000000000000000000006931c09b529f9957ccdded5c5cd49d33774020d10c3c6186d0ab497447f3d75f660d001c0c357f6f89ca71d4dc207384efe21b9e4b912c72144cc591935165ce9114c5f21b").unwrap(), + signature: Bytes::from_str("0xff00b5d2030ee303b9098a70a89aac8a3cc267e38da8029751a48fc59193aa90de9540772784a825e1df2c012730c75a186f5b62c1eb702c6b60c9c536d5610382751c").unwrap(), + }); + } } diff --git a/crates/account-abstraction-core/core/src/userop.rs b/crates/account-abstraction-core/core/src/userop.rs new file mode 100644 index 00000000..1a17f0c1 --- /dev/null +++ b/crates/account-abstraction-core/core/src/userop.rs @@ -0,0 +1,153 @@ +// src/user_operation.rs + +use alloy_primitives::{Address, B256, Bytes, FixedBytes, U256, keccak256}; +use alloy_rpc_types::erc4337; +use alloy_sol_types::{SolValue, sol}; + +sol! { + struct PackedUserOperationStruct { + bytes32 userOpTypeHash; + address sender; + uint256 nonce; + bytes32 initCodeHash; + bytes32 callDataHash; + bytes32 accountGasLimits; + uint256 preVerificationGas; + bytes32 gasFees; + bytes32 paymasterAndDataHash; + } +} +pub const USEROP_TYPEHASH: &str = "PackedUserOperation(address sender,uint256 nonce,bytes initCode,bytes callData,bytes32 accountGasLimits,uint256 preVerificationGas,bytes32 gasFees,bytes paymasterAndData)"; +pub const EIP712_DOMAIN_TYPEHASH: &str = + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"; + +// From EntryPoint.sol +const DOMAIN_NAME: &str = "ERC4337"; +const DOMAIN_VERSION: &str = "1"; + +pub fn build_init_code_bytes(user_op: &erc4337::PackedUserOperation) -> Bytes { + match user_op.factory { + Some(factory) => { + let factory_data = user_op + .factory_data + .as_ref() + .map(|b| b.as_ref()) + .unwrap_or(&[]); + let mut buf = Vec::with_capacity(20 + factory_data.len()); + buf.extend_from_slice(factory.as_slice()); // 20‑byte address + buf.extend_from_slice(factory_data); + Bytes::from(buf) + } + None => Bytes::new(), // no deployment => empty initCode + } +} + +fn pack_account_gas_limits(verification_gas: U256, call_gas: U256) -> FixedBytes<32> { + let mask = (U256::from(1u64) << 128) - U256::from(1u64); + let v = verification_gas & mask; + let c = call_gas & mask; + FixedBytes::from((v << 128) | c) +} + +pub fn calc_gas_fees(max_priority_fee_per_gas: U256, max_fee_per_gas: U256) -> FixedBytes<32> { + // keep only low 128 bits of each + let mask = (U256::from(1u64) << 128) - U256::from(1u64); + let hi = max_priority_fee_per_gas & mask; + let lo = max_fee_per_gas & mask; + + // hi in high 128 bits, lo in low 128 bits + FixedBytes::from((hi << 128) | lo) +} + +pub fn encode_packed_user_operation( + user_op: &erc4337::PackedUserOperation, + overrideInitCodeHash: B256, +) -> Vec { + let initCodeHash = if overrideInitCodeHash == B256::ZERO { + keccak256(&build_init_code_bytes(user_op)) + } else { + overrideInitCodeHash + }; + let packed_user_operation: PackedUserOperationStruct = PackedUserOperationStruct { + userOpTypeHash: keccak256(USEROP_TYPEHASH.as_bytes()), + sender: user_op.sender, + nonce: user_op.nonce, + initCodeHash: initCodeHash, + callDataHash: keccak256(&user_op.call_data), + accountGasLimits: pack_account_gas_limits( + user_op.verification_gas_limit, + user_op.call_gas_limit, + ), + preVerificationGas: user_op.pre_verification_gas, + gasFees: calc_gas_fees(user_op.max_priority_fee_per_gas, user_op.max_fee_per_gas), + paymasterAndDataHash: keccak256(&user_op.paymaster_data.as_ref().unwrap_or(&Bytes::new())), + }; + return packed_user_operation.abi_encode(); +} + +pub fn encode_user_operation(user_op: &erc4337::UserOperation) -> Vec { + let packed_user_operation: PackedUserOperationStruct = PackedUserOperationStruct { + userOpTypeHash: keccak256(USEROP_TYPEHASH.as_bytes()), + sender: user_op.sender, + nonce: user_op.nonce, + initCodeHash: keccak256(&user_op.init_code), + callDataHash: keccak256(&user_op.call_data), + accountGasLimits: pack_account_gas_limits( + user_op.verification_gas_limit, + user_op.call_gas_limit, + ), + preVerificationGas: user_op.pre_verification_gas, + gasFees: calc_gas_fees(user_op.max_priority_fee_per_gas, user_op.max_fee_per_gas), + paymasterAndDataHash: keccak256(&user_op.paymaster_and_data), + }; + return packed_user_operation.abi_encode(); +} + +pub fn compute_domain_separator(chain_id: i32, entry_point: Address) -> B256 { + let name_hash = keccak256(DOMAIN_NAME.as_bytes()); + let version_hash = keccak256(DOMAIN_VERSION.as_bytes()); + + // keccak256(abi.encode( + // EIP712_DOMAIN_TYPEHASH, + // keccak256("ERC4337"), + // keccak256("1"), + // chainId, + // entryPointAddress + // )) + sol! { + struct DomainSeparatorData { + bytes32 typeHash; + bytes32 nameHash; + bytes32 versionHash; + uint256 chainId; + address verifyingContract; + } + } + + let data = DomainSeparatorData { + typeHash: keccak256(EIP712_DOMAIN_TYPEHASH.as_bytes()), + nameHash: name_hash, + versionHash: version_hash, + chainId: U256::from(chain_id), + verifyingContract: entry_point, + }; + + keccak256(data.abi_encode()) +} + +pub fn to_typed_data_hash(domain_separator: B256, struct_hash: B256) -> B256 { + // Create the 66-byte buffer: 0x19 0x01 + 32 bytes + 32 bytes + let mut enc = [0u8; 66]; // Create a 66-byte buffer + // Set magic constants + enc[0] = 0x19; + enc[1] = 0x01; + + // Copy domain separator + enc[2..34].copy_from_slice(domain_separator.as_slice()); + + // Copy struct hash + enc[34..66].copy_from_slice(struct_hash.as_slice()); + + // keccak256 hash + keccak256(enc) +} From 96e264e673fb9c368c562a30eedbba31104d6190 Mon Sep 17 00:00:00 2001 From: Rayyan Alam Date: Thu, 4 Dec 2025 18:46:46 -0500 Subject: [PATCH 09/24] feat: finish lib rs for user op --- .../account-abstraction-core/core/src/lib.rs | 3 +- .../core/src/types.rs | 17 -- .../core/src/userop.rs | 20 +-- .../account-abstraction-core/core/src/v06.rs | 129 ++++++++++++++ .../account-abstraction-core/core/src/v07.rs | 168 ++++++++++++++++++ 5 files changed, 308 insertions(+), 29 deletions(-) create mode 100644 crates/account-abstraction-core/core/src/v06.rs create mode 100644 crates/account-abstraction-core/core/src/v07.rs diff --git a/crates/account-abstraction-core/core/src/lib.rs b/crates/account-abstraction-core/core/src/lib.rs index 921843ec..07fbe3cc 100644 --- a/crates/account-abstraction-core/core/src/lib.rs +++ b/crates/account-abstraction-core/core/src/lib.rs @@ -1,6 +1,7 @@ pub mod account_abstraction_service; pub mod types; pub mod userop; - +pub mod v06; +pub mod v07; pub use account_abstraction_service::{AccountAbstractionService, AccountAbstractionServiceImpl}; pub use types::{SendUserOperationResponse, VersionedUserOperation}; diff --git a/crates/account-abstraction-core/core/src/types.rs b/crates/account-abstraction-core/core/src/types.rs index 77c261b2..fe753485 100644 --- a/crates/account-abstraction-core/core/src/types.rs +++ b/crates/account-abstraction-core/core/src/types.rs @@ -174,21 +174,4 @@ mod tests { } } } - - #[test] - fn should_compute_user_op_hash() { - let user_operation = VersionedUserOperation::EntryPointV07(PackedUserOperation { - sender: Address::from_str("0x6A84A0cF27291eF29042305547a898D3861F05f3").unwrap(), - nonce: U256::from(34478152104024148605889140946955407093471678057486667436843787709996345589760), - init_code: Bytes::from_str("0x").unwrap(), - call_data: Bytes::from_str("0x34fcd5be000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a6c9ba866992cfd7fd6460ba912bfa405ada9df00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a4712ee07a000000000000000000000000000000000000000000000000000000003bad06880000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000204a6f6220313030303432303134302064656c697665727920616363657074656400000000000000000000000000000000000000000000000000000000").unwrap(), - call_gas_limit: Uint::from(0x5208), - verification_gas_limit: Uint::from(0x100000), - pre_verification_gas: Uint::from(0x10000), - max_fee_per_gas: Uint::from(0x59682f10), - max_priority_fee_per_gas: Uint::from(0x3b9aca00), - paymaster_and_data: Bytes::from_str("0x2cc0c7981d846b9f2a16276556f6e8cb52bfb63300000000000000000000000000007f720000000000000000000000000000000000000000000000006931c09b529f9957ccdded5c5cd49d33774020d10c3c6186d0ab497447f3d75f660d001c0c357f6f89ca71d4dc207384efe21b9e4b912c72144cc591935165ce9114c5f21b").unwrap(), - signature: Bytes::from_str("0xff00b5d2030ee303b9098a70a89aac8a3cc267e38da8029751a48fc59193aa90de9540772784a825e1df2c012730c75a186f5b62c1eb702c6b60c9c536d5610382751c").unwrap(), - }); - } } diff --git a/crates/account-abstraction-core/core/src/userop.rs b/crates/account-abstraction-core/core/src/userop.rs index 1a17f0c1..76c47f60 100644 --- a/crates/account-abstraction-core/core/src/userop.rs +++ b/crates/account-abstraction-core/core/src/userop.rs @@ -42,21 +42,19 @@ pub fn build_init_code_bytes(user_op: &erc4337::PackedUserOperation) -> Bytes { } } -fn pack_account_gas_limits(verification_gas: U256, call_gas: U256) -> FixedBytes<32> { +fn pack_u256_pair_to_bytes32(high: U256, low: U256) -> FixedBytes<32> { let mask = (U256::from(1u64) << 128) - U256::from(1u64); - let v = verification_gas & mask; - let c = call_gas & mask; - FixedBytes::from((v << 128) | c) + let hi = high & mask; + let lo = low & mask; + FixedBytes::from((hi << 128) | lo) } -pub fn calc_gas_fees(max_priority_fee_per_gas: U256, max_fee_per_gas: U256) -> FixedBytes<32> { - // keep only low 128 bits of each - let mask = (U256::from(1u64) << 128) - U256::from(1u64); - let hi = max_priority_fee_per_gas & mask; - let lo = max_fee_per_gas & mask; +fn pack_account_gas_limits(verification_gas: U256, call_gas: U256) -> FixedBytes<32> { + pack_u256_pair_to_bytes32(verification_gas, call_gas) +} - // hi in high 128 bits, lo in low 128 bits - FixedBytes::from((hi << 128) | lo) +pub fn calc_gas_fees(max_priority_fee_per_gas: U256, max_fee_per_gas: U256) -> FixedBytes<32> { + pack_u256_pair_to_bytes32(max_priority_fee_per_gas, max_fee_per_gas) } pub fn encode_packed_user_operation( diff --git a/crates/account-abstraction-core/core/src/v06.rs b/crates/account-abstraction-core/core/src/v06.rs new file mode 100644 index 00000000..59054a50 --- /dev/null +++ b/crates/account-abstraction-core/core/src/v06.rs @@ -0,0 +1,129 @@ +use alloy_primitives::U256; +use alloy_rpc_types::erc4337; +use alloy_sol_types::{SolValue, sol}; +sol! { + #[allow(missing_docs)] + #[derive(Default, Debug, PartialEq, Eq)] + struct UserOperationHashEncoded { + bytes32 encodedHash; + address entryPoint; + uint256 chainId; + } + + #[allow(missing_docs)] + #[derive(Default, Debug, PartialEq, Eq)] + struct UserOperationPackedForHash { + address sender; + uint256 nonce; + bytes32 hashInitCode; + bytes32 hashCallData; + uint256 callGasLimit; + uint256 verificationGasLimit; + uint256 preVerificationGas; + uint256 maxFeePerGas; + uint256 maxPriorityFeePerGas; + bytes32 hashPaymasterAndData; + } +} + +impl From for UserOperationPackedForHash { + fn from(op: erc4337::UserOperation) -> UserOperationPackedForHash { + UserOperationPackedForHash { + sender: op.sender, + nonce: op.nonce, + hashInitCode: alloy_primitives::keccak256(op.init_code), + hashCallData: alloy_primitives::keccak256(op.call_data), + callGasLimit: U256::from(op.call_gas_limit), + verificationGasLimit: U256::from(op.verification_gas_limit), + preVerificationGas: U256::from(op.pre_verification_gas), + maxFeePerGas: U256::from(op.max_fee_per_gas), + maxPriorityFeePerGas: U256::from(op.max_priority_fee_per_gas), + hashPaymasterAndData: alloy_primitives::keccak256(op.paymaster_and_data), + } + } +} + +pub fn encode_user_operation_hash( + user_operation: &erc4337::UserOperation, + entry_point: alloy_primitives::Address, + chain_id: i32, +) -> alloy_primitives::B256 { + let packed = UserOperationPackedForHash::from(user_operation.clone()); + let encoded = UserOperationHashEncoded { + encodedHash: alloy_primitives::keccak256(packed.abi_encode()), + entryPoint: entry_point, + chainId: U256::from(chain_id), + }; + alloy_primitives::keccak256(encoded.abi_encode()) +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{Bytes, U256, address, b256, bytes}; + use alloy_rpc_types::erc4337; + + #[test] + fn test_hash_zeroed() { + let entry_point_address_v0_6 = address!("66a15edcc3b50a663e72f1457ffd49b9ae284ddc"); + let chainId = 1337; + let userOpWithZeroedInitCode = erc4337::UserOperation { + sender: address!("0x0000000000000000000000000000000000000000"), + nonce: U256::ZERO, + init_code: Bytes::default(), + call_data: Bytes::default(), + call_gas_limit: U256::from(0), + verification_gas_limit: U256::from(0), + pre_verification_gas: U256::from(0), + max_fee_per_gas: U256::from(0), + max_priority_fee_per_gas: U256::from(0), + paymaster_and_data: Bytes::default(), + signature: Bytes::default(), + }; + + let hash = encode_user_operation_hash( + &userOpWithZeroedInitCode, + entry_point_address_v0_6, + chainId, + ); + assert_eq!( + hash, + b256!("dca97c3b49558ab360659f6ead939773be8bf26631e61bb17045bb70dc983b2d") + ); + } + + #[test] + fn test_hash_non_zeroed() { + let entry_point_address_v0_6 = address!("66a15edcc3b50a663e72f1457ffd49b9ae284ddc"); + let chainId = 1337; + let userOpWithNonZeroedInitCode = erc4337::UserOperation { + sender: address!("0x1306b01bc3e4ad202612d3843387e94737673f53"), + nonce: U256::from(8942), + init_code: "0x6942069420694206942069420694206942069420" + .parse() + .unwrap(), + call_data: "0x0000000000000000000000000000000000000000080085" + .parse() + .unwrap(), + call_gas_limit: U256::from(10_000), + verification_gas_limit: U256::from(100_000), + pre_verification_gas: U256::from(100), + max_fee_per_gas: U256::from(99_999), + max_priority_fee_per_gas: U256::from(9_999_999), + paymaster_and_data: bytes!( + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + ), + signature: bytes!("da0929f527cded8d0a1eaf2e8861d7f7e2d8160b7b13942f99dd367df4473a"), + }; + + let hash = encode_user_operation_hash( + &userOpWithNonZeroedInitCode, + entry_point_address_v0_6, + chainId, + ); + assert_eq!( + hash, + b256!("484add9e4d8c3172d11b5feb6a3cc712280e176d278027cfa02ee396eb28afa1") + ); + } +} diff --git a/crates/account-abstraction-core/core/src/v07.rs b/crates/account-abstraction-core/core/src/v07.rs new file mode 100644 index 00000000..f1ebed8b --- /dev/null +++ b/crates/account-abstraction-core/core/src/v07.rs @@ -0,0 +1,168 @@ +use alloy_primitives::{Address, FixedBytes, U256}; +use alloy_rpc_types::erc4337; +use alloy_sol_types::{SolValue, sol}; +use alloy_primitives::{Bytes, keccak256}; + + +sol!( + #[allow(missing_docs)] + #[derive(Default, Debug, PartialEq, Eq)] + struct PackedUserOperation { + address sender; + uint256 nonce; + bytes initCode; + bytes callData; + bytes32 accountGasLimits; + uint256 preVerificationGas; + bytes32 gasFees; + bytes paymasterAndData; + bytes signature; + } + +); + +sol! { + #[derive(Default, Debug, PartialEq, Eq)] + struct UserOperationHashEncoded { + bytes32 encodedHash; + address entryPoint; + uint256 chainId; + } + + #[derive(Default, Debug, PartialEq, Eq)] + struct UserOperationPackedForHash { + address sender; + uint256 nonce; + bytes32 hashInitCode; + bytes32 hashCallData; + bytes32 accountGasLimits; + uint256 preVerificationGas; + bytes32 gasFees; + bytes32 hashPaymasterAndData; + } +} + +fn pack_u256_pair_to_bytes32(high: U256, low: U256) -> FixedBytes<32> { + let mask = (U256::from(1u64) << 128) - U256::from(1u64); + let hi = high & mask; + let lo = low & mask; + FixedBytes::from((hi << 128) | lo) +} + +// When packing we combine two u128s into a single bytes32 +fn concat_u128_be(a: u128, b: u128) -> [u8; 32] { + let a = a.to_be_bytes(); + let b = b.to_be_bytes(); + std::array::from_fn(|i| { + if let Some(i) = i.checked_sub(a.len()) { + b[i] + } else { + a[i] + } + }) +} + +fn pack_user_operation(uo: &erc4337::PackedUserOperation) -> PackedUserOperation { + // INIT CODE, is factory address, appended with factory data + let init_code = if let Some(factory) = uo.factory { + let mut init_code = factory.to_vec(); + init_code.extend_from_slice(&uo.factory_data.clone().unwrap_or_default()); + Bytes::from(init_code) + } else { + Bytes::new() + }; + let account_gas_limits = pack_u256_pair_to_bytes32(uo.verification_gas_limit.into(), uo.call_gas_limit.into()); + let gas_fees = pack_u256_pair_to_bytes32(uo.max_priority_fee_per_gas.into(), uo.max_fee_per_gas.into()); + let pvgl: [u8; 16] = uo.paymaster_verification_gas_limit.unwrap_or_default().to_be_bytes(); + let pogl: [u8; 16] = uo.paymaster_post_op_gas_limit.unwrap_or_default().to_be_bytes(); + let paymaster_and_data = if let Some(paymaster) = uo.paymaster { + let mut paymaster_and_data = paymaster.to_vec(); + paymaster_and_data.extend_from_slice(&pvgl); + paymaster_and_data.extend_from_slice(&pogl); + paymaster_and_data.extend_from_slice(&uo.paymaster_data.clone().unwrap()); + Bytes::from(paymaster_and_data) + } else { + Bytes::new() + }; + PackedUserOperation { + sender: uo.sender, + nonce: uo.nonce, + initCode: init_code, + callData: uo.call_data.clone(), + accountGasLimits: FixedBytes::from(account_gas_limits), + preVerificationGas: U256::from(uo.pre_verification_gas), + gasFees: FixedBytes::from(gas_fees), + paymasterAndData: paymaster_and_data, + signature: uo.signature.clone(), + } +} + +fn hash_packed_user_operation( + puo: &PackedUserOperation, + entry_point: Address, + chain_id: u64, +) -> FixedBytes<32> { + let hash_init_code = alloy_primitives::keccak256(&puo.initCode); + let hash_call_data = alloy_primitives::keccak256(&puo.callData); + let hash_paymaster_and_data = alloy_primitives::keccak256(&puo.paymasterAndData); + + let packed_for_hash = UserOperationPackedForHash { + sender: puo.sender, + nonce: puo.nonce, + hashInitCode: hash_init_code, + hashCallData: hash_call_data, + accountGasLimits: puo.accountGasLimits, + preVerificationGas: puo.preVerificationGas, + gasFees: puo.gasFees, + hashPaymasterAndData: hash_paymaster_and_data, + }; + + let hashed = alloy_primitives::keccak256(packed_for_hash.abi_encode()); + + let encoded = UserOperationHashEncoded { + encodedHash: hashed, + entryPoint: entry_point, + chainId: U256::from(chain_id), + }; + + keccak256(encoded.abi_encode()) +} + +#[cfg(test)] +mod test { + use super::*; + use alloy_primitives::{address, b256, bytes, uint}; + use alloy_rpc_types::erc4337; + use alloy_primitives::{Bytes, U256}; + use alloy_sol_types::{SolValue, sol}; + + #[test] + fn test_hash() { + + let puo = PackedUserOperation { + sender: address!("b292Cf4a8E1fF21Ac27C4f94071Cd02C022C414b"), + nonce: uint!(0xF83D07238A7C8814A48535035602123AD6DBFA63000000000000000000000001_U256), + initCode: Bytes::default(), // Empty + callData: + bytes!("e9ae5c53000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000 + 0000000000000000000000000000000000001d8b292cf4a8e1ff21ac27c4f94071cd02c022c414b00000000000000000000000000000000000000000000000000000000000000009517e29f000000000000000000 + 0000000000000000000000000000000000000000000002000000000000000000000000ad6330089d9a1fe89f4020292e1afe9969a5a2fc00000000000000000000000000000000000000000000000000000000000 + 0006000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000015180000000000000000000000000000000000000 + 00000000000000000000000000000000000000000000000000000000000000000000000000000000018e2fbe898000000000000000000000000000000000000000000000000000000000000000800000000000000 + 0000000000000000000000000000000000000000000000000800000000000000000000000002372912728f93ab3daaaebea4f87e6e28476d987000000000000000000000000000000000000000000000000002386 + f26fc10000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + accountGasLimits: b256!("000000000000000000000000000114fc0000000000000000000000000012c9b5"), + preVerificationGas: U256::from(48916), + gasFees: b256!("000000000000000000000000524121000000000000000000000000109a4a441a"), + paymasterAndData: Bytes::default(), // Empty + signature: bytes!("3c7bfe22c9c2ef8994a9637bcc4df1741c5dc0c25b209545a7aeb20f7770f351479b683bd17c4d55bc32e2a649c8d2dff49dcfcc1f3fd837bcd88d1e69a434cf1c"), + }; + + let expected_hash = b256!("e486401370d145766c3cf7ba089553214a1230d38662ae532c9b62eb6dadcf7e"); + let uo = hash_packed_user_operation(&puo, address!("0x0000000071727De22E5E9d8BAf0edAc6f37da032"), 11155111); + + assert_eq!(uo, expected_hash); + } +} + + From be53c7438bae299c1a705d04b5d58f28c4ba6d95 Mon Sep 17 00:00:00 2001 From: Rayyan Alam Date: Fri, 5 Dec 2025 10:21:29 -0500 Subject: [PATCH 10/24] chore: setup with DRY --- .../account-abstraction-core/core/src/lib.rs | 1 - .../core/src/types.rs | 45 +++--- .../core/src/userop.rs | 151 ------------------ .../account-abstraction-core/core/src/v06.rs | 6 +- .../account-abstraction-core/core/src/v07.rs | 12 +- 5 files changed, 40 insertions(+), 175 deletions(-) delete mode 100644 crates/account-abstraction-core/core/src/userop.rs diff --git a/crates/account-abstraction-core/core/src/lib.rs b/crates/account-abstraction-core/core/src/lib.rs index 07fbe3cc..cefac65b 100644 --- a/crates/account-abstraction-core/core/src/lib.rs +++ b/crates/account-abstraction-core/core/src/lib.rs @@ -1,6 +1,5 @@ pub mod account_abstraction_service; pub mod types; -pub mod userop; pub mod v06; pub mod v07; pub use account_abstraction_service::{AccountAbstractionService, AccountAbstractionServiceImpl}; diff --git a/crates/account-abstraction-core/core/src/types.rs b/crates/account-abstraction-core/core/src/types.rs index fe753485..f22b9157 100644 --- a/crates/account-abstraction-core/core/src/types.rs +++ b/crates/account-abstraction-core/core/src/types.rs @@ -1,10 +1,25 @@ -use crate::userop::{ - compute_domain_separator, encode_packed_user_operation, encode_user_operation, - to_typed_data_hash, -}; + use alloy_primitives::{Address, B256, U256, keccak256}; use alloy_rpc_types::erc4337; pub use alloy_rpc_types::erc4337::SendUserOperationResponse; +use crate::v06; +use crate::v07; +pub trait UserOperation { + fn hash(&self, entry_point: Address, chain_id: i32) -> B256; +} + +impl UserOperation for erc4337::UserOperation { + fn hash(&self, entry_point: Address, chain_id: i32) -> B256 { + v06::hash_user_operation_v06(self, entry_point, chain_id) + } +} + +impl UserOperation for erc4337::PackedUserOperation { + fn hash(&self, entry_point: Address, chain_id: i32) -> B256 { + v07::hash_user_operation_v07(self, entry_point, chain_id) + } +} + use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(tag = "type")] @@ -17,30 +32,24 @@ pub enum VersionedUserOperation { pub struct UserOperationRequest { pub user_operation: VersionedUserOperation, pub entry_point: Address, + pub chain_id: i32, } impl UserOperationRequest { pub fn hash(&self) -> B256 { - let chain_id = 0x2105; // TODO: LOGIC TO GET CHAIN ID - let abiEncoded = self.encode_user_operation(&self.user_operation); - let hashedUserOperation = keccak256(&abiEncoded); - let domainSeparator = compute_domain_separator(chain_id, self.entry_point); - to_typed_data_hash(domainSeparator, hashedUserOperation) - } - - pub fn encode_user_operation(&self, op: &VersionedUserOperation) -> Vec { - match op { - VersionedUserOperation::EntryPointV06(user_op) => { - encode_user_operation(user_op).to_vec() + match &self.user_operation { + VersionedUserOperation::EntryPointV06(user_operation) => { + user_operation.hash(self.entry_point, self.chain_id) } - VersionedUserOperation::EntryPointV07(user_op) => { - let overrideInitCodeHash = B256::ZERO; // TODO: LOGIC TO GET OVERRIDE INIT CODE HASH - encode_packed_user_operation(user_op, overrideInitCodeHash).to_vec() + VersionedUserOperation::EntryPointV07(user_operation) => { + user_operation.hash(self.entry_point, self.chain_id) } } } } + + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UserOperationRequestValidationResult { diff --git a/crates/account-abstraction-core/core/src/userop.rs b/crates/account-abstraction-core/core/src/userop.rs deleted file mode 100644 index 76c47f60..00000000 --- a/crates/account-abstraction-core/core/src/userop.rs +++ /dev/null @@ -1,151 +0,0 @@ -// src/user_operation.rs - -use alloy_primitives::{Address, B256, Bytes, FixedBytes, U256, keccak256}; -use alloy_rpc_types::erc4337; -use alloy_sol_types::{SolValue, sol}; - -sol! { - struct PackedUserOperationStruct { - bytes32 userOpTypeHash; - address sender; - uint256 nonce; - bytes32 initCodeHash; - bytes32 callDataHash; - bytes32 accountGasLimits; - uint256 preVerificationGas; - bytes32 gasFees; - bytes32 paymasterAndDataHash; - } -} -pub const USEROP_TYPEHASH: &str = "PackedUserOperation(address sender,uint256 nonce,bytes initCode,bytes callData,bytes32 accountGasLimits,uint256 preVerificationGas,bytes32 gasFees,bytes paymasterAndData)"; -pub const EIP712_DOMAIN_TYPEHASH: &str = - "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"; - -// From EntryPoint.sol -const DOMAIN_NAME: &str = "ERC4337"; -const DOMAIN_VERSION: &str = "1"; - -pub fn build_init_code_bytes(user_op: &erc4337::PackedUserOperation) -> Bytes { - match user_op.factory { - Some(factory) => { - let factory_data = user_op - .factory_data - .as_ref() - .map(|b| b.as_ref()) - .unwrap_or(&[]); - let mut buf = Vec::with_capacity(20 + factory_data.len()); - buf.extend_from_slice(factory.as_slice()); // 20‑byte address - buf.extend_from_slice(factory_data); - Bytes::from(buf) - } - None => Bytes::new(), // no deployment => empty initCode - } -} - -fn pack_u256_pair_to_bytes32(high: U256, low: U256) -> FixedBytes<32> { - let mask = (U256::from(1u64) << 128) - U256::from(1u64); - let hi = high & mask; - let lo = low & mask; - FixedBytes::from((hi << 128) | lo) -} - -fn pack_account_gas_limits(verification_gas: U256, call_gas: U256) -> FixedBytes<32> { - pack_u256_pair_to_bytes32(verification_gas, call_gas) -} - -pub fn calc_gas_fees(max_priority_fee_per_gas: U256, max_fee_per_gas: U256) -> FixedBytes<32> { - pack_u256_pair_to_bytes32(max_priority_fee_per_gas, max_fee_per_gas) -} - -pub fn encode_packed_user_operation( - user_op: &erc4337::PackedUserOperation, - overrideInitCodeHash: B256, -) -> Vec { - let initCodeHash = if overrideInitCodeHash == B256::ZERO { - keccak256(&build_init_code_bytes(user_op)) - } else { - overrideInitCodeHash - }; - let packed_user_operation: PackedUserOperationStruct = PackedUserOperationStruct { - userOpTypeHash: keccak256(USEROP_TYPEHASH.as_bytes()), - sender: user_op.sender, - nonce: user_op.nonce, - initCodeHash: initCodeHash, - callDataHash: keccak256(&user_op.call_data), - accountGasLimits: pack_account_gas_limits( - user_op.verification_gas_limit, - user_op.call_gas_limit, - ), - preVerificationGas: user_op.pre_verification_gas, - gasFees: calc_gas_fees(user_op.max_priority_fee_per_gas, user_op.max_fee_per_gas), - paymasterAndDataHash: keccak256(&user_op.paymaster_data.as_ref().unwrap_or(&Bytes::new())), - }; - return packed_user_operation.abi_encode(); -} - -pub fn encode_user_operation(user_op: &erc4337::UserOperation) -> Vec { - let packed_user_operation: PackedUserOperationStruct = PackedUserOperationStruct { - userOpTypeHash: keccak256(USEROP_TYPEHASH.as_bytes()), - sender: user_op.sender, - nonce: user_op.nonce, - initCodeHash: keccak256(&user_op.init_code), - callDataHash: keccak256(&user_op.call_data), - accountGasLimits: pack_account_gas_limits( - user_op.verification_gas_limit, - user_op.call_gas_limit, - ), - preVerificationGas: user_op.pre_verification_gas, - gasFees: calc_gas_fees(user_op.max_priority_fee_per_gas, user_op.max_fee_per_gas), - paymasterAndDataHash: keccak256(&user_op.paymaster_and_data), - }; - return packed_user_operation.abi_encode(); -} - -pub fn compute_domain_separator(chain_id: i32, entry_point: Address) -> B256 { - let name_hash = keccak256(DOMAIN_NAME.as_bytes()); - let version_hash = keccak256(DOMAIN_VERSION.as_bytes()); - - // keccak256(abi.encode( - // EIP712_DOMAIN_TYPEHASH, - // keccak256("ERC4337"), - // keccak256("1"), - // chainId, - // entryPointAddress - // )) - sol! { - struct DomainSeparatorData { - bytes32 typeHash; - bytes32 nameHash; - bytes32 versionHash; - uint256 chainId; - address verifyingContract; - } - } - - let data = DomainSeparatorData { - typeHash: keccak256(EIP712_DOMAIN_TYPEHASH.as_bytes()), - nameHash: name_hash, - versionHash: version_hash, - chainId: U256::from(chain_id), - verifyingContract: entry_point, - }; - - keccak256(data.abi_encode()) -} - -pub fn to_typed_data_hash(domain_separator: B256, struct_hash: B256) -> B256 { - // Create the 66-byte buffer: 0x19 0x01 + 32 bytes + 32 bytes - let mut enc = [0u8; 66]; // Create a 66-byte buffer - // Set magic constants - enc[0] = 0x19; - enc[1] = 0x01; - - // Copy domain separator - enc[2..34].copy_from_slice(domain_separator.as_slice()); - - // Copy struct hash - enc[34..66].copy_from_slice(struct_hash.as_slice()); - - // keccak256 hash - keccak256(enc) -} diff --git a/crates/account-abstraction-core/core/src/v06.rs b/crates/account-abstraction-core/core/src/v06.rs index 59054a50..92abfd05 100644 --- a/crates/account-abstraction-core/core/src/v06.rs +++ b/crates/account-abstraction-core/core/src/v06.rs @@ -43,7 +43,7 @@ impl From for UserOperationPackedForHash { } } -pub fn encode_user_operation_hash( +pub fn hash_user_operation_v06( user_operation: &erc4337::UserOperation, entry_point: alloy_primitives::Address, chain_id: i32, @@ -81,7 +81,7 @@ mod tests { signature: Bytes::default(), }; - let hash = encode_user_operation_hash( + let hash = hash_user_operation_v06( &userOpWithZeroedInitCode, entry_point_address_v0_6, chainId, @@ -116,7 +116,7 @@ mod tests { signature: bytes!("da0929f527cded8d0a1eaf2e8861d7f7e2d8160b7b13942f99dd367df4473a"), }; - let hash = encode_user_operation_hash( + let hash = hash_user_operation_v06( &userOpWithNonZeroedInitCode, entry_point_address_v0_6, chainId, diff --git a/crates/account-abstraction-core/core/src/v07.rs b/crates/account-abstraction-core/core/src/v07.rs index f1ebed8b..ab1019d3 100644 --- a/crates/account-abstraction-core/core/src/v07.rs +++ b/crates/account-abstraction-core/core/src/v07.rs @@ -100,7 +100,7 @@ fn pack_user_operation(uo: &erc4337::PackedUserOperation) -> PackedUserOperation fn hash_packed_user_operation( puo: &PackedUserOperation, entry_point: Address, - chain_id: u64, + chain_id: i32, ) -> FixedBytes<32> { let hash_init_code = alloy_primitives::keccak256(&puo.initCode); let hash_call_data = alloy_primitives::keccak256(&puo.callData); @@ -128,11 +128,19 @@ fn hash_packed_user_operation( keccak256(encoded.abi_encode()) } +pub fn hash_user_operation_v07( + user_operation: &erc4337::PackedUserOperation, + entry_point: Address, + chain_id: i32, +) -> FixedBytes<32> { + let packed = pack_user_operation(user_operation); + hash_packed_user_operation(&packed, entry_point, chain_id) +} + #[cfg(test)] mod test { use super::*; use alloy_primitives::{address, b256, bytes, uint}; - use alloy_rpc_types::erc4337; use alloy_primitives::{Bytes, U256}; use alloy_sol_types::{SolValue, sol}; From f3c977aed3cace2eff1068523f2396ae5954fb23 Mon Sep 17 00:00:00 2001 From: Rayyan Alam Date: Fri, 5 Dec 2025 12:14:50 -0500 Subject: [PATCH 11/24] feat: create services and clean up --- Cargo.lock | 1 + crates/account-abstraction-core/Cargo.toml | 2 +- .../core/src/account_abstraction_service.rs | 2 +- .../core/src/entrypoints/mod.rs | 2 + .../core/src/{ => entrypoints}/v06.rs | 7 +- .../core/src/{ => entrypoints}/v07.rs | 116 +++++++++--------- .../account-abstraction-core/core/src/lib.rs | 3 +- .../core/src/types.rs | 80 +++++++++--- crates/ingress-rpc/src/service.rs | 4 +- 9 files changed, 128 insertions(+), 89 deletions(-) create mode 100644 crates/account-abstraction-core/core/src/entrypoints/mod.rs rename crates/account-abstraction-core/core/src/{ => entrypoints}/v06.rs (96%) rename crates/account-abstraction-core/core/src/{ => entrypoints}/v07.rs (68%) diff --git a/Cargo.lock b/Cargo.lock index 8bfbca56..147d2c0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,6 +13,7 @@ dependencies = [ "alloy-rpc-types", "alloy-serde", "alloy-sol-types", + "anyhow", "async-trait", "jsonrpsee", "op-alloy-network", diff --git a/crates/account-abstraction-core/Cargo.toml b/crates/account-abstraction-core/Cargo.toml index 67ab7ef6..c2ee01db 100644 --- a/crates/account-abstraction-core/Cargo.toml +++ b/crates/account-abstraction-core/Cargo.toml @@ -22,7 +22,7 @@ jsonrpsee.workspace = true async-trait = { workspace = true } alloy-dyn-abi = {workspace = true } alloy-sol-types.workspace= true - +anyhow.workspace = true [dev-dependencies] alloy-primitives.workspace = true diff --git a/crates/account-abstraction-core/core/src/account_abstraction_service.rs b/crates/account-abstraction-core/core/src/account_abstraction_service.rs index 186096f2..4a19f1dd 100644 --- a/crates/account-abstraction-core/core/src/account_abstraction_service.rs +++ b/crates/account-abstraction-core/core/src/account_abstraction_service.rs @@ -89,7 +89,7 @@ mod tests { } fn new_test_user_operation_v06() -> VersionedUserOperation { - VersionedUserOperation::EntryPointV06(UserOperation { + VersionedUserOperation::UserOperation(UserOperation { sender: Address::ZERO, nonce: U256::from(0), init_code: Bytes::default(), diff --git a/crates/account-abstraction-core/core/src/entrypoints/mod.rs b/crates/account-abstraction-core/core/src/entrypoints/mod.rs new file mode 100644 index 00000000..fa873c59 --- /dev/null +++ b/crates/account-abstraction-core/core/src/entrypoints/mod.rs @@ -0,0 +1,2 @@ +pub mod v06; +pub mod v07; diff --git a/crates/account-abstraction-core/core/src/v06.rs b/crates/account-abstraction-core/core/src/entrypoints/v06.rs similarity index 96% rename from crates/account-abstraction-core/core/src/v06.rs rename to crates/account-abstraction-core/core/src/entrypoints/v06.rs index 92abfd05..8cb9bc6b 100644 --- a/crates/account-abstraction-core/core/src/v06.rs +++ b/crates/account-abstraction-core/core/src/entrypoints/v06.rs @@ -81,11 +81,8 @@ mod tests { signature: Bytes::default(), }; - let hash = hash_user_operation_v06( - &userOpWithZeroedInitCode, - entry_point_address_v0_6, - chainId, - ); + let hash = + hash_user_operation_v06(&userOpWithZeroedInitCode, entry_point_address_v0_6, chainId); assert_eq!( hash, b256!("dca97c3b49558ab360659f6ead939773be8bf26631e61bb17045bb70dc983b2d") diff --git a/crates/account-abstraction-core/core/src/v07.rs b/crates/account-abstraction-core/core/src/entrypoints/v07.rs similarity index 68% rename from crates/account-abstraction-core/core/src/v07.rs rename to crates/account-abstraction-core/core/src/entrypoints/v07.rs index ab1019d3..c1d99bf3 100644 --- a/crates/account-abstraction-core/core/src/v07.rs +++ b/crates/account-abstraction-core/core/src/entrypoints/v07.rs @@ -1,8 +1,7 @@ use alloy_primitives::{Address, FixedBytes, U256}; +use alloy_primitives::{Bytes, keccak256}; use alloy_rpc_types::erc4337; use alloy_sol_types::{SolValue, sol}; -use alloy_primitives::{Bytes, keccak256}; - sol!( #[allow(missing_docs)] @@ -42,6 +41,51 @@ sol! { } } +impl From for PackedUserOperation { + fn from(uo: erc4337::PackedUserOperation) -> Self { + let init_code = if let Some(factory) = uo.factory { + let mut init_code = factory.to_vec(); + init_code.extend_from_slice(&uo.factory_data.clone().unwrap_or_default()); + Bytes::from(init_code) + } else { + Bytes::new() + }; + let account_gas_limits = + pack_u256_pair_to_bytes32(uo.verification_gas_limit.into(), uo.call_gas_limit.into()); + let gas_fees = pack_u256_pair_to_bytes32( + uo.max_priority_fee_per_gas.into(), + uo.max_fee_per_gas.into(), + ); + let pvgl: [u8; 16] = uo + .paymaster_verification_gas_limit + .unwrap_or_default() + .to_be_bytes(); + let pogl: [u8; 16] = uo + .paymaster_post_op_gas_limit + .unwrap_or_default() + .to_be_bytes(); + let paymaster_and_data = if let Some(paymaster) = uo.paymaster { + let mut paymaster_and_data = paymaster.to_vec(); + paymaster_and_data.extend_from_slice(&pvgl); + paymaster_and_data.extend_from_slice(&pogl); + paymaster_and_data.extend_from_slice(&uo.paymaster_data.clone().unwrap()); + Bytes::from(paymaster_and_data) + } else { + Bytes::new() + }; + PackedUserOperation { + sender: uo.sender, + nonce: uo.nonce, + initCode: init_code, + callData: uo.call_data.clone(), + accountGasLimits: FixedBytes::from(account_gas_limits), + preVerificationGas: U256::from(uo.pre_verification_gas), + gasFees: FixedBytes::from(gas_fees), + paymasterAndData: paymaster_and_data, + signature: uo.signature.clone(), + } + } +} fn pack_u256_pair_to_bytes32(high: U256, low: U256) -> FixedBytes<32> { let mask = (U256::from(1u64) << 128) - U256::from(1u64); let hi = high & mask; @@ -49,54 +93,6 @@ fn pack_u256_pair_to_bytes32(high: U256, low: U256) -> FixedBytes<32> { FixedBytes::from((hi << 128) | lo) } -// When packing we combine two u128s into a single bytes32 -fn concat_u128_be(a: u128, b: u128) -> [u8; 32] { - let a = a.to_be_bytes(); - let b = b.to_be_bytes(); - std::array::from_fn(|i| { - if let Some(i) = i.checked_sub(a.len()) { - b[i] - } else { - a[i] - } - }) -} - -fn pack_user_operation(uo: &erc4337::PackedUserOperation) -> PackedUserOperation { - // INIT CODE, is factory address, appended with factory data - let init_code = if let Some(factory) = uo.factory { - let mut init_code = factory.to_vec(); - init_code.extend_from_slice(&uo.factory_data.clone().unwrap_or_default()); - Bytes::from(init_code) - } else { - Bytes::new() - }; - let account_gas_limits = pack_u256_pair_to_bytes32(uo.verification_gas_limit.into(), uo.call_gas_limit.into()); - let gas_fees = pack_u256_pair_to_bytes32(uo.max_priority_fee_per_gas.into(), uo.max_fee_per_gas.into()); - let pvgl: [u8; 16] = uo.paymaster_verification_gas_limit.unwrap_or_default().to_be_bytes(); - let pogl: [u8; 16] = uo.paymaster_post_op_gas_limit.unwrap_or_default().to_be_bytes(); - let paymaster_and_data = if let Some(paymaster) = uo.paymaster { - let mut paymaster_and_data = paymaster.to_vec(); - paymaster_and_data.extend_from_slice(&pvgl); - paymaster_and_data.extend_from_slice(&pogl); - paymaster_and_data.extend_from_slice(&uo.paymaster_data.clone().unwrap()); - Bytes::from(paymaster_and_data) - } else { - Bytes::new() - }; - PackedUserOperation { - sender: uo.sender, - nonce: uo.nonce, - initCode: init_code, - callData: uo.call_data.clone(), - accountGasLimits: FixedBytes::from(account_gas_limits), - preVerificationGas: U256::from(uo.pre_verification_gas), - gasFees: FixedBytes::from(gas_fees), - paymasterAndData: paymaster_and_data, - signature: uo.signature.clone(), - } -} - fn hash_packed_user_operation( puo: &PackedUserOperation, entry_point: Address, @@ -133,20 +129,19 @@ pub fn hash_user_operation_v07( entry_point: Address, chain_id: i32, ) -> FixedBytes<32> { - let packed = pack_user_operation(user_operation); + let packed = PackedUserOperation::from(user_operation.clone()); hash_packed_user_operation(&packed, entry_point, chain_id) } #[cfg(test)] mod test { use super::*; - use alloy_primitives::{address, b256, bytes, uint}; use alloy_primitives::{Bytes, U256}; + use alloy_primitives::{address, b256, bytes, uint}; use alloy_sol_types::{SolValue, sol}; #[test] fn test_hash() { - let puo = PackedUserOperation { sender: address!("b292Cf4a8E1fF21Ac27C4f94071Cd02C022C414b"), nonce: uint!(0xF83D07238A7C8814A48535035602123AD6DBFA63000000000000000000000001_U256), @@ -165,12 +160,15 @@ mod test { paymasterAndData: Bytes::default(), // Empty signature: bytes!("3c7bfe22c9c2ef8994a9637bcc4df1741c5dc0c25b209545a7aeb20f7770f351479b683bd17c4d55bc32e2a649c8d2dff49dcfcc1f3fd837bcd88d1e69a434cf1c"), }; - - let expected_hash = b256!("e486401370d145766c3cf7ba089553214a1230d38662ae532c9b62eb6dadcf7e"); - let uo = hash_packed_user_operation(&puo, address!("0x0000000071727De22E5E9d8BAf0edAc6f37da032"), 11155111); - + + let expected_hash = + b256!("e486401370d145766c3cf7ba089553214a1230d38662ae532c9b62eb6dadcf7e"); + let uo = hash_packed_user_operation( + &puo, + address!("0x0000000071727De22E5E9d8BAf0edAc6f37da032"), + 11155111, + ); + assert_eq!(uo, expected_hash); } } - - diff --git a/crates/account-abstraction-core/core/src/lib.rs b/crates/account-abstraction-core/core/src/lib.rs index cefac65b..fe08aa70 100644 --- a/crates/account-abstraction-core/core/src/lib.rs +++ b/crates/account-abstraction-core/core/src/lib.rs @@ -1,6 +1,5 @@ pub mod account_abstraction_service; +pub mod entrypoints; pub mod types; -pub mod v06; -pub mod v07; pub use account_abstraction_service::{AccountAbstractionService, AccountAbstractionServiceImpl}; pub use types::{SendUserOperationResponse, VersionedUserOperation}; diff --git a/crates/account-abstraction-core/core/src/types.rs b/crates/account-abstraction-core/core/src/types.rs index f22b9157..ea2a0f88 100644 --- a/crates/account-abstraction-core/core/src/types.rs +++ b/crates/account-abstraction-core/core/src/types.rs @@ -1,32 +1,62 @@ - -use alloy_primitives::{Address, B256, U256, keccak256}; +use crate::entrypoints; +use alloy_primitives::{Address, B256, U256, address, keccak256}; use alloy_rpc_types::erc4337; pub use alloy_rpc_types::erc4337::SendUserOperationResponse; -use crate::v06; -use crate::v07; +use anyhow::Result; +use serde::{Deserialize, Serialize}; pub trait UserOperation { fn hash(&self, entry_point: Address, chain_id: i32) -> B256; } impl UserOperation for erc4337::UserOperation { fn hash(&self, entry_point: Address, chain_id: i32) -> B256 { - v06::hash_user_operation_v06(self, entry_point, chain_id) + entrypoints::v06::hash_user_operation_v06(self, entry_point, chain_id) } } impl UserOperation for erc4337::PackedUserOperation { fn hash(&self, entry_point: Address, chain_id: i32) -> B256 { - v07::hash_user_operation_v07(self, entry_point, chain_id) + entrypoints::v07::hash_user_operation_v07(self, entry_point, chain_id) } } -use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(tag = "type")] pub enum VersionedUserOperation { - EntryPointV06(erc4337::UserOperation), - EntryPointV07(erc4337::PackedUserOperation), + UserOperation(erc4337::UserOperation), + PackedUserOperation(erc4337::PackedUserOperation), +} + +#[derive(Debug, Clone)] +pub enum EntryPointVersion { + V06, + V07, } + +impl EntryPointVersion { + pub const V06_ADDRESS: Address = address!("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"); + pub const V07_ADDRESS: Address = address!("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"); +} + +#[derive(Debug)] +pub struct UnknownEntryPointAddress { + pub address: Address, +} + +impl TryFrom
for EntryPointVersion { + type Error = UnknownEntryPointAddress; + + fn try_from(addr: Address) -> Result { + if addr == Self::V06_ADDRESS { + Ok(EntryPointVersion::V06) + } else if addr == Self::V07_ADDRESS { + Ok(EntryPointVersion::V07) + } else { + Err(UnknownEntryPointAddress { address: addr }) + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct UserOperationRequest { @@ -36,20 +66,32 @@ pub struct UserOperationRequest { } impl UserOperationRequest { - pub fn hash(&self) -> B256 { - match &self.user_operation { - VersionedUserOperation::EntryPointV06(user_operation) => { - user_operation.hash(self.entry_point, self.chain_id) + pub fn hash(&self) -> Result { + let entry_point_version = EntryPointVersion::try_from(self.entry_point); + if entry_point_version.is_err() { + return Err(anyhow::anyhow!( + "Unknown entry point version: {:#x}", + self.entry_point + )); + } + match (&self.user_operation, entry_point_version) { + (VersionedUserOperation::UserOperation(user_operation), Ok(EntryPointVersion::V06)) => { + Ok(user_operation.hash(self.entry_point, self.chain_id)) } - VersionedUserOperation::EntryPointV07(user_operation) => { - user_operation.hash(self.entry_point, self.chain_id) + ( + VersionedUserOperation::PackedUserOperation(user_operation), + Ok(EntryPointVersion::V07), + ) => Ok(user_operation.hash(self.entry_point, self.chain_id)), + _ => { + return Err(anyhow::anyhow!( + "Unknown entry point version: {:#x}", + self.entry_point + )); } } } } - - #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UserOperationRequestValidationResult { @@ -105,7 +147,7 @@ mod tests { } let user_operation = user_operation.unwrap(); match user_operation { - VersionedUserOperation::EntryPointV06(user_operation) => { + VersionedUserOperation::UserOperation(user_operation) => { assert_eq!( user_operation.sender, Address::from_str("0x1111111111111111111111111111111111111111").unwrap() @@ -162,7 +204,7 @@ mod tests { } let user_operation = user_operation.unwrap(); match user_operation { - VersionedUserOperation::EntryPointV07(user_operation) => { + VersionedUserOperation::PackedUserOperation(user_operation) => { assert_eq!( user_operation.sender, Address::from_str("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48").unwrap() diff --git a/crates/ingress-rpc/src/service.rs b/crates/ingress-rpc/src/service.rs index ebb53757..6bce222d 100644 --- a/crates/ingress-rpc/src/service.rs +++ b/crates/ingress-rpc/src/service.rs @@ -276,11 +276,11 @@ impl IngressApiServer for IngressService { .user_op_queue_publisher .publish( &user_operation_request.user_operation, - &user_operation_request.hash(), + &user_operation_request.hash().unwrap(), ) .await { - warn!(message = "Failed to publish user operation to queue", user_operation_hash = %user_operation_request.hash(), error = %e); + warn!(message = "Failed to publish user operation to queue", user_operation_hash = %user_operation_request.hash().unwrap(), error = %e); return Err( EthApiError::InvalidParams("Failed to queue user operation".into()).into_rpc_err(), ); From c7f4ac39ad38dd126a91a065d4c73368baa2120e Mon Sep 17 00:00:00 2001 From: Rayyan Alam Date: Fri, 5 Dec 2025 12:17:39 -0500 Subject: [PATCH 12/24] chore: clean up queue rs --- .../core/src/types.rs | 19 +--- crates/ingress-rpc/src/bin/main.rs | 2 +- crates/ingress-rpc/src/lib.rs | 1 - crates/ingress-rpc/src/queue.rs | 96 ++++++++--------- crates/ingress-rpc/src/queue2.rs | 101 ------------------ crates/ingress-rpc/src/service.rs | 2 +- 6 files changed, 47 insertions(+), 174 deletions(-) delete mode 100644 crates/ingress-rpc/src/queue2.rs diff --git a/crates/account-abstraction-core/core/src/types.rs b/crates/account-abstraction-core/core/src/types.rs index ea2a0f88..a4adfdf2 100644 --- a/crates/account-abstraction-core/core/src/types.rs +++ b/crates/account-abstraction-core/core/src/types.rs @@ -4,21 +4,6 @@ use alloy_rpc_types::erc4337; pub use alloy_rpc_types::erc4337::SendUserOperationResponse; use anyhow::Result; use serde::{Deserialize, Serialize}; -pub trait UserOperation { - fn hash(&self, entry_point: Address, chain_id: i32) -> B256; -} - -impl UserOperation for erc4337::UserOperation { - fn hash(&self, entry_point: Address, chain_id: i32) -> B256 { - entrypoints::v06::hash_user_operation_v06(self, entry_point, chain_id) - } -} - -impl UserOperation for erc4337::PackedUserOperation { - fn hash(&self, entry_point: Address, chain_id: i32) -> B256 { - entrypoints::v07::hash_user_operation_v07(self, entry_point, chain_id) - } -} #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(tag = "type")] @@ -76,12 +61,12 @@ impl UserOperationRequest { } match (&self.user_operation, entry_point_version) { (VersionedUserOperation::UserOperation(user_operation), Ok(EntryPointVersion::V06)) => { - Ok(user_operation.hash(self.entry_point, self.chain_id)) + Ok(entrypoints::v06::hash_user_operation_v06(user_operation, self.entry_point, self.chain_id)) } ( VersionedUserOperation::PackedUserOperation(user_operation), Ok(EntryPointVersion::V07), - ) => Ok(user_operation.hash(self.entry_point, self.chain_id)), + ) => Ok(entrypoints::v07::hash_user_operation_v07(user_operation, self.entry_point, self.chain_id)), _ => { return Err(anyhow::anyhow!( "Unknown entry point version: {:#x}", diff --git a/crates/ingress-rpc/src/bin/main.rs b/crates/ingress-rpc/src/bin/main.rs index fecae9db..7a1ce23c 100644 --- a/crates/ingress-rpc/src/bin/main.rs +++ b/crates/ingress-rpc/src/bin/main.rs @@ -12,7 +12,7 @@ use tips_ingress_rpc::Config; use tips_ingress_rpc::connect_ingress_to_builder; use tips_ingress_rpc::health::bind_health_server; use tips_ingress_rpc::metrics::init_prometheus_exporter; -use tips_ingress_rpc::queue2::KafkaMessageQueue; +use tips_ingress_rpc::queue::KafkaMessageQueue; use tips_ingress_rpc::service::{IngressApiServer, IngressService}; use tokio::sync::{broadcast, mpsc}; use tracing::info; diff --git a/crates/ingress-rpc/src/lib.rs b/crates/ingress-rpc/src/lib.rs index c91d731a..e157964b 100644 --- a/crates/ingress-rpc/src/lib.rs +++ b/crates/ingress-rpc/src/lib.rs @@ -1,7 +1,6 @@ pub mod health; pub mod metrics; pub mod queue; -pub mod queue2; pub mod service; pub mod validation; use alloy_primitives::TxHash; diff --git a/crates/ingress-rpc/src/queue.rs b/crates/ingress-rpc/src/queue.rs index c3df12a1..d8028ac2 100644 --- a/crates/ingress-rpc/src/queue.rs +++ b/crates/ingress-rpc/src/queue.rs @@ -1,3 +1,4 @@ +use account_abstraction_core::types::VersionedUserOperation; use alloy_primitives::B256; use anyhow::Result; use async_trait::async_trait; @@ -7,49 +8,44 @@ use tips_core::AcceptedBundle; use tokio::time::Duration; use tracing::{error, info}; -/// A queue to buffer transactions #[async_trait] -pub trait QueuePublisher: Send + Sync { - async fn publish(&self, bundle: &AcceptedBundle, bundle_hash: &B256) -> Result<()>; +pub trait MessageQueue: Send + Sync { + async fn publish_raw(&self, topic: &str, key: &str, payload: &[u8]) -> Result<()>; } -/// A queue to buffer transactions -pub struct KafkaQueuePublisher { + +pub struct KafkaMessageQueue { producer: FutureProducer, - topic: String, } -impl KafkaQueuePublisher { - pub fn new(producer: FutureProducer, topic: String) -> Self { - Self { producer, topic } +impl KafkaMessageQueue { + pub fn new(producer: FutureProducer) -> Self { + Self { producer } } } #[async_trait] -impl QueuePublisher for KafkaQueuePublisher { - async fn publish(&self, bundle: &AcceptedBundle, bundle_hash: &B256) -> Result<()> { - let key = bundle_hash.to_string(); - let payload = serde_json::to_vec(&bundle)?; - +impl MessageQueue for KafkaMessageQueue { + async fn publish_raw(&self, topic: &str, key: &str, payload: &[u8]) -> Result<()> { let enqueue = || async { - let record = FutureRecord::to(&self.topic).key(&key).payload(&payload); + let record = FutureRecord::to(topic).key(key).payload(payload); match self.producer.send(record, Duration::from_secs(5)).await { Ok((partition, offset)) => { info!( - bundle_hash = %bundle_hash, + key = %key, partition = partition, offset = offset, - topic = %self.topic, - "Successfully enqueued bundle" + topic = %topic, + "Successfully enqueued message" ); Ok(()) } Err((err, _)) => { error!( - bundle_hash = %bundle_hash, + key = key, error = %err, - topic = %self.topic, - "Failed to enqueue bundle" + topic = topic, + "Failed to enqueue message" ); Err(anyhow::anyhow!("Failed to enqueue bundle: {err}")) } @@ -64,48 +60,42 @@ impl QueuePublisher for KafkaQueuePublisher { .with_max_times(3), ) .notify(|err: &anyhow::Error, dur: Duration| { - info!("retrying to enqueue bundle {:?} after {:?}", err, dur); + info!("retrying to enqueue message {:?} after {:?}", err, dur); }) .await } } -#[cfg(test)] -mod tests { - use super::*; - use rdkafka::config::ClientConfig; - use tips_core::{ - AcceptedBundle, Bundle, BundleExtensions, test_utils::create_test_meter_bundle_response, - }; - use tokio::time::{Duration, Instant}; +pub struct UserOpQueuePublisher { + queue: std::sync::Arc, + topic: String, +} - fn create_test_bundle() -> Bundle { - Bundle::default() +impl UserOpQueuePublisher { + pub fn new(queue: std::sync::Arc, topic: String) -> Self { + Self { queue, topic } } - #[tokio::test] - async fn test_backoff_retry_logic() { - // use an invalid broker address to trigger the backoff logic - let producer = ClientConfig::new() - .set("bootstrap.servers", "localhost:9999") - .set("message.timeout.ms", "100") - .create() - .expect("Producer creation failed"); + pub async fn publish(&self, user_op: &VersionedUserOperation, hash: &B256) -> Result<()> { + let key = hash.to_string(); + let payload = serde_json::to_vec(&user_op)?; + self.queue.publish_raw(&self.topic, &key, &payload).await + } +} - let publisher = KafkaQueuePublisher::new(producer, "tips-ingress-rpc".to_string()); - let bundle = create_test_bundle(); - let accepted_bundle = AcceptedBundle::new( - bundle.try_into().unwrap(), - create_test_meter_bundle_response(), - ); - let bundle_hash = &accepted_bundle.bundle_hash(); +pub struct BundleQueuePublisher { + queue: std::sync::Arc, + topic: String, +} - let start = Instant::now(); - let result = publisher.publish(&accepted_bundle, bundle_hash).await; - let elapsed = start.elapsed(); +impl BundleQueuePublisher { + pub fn new(queue: std::sync::Arc, topic: String) -> Self { + Self { queue, topic } + } - // the backoff tries at minimum 100ms, so verify we tried at least once - assert!(result.is_err()); - assert!(elapsed >= Duration::from_millis(100)); + pub async fn publish(&self, bundle: &AcceptedBundle, hash: &B256) -> Result<()> { + let key = hash.to_string(); + let payload = serde_json::to_vec(bundle)?; + self.queue.publish_raw(&self.topic, &key, &payload).await } } diff --git a/crates/ingress-rpc/src/queue2.rs b/crates/ingress-rpc/src/queue2.rs deleted file mode 100644 index d8028ac2..00000000 --- a/crates/ingress-rpc/src/queue2.rs +++ /dev/null @@ -1,101 +0,0 @@ -use account_abstraction_core::types::VersionedUserOperation; -use alloy_primitives::B256; -use anyhow::Result; -use async_trait::async_trait; -use backon::{ExponentialBuilder, Retryable}; -use rdkafka::producer::{FutureProducer, FutureRecord}; -use tips_core::AcceptedBundle; -use tokio::time::Duration; -use tracing::{error, info}; - -#[async_trait] -pub trait MessageQueue: Send + Sync { - async fn publish_raw(&self, topic: &str, key: &str, payload: &[u8]) -> Result<()>; -} - -pub struct KafkaMessageQueue { - producer: FutureProducer, -} - -impl KafkaMessageQueue { - pub fn new(producer: FutureProducer) -> Self { - Self { producer } - } -} - -#[async_trait] -impl MessageQueue for KafkaMessageQueue { - async fn publish_raw(&self, topic: &str, key: &str, payload: &[u8]) -> Result<()> { - let enqueue = || async { - let record = FutureRecord::to(topic).key(key).payload(payload); - - match self.producer.send(record, Duration::from_secs(5)).await { - Ok((partition, offset)) => { - info!( - key = %key, - partition = partition, - offset = offset, - topic = %topic, - "Successfully enqueued message" - ); - Ok(()) - } - Err((err, _)) => { - error!( - key = key, - error = %err, - topic = topic, - "Failed to enqueue message" - ); - Err(anyhow::anyhow!("Failed to enqueue bundle: {err}")) - } - } - }; - - enqueue - .retry( - &ExponentialBuilder::default() - .with_min_delay(Duration::from_millis(100)) - .with_max_delay(Duration::from_secs(5)) - .with_max_times(3), - ) - .notify(|err: &anyhow::Error, dur: Duration| { - info!("retrying to enqueue message {:?} after {:?}", err, dur); - }) - .await - } -} - -pub struct UserOpQueuePublisher { - queue: std::sync::Arc, - topic: String, -} - -impl UserOpQueuePublisher { - pub fn new(queue: std::sync::Arc, topic: String) -> Self { - Self { queue, topic } - } - - pub async fn publish(&self, user_op: &VersionedUserOperation, hash: &B256) -> Result<()> { - let key = hash.to_string(); - let payload = serde_json::to_vec(&user_op)?; - self.queue.publish_raw(&self.topic, &key, &payload).await - } -} - -pub struct BundleQueuePublisher { - queue: std::sync::Arc, - topic: String, -} - -impl BundleQueuePublisher { - pub fn new(queue: std::sync::Arc, topic: String) -> Self { - Self { queue, topic } - } - - pub async fn publish(&self, bundle: &AcceptedBundle, hash: &B256) -> Result<()> { - let key = hash.to_string(); - let payload = serde_json::to_vec(bundle)?; - self.queue.publish_raw(&self.topic, &key, &payload).await - } -} diff --git a/crates/ingress-rpc/src/service.rs b/crates/ingress-rpc/src/service.rs index 6bce222d..1c030f22 100644 --- a/crates/ingress-rpc/src/service.rs +++ b/crates/ingress-rpc/src/service.rs @@ -20,7 +20,7 @@ use tokio::time::{Duration, Instant, timeout}; use tracing::{info, warn}; use crate::metrics::{Metrics, record_histogram}; -use crate::queue2::{BundleQueuePublisher, KafkaMessageQueue, UserOpQueuePublisher}; +use crate::queue::{BundleQueuePublisher, KafkaMessageQueue, UserOpQueuePublisher}; use crate::validation::validate_bundle; use crate::{Config, TxSubmissionMethod}; use account_abstraction_core::types::{SendUserOperationResponse, UserOperationRequest}; From e94553af8fb5908029b91d9d26f870cfdfe72ca2 Mon Sep 17 00:00:00 2001 From: Rayyan Alam Date: Mon, 8 Dec 2025 08:35:40 -0500 Subject: [PATCH 13/24] feat: clean up system for types rs --- .../core/src/entrypoints/mod.rs | 1 + .../core/src/entrypoints/v06.rs | 10 +-- .../core/src/entrypoints/v07.rs | 11 ++- .../core/src/entrypoints/version.rs | 32 +++++++++ .../core/src/types.rs | 70 ++++--------------- 5 files changed, 58 insertions(+), 66 deletions(-) create mode 100644 crates/account-abstraction-core/core/src/entrypoints/version.rs diff --git a/crates/account-abstraction-core/core/src/entrypoints/mod.rs b/crates/account-abstraction-core/core/src/entrypoints/mod.rs index fa873c59..59115138 100644 --- a/crates/account-abstraction-core/core/src/entrypoints/mod.rs +++ b/crates/account-abstraction-core/core/src/entrypoints/mod.rs @@ -1,2 +1,3 @@ pub mod v06; pub mod v07; +pub mod version; \ No newline at end of file diff --git a/crates/account-abstraction-core/core/src/entrypoints/v06.rs b/crates/account-abstraction-core/core/src/entrypoints/v06.rs index 8cb9bc6b..4f76b764 100644 --- a/crates/account-abstraction-core/core/src/entrypoints/v06.rs +++ b/crates/account-abstraction-core/core/src/entrypoints/v06.rs @@ -1,4 +1,4 @@ -use alloy_primitives::U256; +use alloy_primitives::{U256, ChainId}; use alloy_rpc_types::erc4337; use alloy_sol_types::{SolValue, sol}; sol! { @@ -43,10 +43,10 @@ impl From for UserOperationPackedForHash { } } -pub fn hash_user_operation_v06( +pub fn hash_user_operation( user_operation: &erc4337::UserOperation, entry_point: alloy_primitives::Address, - chain_id: i32, + chain_id: ChainId, ) -> alloy_primitives::B256 { let packed = UserOperationPackedForHash::from(user_operation.clone()); let encoded = UserOperationHashEncoded { @@ -82,7 +82,7 @@ mod tests { }; let hash = - hash_user_operation_v06(&userOpWithZeroedInitCode, entry_point_address_v0_6, chainId); + hash_user_operation(&userOpWithZeroedInitCode, entry_point_address_v0_6, chainId); assert_eq!( hash, b256!("dca97c3b49558ab360659f6ead939773be8bf26631e61bb17045bb70dc983b2d") @@ -113,7 +113,7 @@ mod tests { signature: bytes!("da0929f527cded8d0a1eaf2e8861d7f7e2d8160b7b13942f99dd367df4473a"), }; - let hash = hash_user_operation_v06( + let hash = hash_user_operation( &userOpWithNonZeroedInitCode, entry_point_address_v0_6, chainId, diff --git a/crates/account-abstraction-core/core/src/entrypoints/v07.rs b/crates/account-abstraction-core/core/src/entrypoints/v07.rs index c1d99bf3..6302d4bc 100644 --- a/crates/account-abstraction-core/core/src/entrypoints/v07.rs +++ b/crates/account-abstraction-core/core/src/entrypoints/v07.rs @@ -1,4 +1,4 @@ -use alloy_primitives::{Address, FixedBytes, U256}; +use alloy_primitives::{Address, FixedBytes, U256, ChainId}; use alloy_primitives::{Bytes, keccak256}; use alloy_rpc_types::erc4337; use alloy_sol_types::{SolValue, sol}; @@ -68,7 +68,7 @@ impl From for PackedUserOperation { let mut paymaster_and_data = paymaster.to_vec(); paymaster_and_data.extend_from_slice(&pvgl); paymaster_and_data.extend_from_slice(&pogl); - paymaster_and_data.extend_from_slice(&uo.paymaster_data.clone().unwrap()); + paymaster_and_data.extend_from_slice(&uo.paymaster_data.unwrap()); Bytes::from(paymaster_and_data) } else { Bytes::new() @@ -96,7 +96,7 @@ fn pack_u256_pair_to_bytes32(high: U256, low: U256) -> FixedBytes<32> { fn hash_packed_user_operation( puo: &PackedUserOperation, entry_point: Address, - chain_id: i32, + chain_id: u64, ) -> FixedBytes<32> { let hash_init_code = alloy_primitives::keccak256(&puo.initCode); let hash_call_data = alloy_primitives::keccak256(&puo.callData); @@ -124,10 +124,10 @@ fn hash_packed_user_operation( keccak256(encoded.abi_encode()) } -pub fn hash_user_operation_v07( +pub fn hash_user_operation( user_operation: &erc4337::PackedUserOperation, entry_point: Address, - chain_id: i32, + chain_id: ChainId, ) -> FixedBytes<32> { let packed = PackedUserOperation::from(user_operation.clone()); hash_packed_user_operation(&packed, entry_point, chain_id) @@ -138,7 +138,6 @@ mod test { use super::*; use alloy_primitives::{Bytes, U256}; use alloy_primitives::{address, b256, bytes, uint}; - use alloy_sol_types::{SolValue, sol}; #[test] fn test_hash() { diff --git a/crates/account-abstraction-core/core/src/entrypoints/version.rs b/crates/account-abstraction-core/core/src/entrypoints/version.rs new file mode 100644 index 00000000..0e0d1cac --- /dev/null +++ b/crates/account-abstraction-core/core/src/entrypoints/version.rs @@ -0,0 +1,32 @@ +use alloy_primitives::{Address, B256, U256, address, keccak256}; + + +#[derive(Debug, Clone)] +pub enum EntryPointVersion { + V06, + V07, +} + +impl EntryPointVersion { + pub const V06_ADDRESS: Address = address!("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"); + pub const V07_ADDRESS: Address = address!("0x0000000071727De22E5E9d8BAf0edAc6f37da032"); +} + +#[derive(Debug)] +pub struct UnknownEntryPointAddress { + pub address: Address, +} + +impl TryFrom
for EntryPointVersion { + type Error = UnknownEntryPointAddress; + + fn try_from(addr: Address) -> Result { + if addr == Self::V06_ADDRESS { + Ok(EntryPointVersion::V06) + } else if addr == Self::V07_ADDRESS { + Ok(EntryPointVersion::V07) + } else { + Err(UnknownEntryPointAddress { address: addr }) + } + } +} \ No newline at end of file diff --git a/crates/account-abstraction-core/core/src/types.rs b/crates/account-abstraction-core/core/src/types.rs index a4adfdf2..4721ad50 100644 --- a/crates/account-abstraction-core/core/src/types.rs +++ b/crates/account-abstraction-core/core/src/types.rs @@ -1,5 +1,5 @@ -use crate::entrypoints; -use alloy_primitives::{Address, B256, U256, address, keccak256}; +use crate::entrypoints::{version::EntryPointVersion, v06, v07}; +use alloy_primitives::{Address, B256, U256, ChainId}; use alloy_rpc_types::erc4337; pub use alloy_rpc_types::erc4337::SendUserOperationResponse; use anyhow::Result; @@ -11,69 +11,29 @@ pub enum VersionedUserOperation { UserOperation(erc4337::UserOperation), PackedUserOperation(erc4337::PackedUserOperation), } - -#[derive(Debug, Clone)] -pub enum EntryPointVersion { - V06, - V07, -} - -impl EntryPointVersion { - pub const V06_ADDRESS: Address = address!("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"); - pub const V07_ADDRESS: Address = address!("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"); -} - -#[derive(Debug)] -pub struct UnknownEntryPointAddress { - pub address: Address, -} - -impl TryFrom
for EntryPointVersion { - type Error = UnknownEntryPointAddress; - - fn try_from(addr: Address) -> Result { - if addr == Self::V06_ADDRESS { - Ok(EntryPointVersion::V06) - } else if addr == Self::V07_ADDRESS { - Ok(EntryPointVersion::V07) - } else { - Err(UnknownEntryPointAddress { address: addr }) - } - } -} - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] - pub struct UserOperationRequest { pub user_operation: VersionedUserOperation, pub entry_point: Address, - pub chain_id: i32, + pub chain_id: ChainId, } impl UserOperationRequest { pub fn hash(&self) -> Result { - let entry_point_version = EntryPointVersion::try_from(self.entry_point); - if entry_point_version.is_err() { - return Err(anyhow::anyhow!( - "Unknown entry point version: {:#x}", - self.entry_point - )); + let entry_point_version = EntryPointVersion::try_from(self.entry_point) + .map_err(|_| anyhow::anyhow!("Unknown entry point version: {:#x}", self.entry_point))?; + + match (&self.user_operation, entry_point_version) { + (VersionedUserOperation::UserOperation(op), EntryPointVersion::V06) => { + Ok(v06::hash_user_operation(op, self.entry_point, self.chain_id)) } - match (&self.user_operation, entry_point_version) { - (VersionedUserOperation::UserOperation(user_operation), Ok(EntryPointVersion::V06)) => { - Ok(entrypoints::v06::hash_user_operation_v06(user_operation, self.entry_point, self.chain_id)) - } - ( - VersionedUserOperation::PackedUserOperation(user_operation), - Ok(EntryPointVersion::V07), - ) => Ok(entrypoints::v07::hash_user_operation_v07(user_operation, self.entry_point, self.chain_id)), - _ => { - return Err(anyhow::anyhow!( - "Unknown entry point version: {:#x}", - self.entry_point - )); - } + (VersionedUserOperation::PackedUserOperation(op), EntryPointVersion::V07) => { + Ok(v07::hash_user_operation(op, self.entry_point, self.chain_id)) } + _ => Err(anyhow::anyhow!( + "Mismatched operation type and entry point version" + )), + } } } From 5e16d81c3291d90d6d21b8d0576ce1ee95472667 Mon Sep 17 00:00:00 2001 From: Rayyan Alam Date: Mon, 8 Dec 2025 08:46:46 -0500 Subject: [PATCH 14/24] chore: clean up and formating --- .../core/src/entrypoints/mod.rs | 2 +- .../core/src/entrypoints/v06.rs | 2 +- .../core/src/entrypoints/v07.rs | 12 +++--- .../core/src/entrypoints/version.rs | 5 +-- .../core/src/types.rs | 26 ++++++------- crates/ingress-rpc/src/service.rs | 37 +++++++++++-------- 6 files changed, 44 insertions(+), 40 deletions(-) diff --git a/crates/account-abstraction-core/core/src/entrypoints/mod.rs b/crates/account-abstraction-core/core/src/entrypoints/mod.rs index 59115138..4946ba2e 100644 --- a/crates/account-abstraction-core/core/src/entrypoints/mod.rs +++ b/crates/account-abstraction-core/core/src/entrypoints/mod.rs @@ -1,3 +1,3 @@ pub mod v06; pub mod v07; -pub mod version; \ No newline at end of file +pub mod version; diff --git a/crates/account-abstraction-core/core/src/entrypoints/v06.rs b/crates/account-abstraction-core/core/src/entrypoints/v06.rs index 4f76b764..925a21ce 100644 --- a/crates/account-abstraction-core/core/src/entrypoints/v06.rs +++ b/crates/account-abstraction-core/core/src/entrypoints/v06.rs @@ -1,4 +1,4 @@ -use alloy_primitives::{U256, ChainId}; +use alloy_primitives::{ChainId, U256}; use alloy_rpc_types::erc4337; use alloy_sol_types::{SolValue, sol}; sol! { diff --git a/crates/account-abstraction-core/core/src/entrypoints/v07.rs b/crates/account-abstraction-core/core/src/entrypoints/v07.rs index 6302d4bc..a8763401 100644 --- a/crates/account-abstraction-core/core/src/entrypoints/v07.rs +++ b/crates/account-abstraction-core/core/src/entrypoints/v07.rs @@ -1,4 +1,4 @@ -use alloy_primitives::{Address, FixedBytes, U256, ChainId}; +use alloy_primitives::{Address, ChainId, FixedBytes, U256}; use alloy_primitives::{Bytes, keccak256}; use alloy_rpc_types::erc4337; use alloy_sol_types::{SolValue, sol}; @@ -51,10 +51,10 @@ impl From for PackedUserOperation { Bytes::new() }; let account_gas_limits = - pack_u256_pair_to_bytes32(uo.verification_gas_limit.into(), uo.call_gas_limit.into()); + pack_u256_pair_to_bytes32(uo.verification_gas_limit, uo.call_gas_limit); let gas_fees = pack_u256_pair_to_bytes32( - uo.max_priority_fee_per_gas.into(), - uo.max_fee_per_gas.into(), + uo.max_priority_fee_per_gas, + uo.max_fee_per_gas, ); let pvgl: [u8; 16] = uo .paymaster_verification_gas_limit @@ -78,9 +78,9 @@ impl From for PackedUserOperation { nonce: uo.nonce, initCode: init_code, callData: uo.call_data.clone(), - accountGasLimits: FixedBytes::from(account_gas_limits), + accountGasLimits: account_gas_limits, preVerificationGas: U256::from(uo.pre_verification_gas), - gasFees: FixedBytes::from(gas_fees), + gasFees: gas_fees, paymasterAndData: paymaster_and_data, signature: uo.signature.clone(), } diff --git a/crates/account-abstraction-core/core/src/entrypoints/version.rs b/crates/account-abstraction-core/core/src/entrypoints/version.rs index 0e0d1cac..ae37e5d4 100644 --- a/crates/account-abstraction-core/core/src/entrypoints/version.rs +++ b/crates/account-abstraction-core/core/src/entrypoints/version.rs @@ -1,5 +1,4 @@ -use alloy_primitives::{Address, B256, U256, address, keccak256}; - +use alloy_primitives::{Address, address}; #[derive(Debug, Clone)] pub enum EntryPointVersion { @@ -29,4 +28,4 @@ impl TryFrom
for EntryPointVersion { Err(UnknownEntryPointAddress { address: addr }) } } -} \ No newline at end of file +} diff --git a/crates/account-abstraction-core/core/src/types.rs b/crates/account-abstraction-core/core/src/types.rs index 4721ad50..7d6d2731 100644 --- a/crates/account-abstraction-core/core/src/types.rs +++ b/crates/account-abstraction-core/core/src/types.rs @@ -1,5 +1,5 @@ -use crate::entrypoints::{version::EntryPointVersion, v06, v07}; -use alloy_primitives::{Address, B256, U256, ChainId}; +use crate::entrypoints::{v06, v07, version::EntryPointVersion}; +use alloy_primitives::{Address, B256, ChainId, U256}; use alloy_rpc_types::erc4337; pub use alloy_rpc_types::erc4337::SendUserOperationResponse; use anyhow::Result; @@ -21,19 +21,19 @@ pub struct UserOperationRequest { impl UserOperationRequest { pub fn hash(&self) -> Result { let entry_point_version = EntryPointVersion::try_from(self.entry_point) - .map_err(|_| anyhow::anyhow!("Unknown entry point version: {:#x}", self.entry_point))?; + .map_err(|_| anyhow::anyhow!("Unknown entry point version: {:#x}", self.entry_point))?; - match (&self.user_operation, entry_point_version) { - (VersionedUserOperation::UserOperation(op), EntryPointVersion::V06) => { - Ok(v06::hash_user_operation(op, self.entry_point, self.chain_id)) + match (&self.user_operation, entry_point_version) { + (VersionedUserOperation::UserOperation(op), EntryPointVersion::V06) => Ok( + v06::hash_user_operation(op, self.entry_point, self.chain_id), + ), + (VersionedUserOperation::PackedUserOperation(op), EntryPointVersion::V07) => Ok( + v07::hash_user_operation(op, self.entry_point, self.chain_id), + ), + _ => Err(anyhow::anyhow!( + "Mismatched operation type and entry point version" + )), } - (VersionedUserOperation::PackedUserOperation(op), EntryPointVersion::V07) => { - Ok(v07::hash_user_operation(op, self.entry_point, self.chain_id)) - } - _ => Err(anyhow::anyhow!( - "Mismatched operation type and entry point version" - )), - } } } diff --git a/crates/ingress-rpc/src/service.rs b/crates/ingress-rpc/src/service.rs index a2ee5650..801a6296 100644 --- a/crates/ingress-rpc/src/service.rs +++ b/crates/ingress-rpc/src/service.rs @@ -20,7 +20,7 @@ use tokio::time::{Duration, Instant, timeout}; use tracing::{debug, info, warn}; use crate::metrics::{Metrics, record_histogram}; -use crate::queue::{BundleQueuePublisher, KafkaMessageQueue, UserOpQueuePublisher, MessageQueue}; +use crate::queue::{BundleQueuePublisher, KafkaMessageQueue, MessageQueue, UserOpQueuePublisher}; use crate::validation::validate_bundle; use crate::{Config, TxSubmissionMethod}; use account_abstraction_core::types::{SendUserOperationResponse, UserOperationRequest}; @@ -59,14 +59,14 @@ pub trait IngressApi { ) -> RpcResult; } -pub struct IngressService { +pub struct IngressService { mempool_provider: Arc>, simulation_provider: Arc>, raw_tx_forward_provider: Option>>, account_abstraction_service: AccountAbstractionServiceImpl, tx_submission_method: TxSubmissionMethod, - bundle_queue_publisher: BundleQueuePublisher, - user_op_queue_publisher: UserOpQueuePublisher, + bundle_queue_publisher: BundleQueuePublisher, + user_op_queue_publisher: UserOpQueuePublisher, audit_channel: mpsc::UnboundedSender, send_transaction_default_lifetime_seconds: u64, metrics: Metrics, @@ -77,10 +77,10 @@ pub struct IngressService { builder_backrun_tx: broadcast::Sender, } -impl IngressService { +impl IngressService { pub fn new( providers: Providers, - queue: KafkaMessageQueue, + queue: Q, audit_channel: mpsc::UnboundedSender, builder_tx: broadcast::Sender, builder_backrun_tx: broadcast::Sender, @@ -123,7 +123,7 @@ impl IngressService { } #[async_trait] -impl IngressApiServer for IngressService { +impl IngressApiServer for IngressService { async fn send_backrun_bundle(&self, bundle: Bundle) -> RpcResult { if !self.backrun_enabled { info!( @@ -298,20 +298,25 @@ impl IngressApiServer for IngressService { &self, user_operation_request: UserOperationRequest, ) -> RpcResult { - dbg!(&user_operation_request); + let user_op_hash = user_operation_request + .hash() + .map_err(|e| EthApiError::InvalidParams(e.to_string()).into_rpc_err())?; + let _ = self .account_abstraction_service .validate_user_operation(&user_operation_request.user_operation) .await?; + if let Err(e) = self .user_op_queue_publisher - .publish( - &user_operation_request.user_operation, - &user_operation_request.hash().unwrap(), - ) + .publish(&user_operation_request.user_operation, &user_op_hash) .await { - warn!(message = "Failed to publish user operation to queue", user_operation_hash = %user_operation_request.hash().unwrap(), error = %e); + warn!( + message = "Failed to publish user operation to queue", + user_operation_hash = %user_op_hash, + error = %e + ); return Err( EthApiError::InvalidParams("Failed to queue user operation".into()).into_rpc_err(), ); @@ -320,7 +325,7 @@ impl IngressApiServer for IngressService { } } -impl IngressService { +impl IngressService { async fn get_tx(&self, data: &Bytes) -> RpcResult> { if data.is_empty() { return Err(EthApiError::EmptyRawTransactionData.into_rpc_err()); @@ -439,6 +444,7 @@ mod tests { use super::*; use crate::{Config, TxSubmissionMethod, queue::MessageQueue}; use alloy_provider::RootProvider; + use anyhow::Result; use async_trait::async_trait; use std::net::{IpAddr, SocketAddr}; use std::str::FromStr; @@ -446,7 +452,6 @@ mod tests { use tokio::sync::{broadcast, mpsc}; use url::Url; use wiremock::{Mock, MockServer, ResponseTemplate, matchers::method}; - use anyhow::Result; struct MockQueue; #[async_trait] @@ -563,7 +568,7 @@ mod tests { let (backrun_tx, _backrun_rx) = broadcast::channel(1); let service = IngressService::new( - providers, KafkaMessageQueue::new(FutureProducer::new(MockQueue)), audit_tx, builder_tx, backrun_tx, config, + providers, MockQueue, audit_tx, builder_tx, backrun_tx, config, ); let bundle = Bundle::default(); From 56f2537572ca9f3acfb606175e930b2110d47b01 Mon Sep 17 00:00:00 2001 From: Rayyan Alam Date: Mon, 8 Dec 2025 08:56:07 -0500 Subject: [PATCH 15/24] chore: fix build --- .../core/src/entrypoints/v07.rs | 10 +++++----- crates/ingress-rpc/src/service.rs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/account-abstraction-core/core/src/entrypoints/v07.rs b/crates/account-abstraction-core/core/src/entrypoints/v07.rs index a8763401..6b4c6cc4 100644 --- a/crates/account-abstraction-core/core/src/entrypoints/v07.rs +++ b/crates/account-abstraction-core/core/src/entrypoints/v07.rs @@ -52,17 +52,16 @@ impl From for PackedUserOperation { }; let account_gas_limits = pack_u256_pair_to_bytes32(uo.verification_gas_limit, uo.call_gas_limit); - let gas_fees = pack_u256_pair_to_bytes32( - uo.max_priority_fee_per_gas, - uo.max_fee_per_gas, - ); + let gas_fees = pack_u256_pair_to_bytes32(uo.max_priority_fee_per_gas, uo.max_fee_per_gas); let pvgl: [u8; 16] = uo .paymaster_verification_gas_limit .unwrap_or_default() + .to::() .to_be_bytes(); let pogl: [u8; 16] = uo .paymaster_post_op_gas_limit .unwrap_or_default() + .to::() .to_be_bytes(); let paymaster_and_data = if let Some(paymaster) = uo.paymaster { let mut paymaster_and_data = paymaster.to_vec(); @@ -90,7 +89,8 @@ fn pack_u256_pair_to_bytes32(high: U256, low: U256) -> FixedBytes<32> { let mask = (U256::from(1u64) << 128) - U256::from(1u64); let hi = high & mask; let lo = low & mask; - FixedBytes::from((hi << 128) | lo) + let combined: U256 = (hi << 128) | lo; + FixedBytes::from(combined.to_be_bytes::<32>()) } fn hash_packed_user_operation( diff --git a/crates/ingress-rpc/src/service.rs b/crates/ingress-rpc/src/service.rs index 801a6296..43c5a4ad 100644 --- a/crates/ingress-rpc/src/service.rs +++ b/crates/ingress-rpc/src/service.rs @@ -20,7 +20,7 @@ use tokio::time::{Duration, Instant, timeout}; use tracing::{debug, info, warn}; use crate::metrics::{Metrics, record_histogram}; -use crate::queue::{BundleQueuePublisher, KafkaMessageQueue, MessageQueue, UserOpQueuePublisher}; +use crate::queue::{BundleQueuePublisher, MessageQueue, UserOpQueuePublisher}; use crate::validation::validate_bundle; use crate::{Config, TxSubmissionMethod}; use account_abstraction_core::types::{SendUserOperationResponse, UserOperationRequest}; @@ -59,7 +59,7 @@ pub trait IngressApi { ) -> RpcResult; } -pub struct IngressService { +pub struct IngressService { mempool_provider: Arc>, simulation_provider: Arc>, raw_tx_forward_provider: Option>>, From a1c16270599b7fcc77814aeb56138af008f36efa Mon Sep 17 00:00:00 2001 From: Rayyan Alam Date: Mon, 8 Dec 2025 09:10:06 -0500 Subject: [PATCH 16/24] chore: clean up cargo --- Cargo.lock | 22 ------------------- Cargo.toml | 2 -- crates/account-abstraction-core/Cargo.toml | 2 -- .../core/src/entrypoints/v06.rs | 14 ++++++------ .../core/src/types.rs | 6 ++--- 5 files changed, 10 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c5774251..6faa5290 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,8 +6,6 @@ version = 4 name = "account-abstraction-core" version = "0.1.0" dependencies = [ - "alloy-abi", - "alloy-dyn-abi", "alloy-primitives", "alloy-provider", "alloy-rpc-types", @@ -58,12 +56,6 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" -[[package]] -name = "alloy-abi" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95d80fbdae433a4c98109023eada56d864e49750d131824bad158f59d79a95d2" - [[package]] name = "alloy-chains" version = "0.2.18" @@ -118,20 +110,6 @@ dependencies = [ "serde", ] -[[package]] -name = "alloy-dyn-abi" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdff496dd4e98a81f4861e66f7eaf5f2488971848bb42d9c892f871730245c8" -dependencies = [ - "alloy-json-abi", - "alloy-primitives", - "alloy-sol-type-parser", - "alloy-sol-types", - "itoa", - "winnow", -] - [[package]] name = "alloy-eip2124" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index c97e74be..f05aed70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,9 +29,7 @@ alloy-consensus = { version = "1.0.41" } alloy-provider = { version = "1.0.41" } alloy-serde = "1.0.41" alloy-rpc-types = "1.1.2" -alloy-dyn-abi = { version = "1.2.0", default-features = false } alloy-sol-types = { version = "1.4.1", default-features = false } -alloy-abi = { version = "0.1.0", default-features = false } # op-alloy op-alloy-network = { version = "0.22.0", default-features = false } diff --git a/crates/account-abstraction-core/Cargo.toml b/crates/account-abstraction-core/Cargo.toml index c2ee01db..55277ca7 100644 --- a/crates/account-abstraction-core/Cargo.toml +++ b/crates/account-abstraction-core/Cargo.toml @@ -20,7 +20,6 @@ reth-rpc-eth-types.workspace = true tokio.workspace = true jsonrpsee.workspace = true async-trait = { workspace = true } -alloy-dyn-abi = {workspace = true } alloy-sol-types.workspace= true anyhow.workspace = true @@ -28,4 +27,3 @@ anyhow.workspace = true alloy-primitives.workspace = true serde_json.workspace = true wiremock.workspace = true -alloy-abi.workspace = true diff --git a/crates/account-abstraction-core/core/src/entrypoints/v06.rs b/crates/account-abstraction-core/core/src/entrypoints/v06.rs index 925a21ce..af694f97 100644 --- a/crates/account-abstraction-core/core/src/entrypoints/v06.rs +++ b/crates/account-abstraction-core/core/src/entrypoints/v06.rs @@ -66,8 +66,8 @@ mod tests { #[test] fn test_hash_zeroed() { let entry_point_address_v0_6 = address!("66a15edcc3b50a663e72f1457ffd49b9ae284ddc"); - let chainId = 1337; - let userOpWithZeroedInitCode = erc4337::UserOperation { + let chain_id = 1337; + let user_op_with_zeroed_init_code = erc4337::UserOperation { sender: address!("0x0000000000000000000000000000000000000000"), nonce: U256::ZERO, init_code: Bytes::default(), @@ -82,7 +82,7 @@ mod tests { }; let hash = - hash_user_operation(&userOpWithZeroedInitCode, entry_point_address_v0_6, chainId); + hash_user_operation(&user_op_with_zeroed_init_code, entry_point_address_v0_6, chain_id); assert_eq!( hash, b256!("dca97c3b49558ab360659f6ead939773be8bf26631e61bb17045bb70dc983b2d") @@ -92,8 +92,8 @@ mod tests { #[test] fn test_hash_non_zeroed() { let entry_point_address_v0_6 = address!("66a15edcc3b50a663e72f1457ffd49b9ae284ddc"); - let chainId = 1337; - let userOpWithNonZeroedInitCode = erc4337::UserOperation { + let chain_id = 1337; + let user_op_with_non_zeroed_init_code = erc4337::UserOperation { sender: address!("0x1306b01bc3e4ad202612d3843387e94737673f53"), nonce: U256::from(8942), init_code: "0x6942069420694206942069420694206942069420" @@ -114,9 +114,9 @@ mod tests { }; let hash = hash_user_operation( - &userOpWithNonZeroedInitCode, + &user_op_with_non_zeroed_init_code, entry_point_address_v0_6, - chainId, + chain_id, ); assert_eq!( hash, diff --git a/crates/account-abstraction-core/core/src/types.rs b/crates/account-abstraction-core/core/src/types.rs index 7d6d2731..695c3a05 100644 --- a/crates/account-abstraction-core/core/src/types.rs +++ b/crates/account-abstraction-core/core/src/types.rs @@ -56,7 +56,7 @@ mod tests { fn should_throw_error_when_deserializing_invalid() { const TEST_INVALID_USER_OPERATION: &str = r#" { - "type": "EntryPointV06", + "type": "UserOperation", "sender": "0x1111111111111111111111111111111111111111", "nonce": "0x0", "callGasLimit": "0x5208" @@ -71,7 +71,7 @@ mod tests { fn should_deserialize_v06() { const TEST_USER_OPERATION: &str = r#" { - "type": "EntryPointV06", + "type": "UserOperation", "sender": "0x1111111111111111111111111111111111111111", "nonce": "0x0", "initCode": "0x", @@ -124,7 +124,7 @@ mod tests { fn should_deserialize_v07() { const TEST_PACKED_USER_OPERATION: &str = r#" { - "type": "EntryPointV07", + "type": "PackedUserOperation", "sender": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "nonce": "0x1", "factory": "0x2222222222222222222222222222222222222222", From 1d07880ba227a803bbe2d9d9ba863a71eb524951 Mon Sep 17 00:00:00 2001 From: Rayyan Alam Date: Mon, 8 Dec 2025 09:18:39 -0500 Subject: [PATCH 17/24] chore: add comments for referencing --- .../core/src/entrypoints/v06.rs | 11 +++++++++++ .../core/src/entrypoints/v07.rs | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/crates/account-abstraction-core/core/src/entrypoints/v06.rs b/crates/account-abstraction-core/core/src/entrypoints/v06.rs index af694f97..8f62e97c 100644 --- a/crates/account-abstraction-core/core/src/entrypoints/v06.rs +++ b/crates/account-abstraction-core/core/src/entrypoints/v06.rs @@ -1,3 +1,14 @@ + + /* + * ERC-4337 v0.6 UserOperation Hash Calculation + * + * 1. Hash variable-length fields: initCode, callData, paymasterAndData + * 2. Pack all fields into struct (using hashes from step 1, gas values as uint256) + * 3. encodedHash = keccak256(abi.encode(packed struct)) + * 4. final hash = keccak256(abi.encode(encodedHash, entryPoint, chainId)) + * + * Reference: rundler/crates/types/src/user_operation/v0_6.rs:927-934 + */ use alloy_primitives::{ChainId, U256}; use alloy_rpc_types::erc4337; use alloy_sol_types::{SolValue, sol}; diff --git a/crates/account-abstraction-core/core/src/entrypoints/v07.rs b/crates/account-abstraction-core/core/src/entrypoints/v07.rs index 6b4c6cc4..67e1742c 100644 --- a/crates/account-abstraction-core/core/src/entrypoints/v07.rs +++ b/crates/account-abstraction-core/core/src/entrypoints/v07.rs @@ -1,3 +1,13 @@ + /* + * ERC-4337 v0.7 UserOperation Hash Calculation + * + * 1. Hash variable-length fields: initCode, callData, paymasterAndData + * 2. Pack all fields into struct (using hashes from step 1, gas values as bytes32) + * 3. encodedHash = keccak256(abi.encode(packed struct)) + * 4. final hash = keccak256(abi.encode(encodedHash, entryPoint, chainId)) + * + * Reference: rundler/crates/types/src/user_operation/v0_7.rs:1094-1123 + */ use alloy_primitives::{Address, ChainId, FixedBytes, U256}; use alloy_primitives::{Bytes, keccak256}; use alloy_rpc_types::erc4337; From 5c3ec28cfcf50d4547fb72d6d690085276ce4ed4 Mon Sep 17 00:00:00 2001 From: Rayyan Alam Date: Mon, 8 Dec 2025 09:20:01 -0500 Subject: [PATCH 18/24] chore: fmt --- .../core/src/entrypoints/v06.rs | 28 ++++++++++--------- .../core/src/entrypoints/v07.rs | 20 ++++++------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/crates/account-abstraction-core/core/src/entrypoints/v06.rs b/crates/account-abstraction-core/core/src/entrypoints/v06.rs index 8f62e97c..4883305d 100644 --- a/crates/account-abstraction-core/core/src/entrypoints/v06.rs +++ b/crates/account-abstraction-core/core/src/entrypoints/v06.rs @@ -1,14 +1,13 @@ - - /* - * ERC-4337 v0.6 UserOperation Hash Calculation - * - * 1. Hash variable-length fields: initCode, callData, paymasterAndData - * 2. Pack all fields into struct (using hashes from step 1, gas values as uint256) - * 3. encodedHash = keccak256(abi.encode(packed struct)) - * 4. final hash = keccak256(abi.encode(encodedHash, entryPoint, chainId)) - * - * Reference: rundler/crates/types/src/user_operation/v0_6.rs:927-934 - */ +/* + * ERC-4337 v0.6 UserOperation Hash Calculation + * + * 1. Hash variable-length fields: initCode, callData, paymasterAndData + * 2. Pack all fields into struct (using hashes from step 1, gas values as uint256) + * 3. encodedHash = keccak256(abi.encode(packed struct)) + * 4. final hash = keccak256(abi.encode(encodedHash, entryPoint, chainId)) + * + * Reference: rundler/crates/types/src/user_operation/v0_6.rs:927-934 + */ use alloy_primitives::{ChainId, U256}; use alloy_rpc_types::erc4337; use alloy_sol_types::{SolValue, sol}; @@ -92,8 +91,11 @@ mod tests { signature: Bytes::default(), }; - let hash = - hash_user_operation(&user_op_with_zeroed_init_code, entry_point_address_v0_6, chain_id); + let hash = hash_user_operation( + &user_op_with_zeroed_init_code, + entry_point_address_v0_6, + chain_id, + ); assert_eq!( hash, b256!("dca97c3b49558ab360659f6ead939773be8bf26631e61bb17045bb70dc983b2d") diff --git a/crates/account-abstraction-core/core/src/entrypoints/v07.rs b/crates/account-abstraction-core/core/src/entrypoints/v07.rs index 67e1742c..d5d475f0 100644 --- a/crates/account-abstraction-core/core/src/entrypoints/v07.rs +++ b/crates/account-abstraction-core/core/src/entrypoints/v07.rs @@ -1,13 +1,13 @@ - /* - * ERC-4337 v0.7 UserOperation Hash Calculation - * - * 1. Hash variable-length fields: initCode, callData, paymasterAndData - * 2. Pack all fields into struct (using hashes from step 1, gas values as bytes32) - * 3. encodedHash = keccak256(abi.encode(packed struct)) - * 4. final hash = keccak256(abi.encode(encodedHash, entryPoint, chainId)) - * - * Reference: rundler/crates/types/src/user_operation/v0_7.rs:1094-1123 - */ +/* + * ERC-4337 v0.7 UserOperation Hash Calculation + * + * 1. Hash variable-length fields: initCode, callData, paymasterAndData + * 2. Pack all fields into struct (using hashes from step 1, gas values as bytes32) + * 3. encodedHash = keccak256(abi.encode(packed struct)) + * 4. final hash = keccak256(abi.encode(encodedHash, entryPoint, chainId)) + * + * Reference: rundler/crates/types/src/user_operation/v0_7.rs:1094-1123 + */ use alloy_primitives::{Address, ChainId, FixedBytes, U256}; use alloy_primitives::{Bytes, keccak256}; use alloy_rpc_types::erc4337; From fbbbc4f52cf09ed7432d3a8d66157a323829770e Mon Sep 17 00:00:00 2001 From: Rayyan Alam Date: Mon, 8 Dec 2025 09:30:17 -0500 Subject: [PATCH 19/24] chore: fix tests --- crates/account-abstraction-core/core/src/types.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/account-abstraction-core/core/src/types.rs b/crates/account-abstraction-core/core/src/types.rs index 695c3a05..6cc90f04 100644 --- a/crates/account-abstraction-core/core/src/types.rs +++ b/crates/account-abstraction-core/core/src/types.rs @@ -41,7 +41,6 @@ impl UserOperationRequest { #[serde(rename_all = "camelCase")] pub struct UserOperationRequestValidationResult { pub expiration_timestamp: u64, - pub hash: B256, pub gas_used: U256, } From bbd2a241d809c55f9848d721d00635643fab6a5f Mon Sep 17 00:00:00 2001 From: Rayyan Alam Date: Mon, 8 Dec 2025 09:52:45 -0500 Subject: [PATCH 20/24] chore: add back in tests --- .../core/src/entrypoints/v07.rs | 5 +-- crates/ingress-rpc/src/queue.rs | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/crates/account-abstraction-core/core/src/entrypoints/v07.rs b/crates/account-abstraction-core/core/src/entrypoints/v07.rs index d5d475f0..60d8fd46 100644 --- a/crates/account-abstraction-core/core/src/entrypoints/v07.rs +++ b/crates/account-abstraction-core/core/src/entrypoints/v07.rs @@ -28,9 +28,6 @@ sol!( bytes signature; } -); - -sol! { #[derive(Default, Debug, PartialEq, Eq)] struct UserOperationHashEncoded { bytes32 encodedHash; @@ -49,7 +46,7 @@ sol! { bytes32 gasFees; bytes32 hashPaymasterAndData; } -} +); impl From for PackedUserOperation { fn from(uo: erc4337::PackedUserOperation) -> Self { diff --git a/crates/ingress-rpc/src/queue.rs b/crates/ingress-rpc/src/queue.rs index d8028ac2..29cf653d 100644 --- a/crates/ingress-rpc/src/queue.rs +++ b/crates/ingress-rpc/src/queue.rs @@ -99,3 +99,44 @@ impl BundleQueuePublisher { self.queue.publish_raw(&self.topic, &key, &payload).await } } + + +#[cfg(test)] +mod tests { + use super::*; + use rdkafka::config::ClientConfig; + use tips_core::{ + AcceptedBundle, Bundle, BundleExtensions, test_utils::create_test_meter_bundle_response, + }; + use tokio::time::{Duration, Instant}; + + fn create_test_bundle() -> Bundle { + Bundle::default() + } + + #[tokio::test] + async fn test_backoff_retry_logic() { + // use an invalid broker address to trigger the backoff logic + let producer = ClientConfig::new() + .set("bootstrap.servers", "localhost:9999") + .set("message.timeout.ms", "100") + .create() + .expect("Producer creation failed"); + + let publisher = KafkaMessageQueue::new(producer); + let bundle = create_test_bundle(); + let accepted_bundle = AcceptedBundle::new( + bundle.try_into().unwrap(), + create_test_meter_bundle_response(), + ); + let bundle_hash = &accepted_bundle.bundle_hash(); + + let start = Instant::now(); + let result = publisher.publish_raw("tips-ingress-rpc", bundle_hash.to_string().as_str(), &serde_json::to_vec(&accepted_bundle).unwrap()).await; + let elapsed = start.elapsed(); + + // the backoff tries at minimum 100ms, so verify we tried at least once + assert!(result.is_err()); + assert!(elapsed >= Duration::from_millis(100)); + } +} \ No newline at end of file From 549d5ffa5e36bdc2a6798104c8df1fa4979924a1 Mon Sep 17 00:00:00 2001 From: Rayyan Alam Date: Mon, 8 Dec 2025 09:54:06 -0500 Subject: [PATCH 21/24] chore: fmt --- crates/ingress-rpc/src/queue.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/ingress-rpc/src/queue.rs b/crates/ingress-rpc/src/queue.rs index 29cf653d..32375476 100644 --- a/crates/ingress-rpc/src/queue.rs +++ b/crates/ingress-rpc/src/queue.rs @@ -100,7 +100,6 @@ impl BundleQueuePublisher { } } - #[cfg(test)] mod tests { use super::*; @@ -132,11 +131,17 @@ mod tests { let bundle_hash = &accepted_bundle.bundle_hash(); let start = Instant::now(); - let result = publisher.publish_raw("tips-ingress-rpc", bundle_hash.to_string().as_str(), &serde_json::to_vec(&accepted_bundle).unwrap()).await; + let result = publisher + .publish_raw( + "tips-ingress-rpc", + bundle_hash.to_string().as_str(), + &serde_json::to_vec(&accepted_bundle).unwrap(), + ) + .await; let elapsed = start.elapsed(); // the backoff tries at minimum 100ms, so verify we tried at least once assert!(result.is_err()); assert!(elapsed >= Duration::from_millis(100)); } -} \ No newline at end of file +} From 9c834635d9797be2e0df386fdd230241244c2422 Mon Sep 17 00:00:00 2001 From: Rayyan Alam Date: Wed, 10 Dec 2025 15:16:46 -0500 Subject: [PATCH 22/24] chore: intitate --- Cargo.lock | 923 ++++++++++++++++-- crates/account-abstraction-core/Cargo.toml | 9 + .../account-abstraction-core/core/src/lib.rs | 2 + .../core/src/mempool.rs | 171 ++++ 4 files changed, 999 insertions(+), 106 deletions(-) create mode 100644 crates/account-abstraction-core/core/src/mempool.rs diff --git a/Cargo.lock b/Cargo.lock index 6faa5290..fd62073e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,9 +16,17 @@ dependencies = [ "jsonrpsee", "op-alloy-network", "reth-rpc-eth-types", + "rundler-pool", + "rundler-provider", + "rundler-sim", + "rundler-task", + "rundler-types", + "rundler-utils", "serde", "serde_json", "tokio", + "tracing", + "tracing-subscriber 0.3.20", "wiremock", ] @@ -66,7 +74,7 @@ dependencies = [ "alloy-rlp", "num_enum", "serde", - "strum", + "strum 0.27.2", ] [[package]] @@ -84,7 +92,7 @@ dependencies = [ "auto_impl", "borsh", "c-kzg", - "derive_more", + "derive_more 2.0.1", "either", "k256", "once_cell", @@ -93,7 +101,7 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -110,6 +118,44 @@ dependencies = [ "serde", ] +[[package]] +name = "alloy-contract" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5903097e4c131ad2dd80d87065f23c715ccb9cdb905fa169dffab8e1e798bae" +dependencies = [ + "alloy-consensus", + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-network", + "alloy-network-primitives", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types-eth", + "alloy-sol-types", + "alloy-transport", + "futures", + "futures-util", + "serde_json", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-dyn-abi" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdff496dd4e98a81f4861e66f7eaf5f2488971848bb42d9c892f871730245c8" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-type-parser", + "alloy-sol-types", + "itoa", + "serde", + "serde_json", + "winnow", +] + [[package]] name = "alloy-eip2124" version = "0.2.0" @@ -120,7 +166,7 @@ dependencies = [ "alloy-rlp", "crc", "serde", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -146,7 +192,7 @@ dependencies = [ "borsh", "k256", "serde", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -164,14 +210,14 @@ dependencies = [ "auto_impl", "borsh", "c-kzg", - "derive_more", + "derive_more 2.0.1", "either", "ethereum_ssz", "ethereum_ssz_derive", "serde", "serde_with", "sha2", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -189,12 +235,12 @@ dependencies = [ "alloy-rpc-types-eth", "alloy-sol-types", "auto_impl", - "derive_more", + "derive_more 2.0.1", "op-alloy-consensus", "op-alloy-rpc-types-engine", "op-revm", "revm", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -247,7 +293,7 @@ dependencies = [ "http 1.3.1", "serde", "serde_json", - "thiserror", + "thiserror 2.0.17", "tracing", ] @@ -270,11 +316,11 @@ dependencies = [ "alloy-sol-types", "async-trait", "auto_impl", - "derive_more", + "derive_more 2.0.1", "futures-utils-wasm", "serde", "serde_json", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -305,7 +351,7 @@ dependencies = [ "op-alloy-consensus", "op-revm", "revm", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -330,7 +376,7 @@ dependencies = [ "bytes", "cfg-if", "const-hex", - "derive_more", + "derive_more 2.0.1", "foldhash 0.2.0", "getrandom 0.3.4", "hashbrown 0.16.0", @@ -342,7 +388,7 @@ dependencies = [ "proptest", "rand 0.9.2", "ruint", - "rustc-hash", + "rustc-hash 2.1.1", "serde", "sha3", "tiny-keccak", @@ -362,7 +408,9 @@ dependencies = [ "alloy-network-primitives", "alloy-primitives", "alloy-rpc-client", + "alloy-rpc-types-debug", "alloy-rpc-types-eth", + "alloy-rpc-types-trace", "alloy-signer", "alloy-sol-types", "alloy-transport", @@ -380,7 +428,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "thiserror", + "thiserror 2.0.17", "tokio", "tracing", "url", @@ -426,7 +474,7 @@ dependencies = [ "serde_json", "tokio", "tokio-stream", - "tower", + "tower 0.5.2", "tracing", "url", "wasmtimer", @@ -468,6 +516,18 @@ dependencies = [ "alloy-serde", ] +[[package]] +name = "alloy-rpc-types-debug" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4936f579d9d10eae01772b2ab3497f9d568684f05f26f8175e12f9a1a2babc33" +dependencies = [ + "alloy-primitives", + "derive_more 2.0.1", + "serde", + "serde_with", +] + [[package]] name = "alloy-rpc-types-engine" version = "1.1.2" @@ -479,13 +539,13 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "alloy-serde", - "derive_more", + "derive_more 2.0.1", "ethereum_ssz", "ethereum_ssz_derive", "jsonwebtoken", "rand 0.8.5", "serde", - "strum", + "strum 0.27.2", ] [[package]] @@ -506,7 +566,7 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -520,7 +580,7 @@ dependencies = [ "alloy-serde", "serde", "serde_json", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -546,7 +606,7 @@ dependencies = [ "either", "elliptic-curve 0.13.8", "k256", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -562,7 +622,7 @@ dependencies = [ "async-trait", "k256", "rand 0.8.5", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -585,6 +645,7 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d792e205ed3b72f795a8044c52877d2e6b6e9b1d13f431478121d8d4eaa9028" dependencies = [ + "alloy-json-abi", "alloy-sol-macro-input", "const-hex", "heck", @@ -603,12 +664,14 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bd1247a8f90b465ef3f1207627547ec16940c35597875cdc09c49d58b19693c" dependencies = [ + "alloy-json-abi", "const-hex", "dunce", "heck", "macro-string", "proc-macro2", "quote", + "serde_json", "syn 2.0.110", "syn-solidity", ] @@ -644,15 +707,15 @@ dependencies = [ "alloy-json-rpc", "auto_impl", "base64 0.22.1", - "derive_more", + "derive_more 2.0.1", "futures", "futures-utils-wasm", "parking_lot", "serde", "serde_json", - "thiserror", + "thiserror 2.0.17", "tokio", - "tower", + "tower 0.5.2", "tracing", "url", "wasmtimer", @@ -668,7 +731,7 @@ dependencies = [ "alloy-transport", "reqwest", "serde_json", - "tower", + "tower 0.5.2", "tracing", "url", ] @@ -682,7 +745,7 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "arrayvec", - "derive_more", + "derive_more 2.0.1", "nybbles", "serde", "smallvec", @@ -1095,6 +1158,17 @@ dependencies = [ "serde_json", ] +[[package]] +name = "async-lock" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -1219,7 +1293,7 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1826f2e4cfc2cd19ee53c42fbf68e2f81ec21108e0b7ecf6a71cf062137360fc" dependencies = [ - "bindgen", + "bindgen 0.72.1", "cc", "cmake", "dunce", @@ -1470,7 +1544,7 @@ dependencies = [ "rustls-pki-types", "tokio", "tokio-rustls 0.26.4", - "tower", + "tower 0.5.2", "tracing", ] @@ -1592,13 +1666,40 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core 0.4.5", + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "itoa", + "matchit 0.7.3", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower 0.5.2", + "tower-layer", + "tower-service", +] + [[package]] name = "axum" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" dependencies = [ - "axum-core", + "axum-core 0.5.5", "bytes", "form_urlencoded", "futures-util", @@ -1608,7 +1709,7 @@ dependencies = [ "hyper 1.8.0", "hyper-util", "itoa", - "matchit", + "matchit 0.8.4", "memchr", "mime", "percent-encoding", @@ -1619,12 +1720,32 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", - "tower", + "tower 0.5.2", "tower-layer", "tower-service", "tracing", ] +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", +] + [[package]] name = "axum-core" version = "0.5.5" @@ -1701,6 +1822,26 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +[[package]] +name = "bindgen" +version = "0.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +dependencies = [ + "bitflags 2.10.0", + "cexpr", + "clang-sys", + "itertools 0.10.5", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.110", +] + [[package]] name = "bindgen" version = "0.72.1" @@ -1716,7 +1857,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 2.1.1", "shlex", "syn 2.0.110", ] @@ -1832,7 +1973,7 @@ dependencies = [ "serde_json", "serde_repr", "serde_urlencoded", - "thiserror", + "thiserror 2.0.17", "tokio", "tokio-util", "tower-service", @@ -1966,8 +2107,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-link", ] @@ -2037,6 +2180,15 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "const-hex" version = "1.17.0" @@ -2075,6 +2227,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "convert_case" version = "0.7.1" @@ -2162,6 +2320,15 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -2382,6 +2549,19 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "syn 2.0.110", +] + [[package]] name = "derive_more" version = "2.0.1" @@ -2397,7 +2577,7 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ - "convert_case", + "convert_case 0.7.1", "proc-macro2", "quote", "syn 2.0.110", @@ -2657,6 +2837,27 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -2735,6 +2936,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "fnv" version = "1.0.7" @@ -3255,6 +3462,20 @@ dependencies = [ "tokio", "tokio-rustls 0.26.4", "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper 1.8.0", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", ] [[package]] @@ -3611,12 +3832,12 @@ dependencies = [ "parking_lot", "pin-project", "rand 0.9.2", - "rustc-hash", + "rustc-hash 2.1.1", "serde", "serde_json", - "thiserror", + "thiserror 2.0.17", "tokio", - "tower", + "tower 0.5.2", "tracing", ] @@ -3652,11 +3873,11 @@ dependencies = [ "serde", "serde_json", "soketto", - "thiserror", + "thiserror 2.0.17", "tokio", "tokio-stream", "tokio-util", - "tower", + "tower 0.5.2", "tracing", ] @@ -3669,7 +3890,7 @@ dependencies = [ "http 1.3.1", "serde", "serde_json", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -3842,6 +4063,12 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "matchit" version = "0.8.4" @@ -3902,7 +4129,7 @@ dependencies = [ "metrics", "metrics-util", "quanta", - "thiserror", + "thiserror 2.0.17", "tokio", "tracing", ] @@ -3976,6 +4203,33 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "moka" +version = "0.12.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8261cd88c312e0004c1d51baad2980c66528dfdb2bee62003e643a4d8f86b077" +dependencies = [ + "async-lock", + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "equivalent", + "event-listener", + "futures-util", + "parking_lot", + "portable-atomic", + "rustc_version 0.4.1", + "smallvec", + "tagptr", + "uuid", +] + +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + [[package]] name = "native-tls" version = "0.2.14" @@ -4167,9 +4421,9 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types-eth", "alloy-serde", - "derive_more", + "derive_more 2.0.1", "serde", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -4206,11 +4460,11 @@ dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", "alloy-serde", - "derive_more", + "derive_more 2.0.1", "op-alloy-consensus", "serde", "serde_json", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -4224,12 +4478,12 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "alloy-rpc-types-engine", - "derive_more", + "derive_more 2.0.1", "ethereum_ssz", "ethereum_ssz_derive", "op-alloy-consensus", "snap", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -4354,6 +4608,12 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.5" @@ -4383,7 +4643,18 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "914a1c2265c98e2446911282c6ac86d8524f495792c38c5bd884f80499c7538a" dependencies = [ - "parse-display-derive", + "parse-display-derive 0.9.1", + "regex", + "regex-syntax", +] + +[[package]] +name = "parse-display" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287d8d3ebdce117b8539f59411e4ed9ec226e0a4153c7f55495c6070d68e6f72" +dependencies = [ + "parse-display-derive 0.10.0", "regex", "regex-syntax", ] @@ -4402,6 +4673,20 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "parse-display-derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc048687be30d79502dea2f623d052f3a074012c6eac41726b7ab17213616b1" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "regex-syntax", + "structmeta", + "syn 2.0.110", +] + [[package]] name = "paste" version = "1.0.15" @@ -4434,6 +4719,16 @@ dependencies = [ "ucd-trie", ] +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap 2.12.0", +] + [[package]] name = "phf" version = "0.13.1" @@ -4654,6 +4949,58 @@ dependencies = [ "unarray", ] +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +dependencies = [ + "heck", + "itertools 0.10.5", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 2.0.110", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + [[package]] name = "quanta" version = "0.12.6" @@ -4686,10 +5033,10 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash", + "rustc-hash 2.1.1", "rustls 0.23.35", "socket2 0.6.1", - "thiserror", + "thiserror 2.0.17", "tokio", "tracing", "web-time", @@ -4706,11 +5053,11 @@ dependencies = [ "lru-slab", "rand 0.9.2", "ring", - "rustc-hash", + "rustc-hash 2.1.1", "rustls 0.23.35", "rustls-pki-types", "slab", - "thiserror", + "thiserror 2.0.17", "tinyvec", "tracing", "web-time", @@ -4997,13 +5344,14 @@ dependencies = [ "tokio", "tokio-native-tls", "tokio-rustls 0.26.4", - "tower", + "tower 0.5.2", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots", ] [[package]] @@ -5014,7 +5362,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-primitives", - "derive_more", + "derive_more 2.0.1", "metrics", "parking_lot", "pin-project", @@ -5022,7 +5370,7 @@ dependencies = [ "reth-errors", "reth-ethereum-primitives", "reth-execution-types", - "reth-metrics", + "reth-metrics 1.9.3", "reth-primitives-traits", "reth-storage-api", "reth-trie", @@ -5045,7 +5393,7 @@ dependencies = [ "alloy-primitives", "alloy-trie", "auto_impl", - "derive_more", + "derive_more 2.0.1", "reth-ethereum-forks", "reth-network-peers", "reth-primitives-traits", @@ -5090,7 +5438,7 @@ dependencies = [ "auto_impl", "reth-execution-types", "reth-primitives-traits", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -5125,7 +5473,7 @@ dependencies = [ "reth-consensus", "reth-execution-errors", "reth-storage-errors", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -5140,13 +5488,13 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "bytes", - "derive_more", + "derive_more 2.0.1", "reth-chainspec", "reth-codecs-derive", "reth-ethereum-primitives", "reth-primitives-traits", "serde", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -5159,7 +5507,7 @@ dependencies = [ "alloy-primitives", "auto_impl", "once_cell", - "rustc-hash", + "rustc-hash 2.1.1", ] [[package]] @@ -5189,7 +5537,7 @@ dependencies = [ "alloy-evm", "alloy-primitives", "auto_impl", - "derive_more", + "derive_more 2.0.1", "futures-util", "reth-execution-errors", "reth-execution-types", @@ -5210,7 +5558,7 @@ dependencies = [ "alloy-rlp", "nybbles", "reth-storage-errors", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -5222,7 +5570,7 @@ dependencies = [ "alloy-eips", "alloy-evm", "alloy-primitives", - "derive_more", + "derive_more 2.0.1", "reth-ethereum-primitives", "reth-primitives-traits", "reth-trie-common", @@ -5238,7 +5586,16 @@ source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea8 dependencies = [ "serde", "serde_json", - "thiserror", + "thiserror 2.0.17", +] + +[[package]] +name = "reth-metrics" +version = "1.4.7" +source = "git+https://github.com/paradigmxyz/reth.git?tag=v1.4.7#dc7cb6e6670b0da294a0e5010e02855f5aaf6b49" +dependencies = [ + "metrics", + "metrics-derive", ] [[package]] @@ -5268,7 +5625,7 @@ dependencies = [ "alloy-rpc-types-admin", "alloy-rpc-types-eth", "auto_impl", - "derive_more", + "derive_more 2.0.1", "enr", "futures", "reth-eth-wire-types", @@ -5277,7 +5634,7 @@ dependencies = [ "reth-network-peers", "reth-network-types", "reth-tokio-util", - "thiserror", + "thiserror 2.0.17", "tokio", "tokio-stream", ] @@ -5291,7 +5648,7 @@ dependencies = [ "alloy-eips", "alloy-primitives", "auto_impl", - "derive_more", + "derive_more 2.0.1", "futures", "reth-consensus", "reth-eth-wire-types", @@ -5313,7 +5670,7 @@ dependencies = [ "alloy-rlp", "secp256k1 0.30.0", "serde_with", - "thiserror", + "thiserror 2.0.17", "url", ] @@ -5340,7 +5697,7 @@ dependencies = [ "alloy-genesis", "alloy-hardforks", "alloy-primitives", - "derive_more", + "derive_more 2.0.1", "miniz_oxide", "op-alloy-consensus", "op-alloy-rpc-types", @@ -5352,7 +5709,7 @@ dependencies = [ "reth-primitives-traits", "serde", "serde_json", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -5376,7 +5733,7 @@ dependencies = [ "reth-storage-errors", "reth-trie-common", "revm", - "thiserror", + "thiserror 2.0.17", "tracing", ] @@ -5404,7 +5761,7 @@ dependencies = [ "reth-primitives-traits", "reth-storage-errors", "revm", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -5450,7 +5807,7 @@ dependencies = [ "alloy-trie", "auto_impl", "bytes", - "derive_more", + "derive_more 2.0.1", "once_cell", "op-alloy-consensus", "reth-codecs", @@ -5460,7 +5817,7 @@ dependencies = [ "secp256k1 0.30.0", "serde", "serde_with", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -5469,10 +5826,10 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-primitives", - "derive_more", + "derive_more 2.0.1", "serde", - "strum", - "thiserror", + "strum 0.27.2", + "thiserror 2.0.17", ] [[package]] @@ -5505,7 +5862,7 @@ dependencies = [ "reth-evm", "reth-primitives-traits", "revm-context", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -5522,7 +5879,7 @@ dependencies = [ "alloy-rpc-types-eth", "alloy-sol-types", "alloy-transport", - "derive_more", + "derive_more 2.0.1", "futures", "itertools 0.14.0", "jsonrpsee-core", @@ -5536,20 +5893,20 @@ dependencies = [ "reth-ethereum-primitives", "reth-evm", "reth-execution-types", - "reth-metrics", + "reth-metrics 1.9.3", "reth-primitives-traits", "reth-revm", "reth-rpc-convert", "reth-rpc-server-types", "reth-storage-api", - "reth-tasks", + "reth-tasks 1.9.3", "reth-transaction-pool", "reth-trie", "revm", "revm-inspectors", "schnellru", "serde", - "thiserror", + "thiserror 2.0.17", "tokio", "tokio-stream", "tracing", @@ -5568,7 +5925,7 @@ dependencies = [ "reth-errors", "reth-network-api", "serde", - "strum", + "strum 0.27.2", ] [[package]] @@ -5588,9 +5945,9 @@ version = "1.9.3" source = "git+https://github.com/paradigmxyz/reth?tag=v1.9.3#27a8c0f5a6dfb27dea84c5751776ecabdd069646" dependencies = [ "alloy-primitives", - "derive_more", + "derive_more 2.0.1", "serde", - "strum", + "strum 0.27.2", ] [[package]] @@ -5623,12 +5980,30 @@ dependencies = [ "alloy-eips", "alloy-primitives", "alloy-rlp", - "derive_more", + "derive_more 2.0.1", "reth-primitives-traits", "reth-prune-types", "reth-static-file-types", "revm-database-interface", - "thiserror", + "thiserror 2.0.17", +] + +[[package]] +name = "reth-tasks" +version = "1.4.7" +source = "git+https://github.com/paradigmxyz/reth.git?tag=v1.4.7#dc7cb6e6670b0da294a0e5010e02855f5aaf6b49" +dependencies = [ + "auto_impl", + "dyn-clone", + "futures-util", + "metrics", + "pin-project", + "rayon", + "reth-metrics 1.4.7", + "thiserror 2.0.17", + "tokio", + "tracing", + "tracing-futures", ] [[package]] @@ -5640,8 +6015,8 @@ dependencies = [ "dyn-clone", "futures-util", "metrics", - "reth-metrics", - "thiserror", + "reth-metrics 1.9.3", + "thiserror 2.0.17", "tokio", "tracing", "tracing-futures", @@ -5679,18 +6054,18 @@ dependencies = [ "reth-ethereum-primitives", "reth-execution-types", "reth-fs-util", - "reth-metrics", + "reth-metrics 1.9.3", "reth-primitives-traits", "reth-storage-api", - "reth-tasks", + "reth-tasks 1.9.3", "revm-interpreter", "revm-primitives", - "rustc-hash", + "rustc-hash 2.1.1", "schnellru", "serde", "serde_json", "smallvec", - "thiserror", + "thiserror 2.0.17", "tokio", "tokio-stream", "tracing", @@ -5731,7 +6106,7 @@ dependencies = [ "alloy-trie", "arrayvec", "bytes", - "derive_more", + "derive_more 2.0.1", "itertools 0.14.0", "nybbles", "rayon", @@ -5908,7 +6283,7 @@ dependencies = [ "revm", "serde", "serde_json", - "thiserror", + "thiserror 2.0.17", ] [[package]] @@ -6078,6 +6453,195 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" +[[package]] +name = "rundler-bindings-fastlz" +version = "0.10.0" +source = "git+https://github.com/rayyan224/rundler#ca4ad813c11d321c625fec1bb588ae9fb28807cf" +dependencies = [ + "bindgen 0.70.1", + "cc", +] + +[[package]] +name = "rundler-contracts" +version = "0.10.0" +source = "git+https://github.com/rayyan224/rundler#ca4ad813c11d321c625fec1bb588ae9fb28807cf" +dependencies = [ + "alloy-contract", + "alloy-primitives", + "alloy-sol-macro", + "alloy-sol-types", + "const-hex", + "serde_json", +] + +[[package]] +name = "rundler-pool" +version = "0.10.0" +source = "git+https://github.com/rayyan224/rundler#ca4ad813c11d321c625fec1bb588ae9fb28807cf" +dependencies = [ + "alloy-network-primitives", + "alloy-primitives", + "alloy-sol-types", + "anyhow", + "async-stream", + "async-trait", + "futures", + "futures-util", + "itertools 0.13.0", + "metrics", + "metrics-derive", + "parking_lot", + "prost", + "rundler-contracts", + "rundler-provider", + "rundler-sim", + "rundler-task", + "rundler-types", + "rundler-utils", + "tokio", + "tokio-stream", + "tonic", + "tonic-build", + "tonic-health", + "tonic-reflection", + "tracing", +] + +[[package]] +name = "rundler-provider" +version = "0.10.0" +source = "git+https://github.com/rayyan224/rundler#ca4ad813c11d321c625fec1bb588ae9fb28807cf" +dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-eips", + "alloy-json-rpc", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rlp", + "alloy-rpc-client", + "alloy-rpc-types-any", + "alloy-rpc-types-eth", + "alloy-rpc-types-trace", + "alloy-serde", + "alloy-sol-types", + "alloy-transport", + "anyhow", + "async-trait", + "auto_impl", + "futures-util", + "metrics", + "metrics-derive", + "moka", + "pin-project", + "rand 0.8.5", + "regex", + "reth-tasks 1.4.7", + "rundler-bindings-fastlz", + "rundler-contracts", + "rundler-types", + "rundler-utils", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tower 0.4.13", + "tracing", + "url", +] + +[[package]] +name = "rundler-sim" +version = "0.10.0" +source = "git+https://github.com/rayyan224/rundler#ca4ad813c11d321c625fec1bb588ae9fb28807cf" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "anyhow", + "arrayvec", + "async-trait", + "futures-util", + "metrics", + "metrics-derive", + "rundler-contracts", + "rundler-provider", + "rundler-types", + "rundler-utils", + "serde", + "serde_with", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "rundler-task" +version = "0.10.0" +source = "git+https://github.com/rayyan224/rundler#ca4ad813c11d321c625fec1bb588ae9fb28807cf" +dependencies = [ + "alloy-primitives", + "anyhow", + "async-trait", + "pin-project", + "reth-tasks 1.4.7", + "rundler-provider", + "rundler-types", + "rundler-utils", + "thiserror 1.0.69", + "tokio", + "tonic", + "tower 0.4.13", + "tracing", +] + +[[package]] +name = "rundler-types" +version = "0.10.0" +source = "git+https://github.com/rayyan224/rundler#ca4ad813c11d321c625fec1bb588ae9fb28807cf" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-sol-types", + "anyhow", + "async-trait", + "auto_impl", + "chrono", + "futures-util", + "metrics", + "metrics-derive", + "num_enum", + "parse-display 0.10.0", + "rundler-contracts", + "rundler-utils", + "serde", + "strum 0.26.3", + "thiserror 1.0.69", +] + +[[package]] +name = "rundler-utils" +version = "0.10.0" +source = "git+https://github.com/rayyan224/rundler#ca4ad813c11d321c625fec1bb588ae9fb28807cf" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-eth", + "anyhow", + "derive_more 0.99.20", + "itertools 0.13.0", + "metrics", + "rand 0.8.5", + "schnellru", + "tokio", + "tracing", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hash" version = "2.1.1" @@ -6652,7 +7216,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", - "thiserror", + "thiserror 2.0.17", "time", ] @@ -6786,13 +7350,35 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros 0.26.4", +] + [[package]] name = "strum" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros", + "strum_macros 0.27.2", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.110", ] [[package]] @@ -6867,6 +7453,12 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + [[package]] name = "tap" version = "1.0.1" @@ -6902,12 +7494,12 @@ dependencies = [ "futures", "log", "memchr", - "parse-display", + "parse-display 0.9.1", "pin-project-lite", "serde", "serde_json", "serde_with", - "thiserror", + "thiserror 2.0.17", "tokio", "tokio-stream", "tokio-tar", @@ -6924,13 +7516,33 @@ dependencies = [ "testcontainers", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", ] [[package]] @@ -7086,7 +7698,7 @@ dependencies = [ "alloy-signer-local", "anyhow", "async-trait", - "axum", + "axum 0.8.7", "backon", "clap", "dotenvy", @@ -7240,6 +7852,96 @@ dependencies = [ "winnow", ] +[[package]] +name = "tonic" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +dependencies = [ + "async-stream", + "async-trait", + "axum 0.7.9", + "base64 0.22.1", + "bytes", + "h2 0.4.12", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.0", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "socket2 0.5.10", + "tokio", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "prost-types", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "tonic-health" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1eaf34ddb812120f5c601162d5429933c9b527d901ab0e7f930d3147e33a09b2" +dependencies = [ + "async-stream", + "prost", + "tokio", + "tokio-stream", + "tonic", +] + +[[package]] +name = "tonic-reflection" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "878d81f52e7fcfd80026b7fdb6a9b578b3c3653ba987f87f0dce4b64043cba27" +dependencies = [ + "prost", + "prost-types", + "tokio", + "tokio-stream", + "tonic", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand 0.8.5", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower" version = "0.5.2" @@ -7269,7 +7971,7 @@ dependencies = [ "http-body 1.0.1", "iri-string", "pin-project-lite", - "tower", + "tower 0.5.2", "tower-layer", "tower-service", ] @@ -7631,6 +8333,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "winapi" version = "0.3.9" @@ -8144,7 +8855,7 @@ version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ - "bindgen", + "bindgen 0.72.1", "cc", "pkg-config", ] diff --git a/crates/account-abstraction-core/Cargo.toml b/crates/account-abstraction-core/Cargo.toml index 55277ca7..e729ebd0 100644 --- a/crates/account-abstraction-core/Cargo.toml +++ b/crates/account-abstraction-core/Cargo.toml @@ -22,6 +22,15 @@ jsonrpsee.workspace = true async-trait = { workspace = true } alloy-sol-types.workspace= true anyhow.workspace = true +rundler-types = { git = "https://github.com/rayyan224/rundler", package = "rundler-types" } +rundler-pool = { git = "https://github.com/rayyan224/rundler", package = "rundler-pool" } +rundler-sim = { git = "https://github.com/rayyan224/rundler", package = "rundler-sim" } +rundler-provider = { git = "https://github.com/rayyan224/rundler", package = "rundler-provider" } +rundler-task = { git = "https://github.com/rayyan224/rundler", package = "rundler-task" } +rundler-utils = { git = "https://github.com/rayyan224/rundler", package = "rundler-utils" } +tracing.workspace = true +tracing-subscriber.workspace = true + [dev-dependencies] alloy-primitives.workspace = true diff --git a/crates/account-abstraction-core/core/src/lib.rs b/crates/account-abstraction-core/core/src/lib.rs index fe08aa70..7444b43e 100644 --- a/crates/account-abstraction-core/core/src/lib.rs +++ b/crates/account-abstraction-core/core/src/lib.rs @@ -1,5 +1,7 @@ pub mod account_abstraction_service; pub mod entrypoints; +pub mod mempool; pub mod types; pub use account_abstraction_service::{AccountAbstractionService, AccountAbstractionServiceImpl}; +pub use mempool::SimpleMempool; pub use types::{SendUserOperationResponse, VersionedUserOperation}; diff --git a/crates/account-abstraction-core/core/src/mempool.rs b/crates/account-abstraction-core/core/src/mempool.rs new file mode 100644 index 00000000..5f483d10 --- /dev/null +++ b/crates/account-abstraction-core/core/src/mempool.rs @@ -0,0 +1,171 @@ +use alloy_primitives::{Address, B256, U256}; +use anyhow::Result; +use rundler_pool::{LocalPoolBuilder, LocalPoolHandle, PoolConfig, PoolTask, PoolTaskArgs}; +use rundler_sim::PrecheckSettings; +use rundler_sim::simulation::Settings as SimulationSettings; +use rundler_types::{ + EntryPointVersion, PriorityFeeMode, UserOperationId, UserOperationVariant, chain::ChainSpec, +}; +use rundler_types::pool::Pool; +use std::collections::HashMap; +use tokio::sync::broadcast; +use tracing::info; + +pub struct MempoolConfig { + pub rpc_url: String, + pub chain_id: u64, + pub entry_point_address: Address, + pub entry_point_version: EntryPointVersion, +} + +impl Default for MempoolConfig { + fn default() -> Self { + Self { + rpc_url: "http://localhost:8545".to_string(), + chain_id: 1, + entry_point_address: Address::ZERO, + entry_point_version: EntryPointVersion::V0_7, + } + } +} + +pub struct SimpleMempool { + config: MempoolConfig, + pool_handle: Option, +} + +impl SimpleMempool { + pub fn new(config: MempoolConfig) -> Self { + Self { + config, + pool_handle: None, + } + } + + pub async fn initialize(&mut self) -> Result<()> { + info!( + "Initializing mempool with chain_id: {}", + self.config.chain_id + ); + + let chain_spec = ChainSpec { + id: self.config.chain_id, + ..Default::default() + }; + + let pool_config = PoolConfig { + chain_spec: chain_spec.clone(), + entry_point: self.config.entry_point_address, + entry_point_version: self.config.entry_point_version, + same_sender_mempool_count: 4, + min_replacement_fee_increase_percentage: 10, + max_size_of_pool_bytes: 500_000_000, + blocklist: None, + allowlist: None, + precheck_settings: PrecheckSettings { + max_verification_gas: 5_000_000, + max_bundle_execution_gas: 1_000_000_000, + bundle_priority_fee_overhead_percent: 0, + base_fee_accept_percent: 50, + max_uo_cost: U256::from(10u128.pow(18)), + priority_fee_mode: PriorityFeeMode::BaseFeePercent(0), + pre_verification_gas_accept_percent: 100, + verification_gas_limit_efficiency_reject_threshold: 10.0, + }, + sim_settings: SimulationSettings { + min_stake_value: U256::from(10u128.pow(18)), + min_unstake_delay: 84600, + tracer_timeout: "5s".to_string(), + enable_unsafe_fallback: false, + }, + mempool_channel_configs: HashMap::from([(B256::ZERO, Default::default())]), + reputation_tracking_enabled: true, + paymaster_tracking_enabled: true, + da_gas_tracking_enabled: false, + drop_min_num_blocks: 10, + throttled_entity_mempool_count: 4, + throttled_entity_live_blocks: 10, + paymaster_cache_length: 10000, + max_expected_storage_slots: 100, + execution_gas_limit_efficiency_reject_threshold: 10.0, + verification_gas_limit_efficiency_reject_threshold: 10.0, + max_time_in_pool: Some(std::time::Duration::from_secs(3600)), + }; + + let (event_tx, _event_rx) = broadcast::channel(1000); + let pool_builder = LocalPoolBuilder::new(100); + + let pool_task_args = PoolTaskArgs { + chain_spec: chain_spec.clone(), + http_url: self.config.rpc_url.clone(), + chain_max_sync_retries: 3, + chain_poll_interval: std::time::Duration::from_secs(1), + pool_configs: vec![pool_config], + remote_address: None, + chain_update_channel_capacity: 1000, + unsafe_mode: false, + }; + + let _pool_task = PoolTask::new(pool_task_args, event_tx.clone(), pool_builder, event_tx); + + let pool_builder = LocalPoolBuilder::new(100); + let handle = pool_builder.get_handle(); + + self.pool_handle = Some(handle); + + info!("Mempool initialized successfully"); + + Ok(()) + } + + pub async fn add_operation(&self, op: UserOperationVariant) -> Result { + let handle = self + .pool_handle + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Pool not initialized"))?; + + // Default permissions: no special limits or sponsorship. + let perms = rundler_types::UserOperationPermissions::default(); + + let hash = handle + .add_op(op, perms) + .await + .map_err(anyhow::Error::from)?; + + Ok(hash) + } + + pub async fn get_operations(&self, _max_ops: u64) -> Result> { + let _pool_builder = self + .pool_handle + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Pool not initialized"))?; + + anyhow::bail!( + "get_operations not yet fully implemented - need to determine correct LocalPoolHandle API" + ) + } + + pub async fn remove_operation(&self, _op_id: UserOperationId) -> Result<()> { + let _pool_builder = self + .pool_handle + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Pool not initialized"))?; + + anyhow::bail!( + "remove_operation not yet fully implemented - need to determine correct LocalPoolHandle API" + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_mempool_creation() { + let config = MempoolConfig::default(); + let mempool = SimpleMempool::new(config); + assert!(mempool.pool_handle.is_some()); + } +} From b587d180956395c6a54d8f958d32d123ef010b8e Mon Sep 17 00:00:00 2001 From: Rayyan Alam Date: Wed, 10 Dec 2025 15:19:32 -0500 Subject: [PATCH 23/24] chore: create mempool --- crates/account-abstraction-core/core/src/mempool.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/account-abstraction-core/core/src/mempool.rs b/crates/account-abstraction-core/core/src/mempool.rs index 5f483d10..ed4201ac 100644 --- a/crates/account-abstraction-core/core/src/mempool.rs +++ b/crates/account-abstraction-core/core/src/mempool.rs @@ -165,7 +165,13 @@ mod tests { #[tokio::test] async fn test_mempool_creation() { let config = MempoolConfig::default(); - let mempool = SimpleMempool::new(config); + let mut mempool = SimpleMempool::new(config); + + // Should start without a pool handle until initialized. + assert!(mempool.pool_handle.is_none()); + + // After initialize, a handle should be available. + mempool.initialize().await.unwrap(); assert!(mempool.pool_handle.is_some()); } } From 7af97185311222eb2ab1a052f74746d65b772680 Mon Sep 17 00:00:00 2001 From: Rayyan Alam Date: Wed, 10 Dec 2025 15:24:32 -0500 Subject: [PATCH 24/24] chore: format nit pics --- .../core/src/mempool.rs | 2 +- crates/ingress-rpc/src/queue.rs | 20 +++++++++---------- crates/ingress-rpc/src/service.rs | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/account-abstraction-core/core/src/mempool.rs b/crates/account-abstraction-core/core/src/mempool.rs index ed4201ac..218727b2 100644 --- a/crates/account-abstraction-core/core/src/mempool.rs +++ b/crates/account-abstraction-core/core/src/mempool.rs @@ -3,10 +3,10 @@ use anyhow::Result; use rundler_pool::{LocalPoolBuilder, LocalPoolHandle, PoolConfig, PoolTask, PoolTaskArgs}; use rundler_sim::PrecheckSettings; use rundler_sim::simulation::Settings as SimulationSettings; +use rundler_types::pool::Pool; use rundler_types::{ EntryPointVersion, PriorityFeeMode, UserOperationId, UserOperationVariant, chain::ChainSpec, }; -use rundler_types::pool::Pool; use std::collections::HashMap; use tokio::sync::broadcast; use tracing::info; diff --git a/crates/ingress-rpc/src/queue.rs b/crates/ingress-rpc/src/queue.rs index 32375476..79d18c2c 100644 --- a/crates/ingress-rpc/src/queue.rs +++ b/crates/ingress-rpc/src/queue.rs @@ -4,13 +4,13 @@ use anyhow::Result; use async_trait::async_trait; use backon::{ExponentialBuilder, Retryable}; use rdkafka::producer::{FutureProducer, FutureRecord}; +use std::sync::Arc; use tips_core::AcceptedBundle; use tokio::time::Duration; use tracing::{error, info}; - #[async_trait] pub trait MessageQueue: Send + Sync { - async fn publish_raw(&self, topic: &str, key: &str, payload: &[u8]) -> Result<()>; + async fn publish(&self, topic: &str, key: &str, payload: &[u8]) -> Result<()>; } pub struct KafkaMessageQueue { @@ -25,7 +25,7 @@ impl KafkaMessageQueue { #[async_trait] impl MessageQueue for KafkaMessageQueue { - async fn publish_raw(&self, topic: &str, key: &str, payload: &[u8]) -> Result<()> { + async fn publish(&self, topic: &str, key: &str, payload: &[u8]) -> Result<()> { let enqueue = || async { let record = FutureRecord::to(topic).key(key).payload(payload); @@ -67,36 +67,36 @@ impl MessageQueue for KafkaMessageQueue { } pub struct UserOpQueuePublisher { - queue: std::sync::Arc, + queue: Arc, topic: String, } impl UserOpQueuePublisher { - pub fn new(queue: std::sync::Arc, topic: String) -> Self { + pub fn new(queue: Arc, topic: String) -> Self { Self { queue, topic } } pub async fn publish(&self, user_op: &VersionedUserOperation, hash: &B256) -> Result<()> { let key = hash.to_string(); let payload = serde_json::to_vec(&user_op)?; - self.queue.publish_raw(&self.topic, &key, &payload).await + self.queue.publish(&self.topic, &key, &payload).await } } pub struct BundleQueuePublisher { - queue: std::sync::Arc, + queue: Arc, topic: String, } impl BundleQueuePublisher { - pub fn new(queue: std::sync::Arc, topic: String) -> Self { + pub fn new(queue: Arc, topic: String) -> Self { Self { queue, topic } } pub async fn publish(&self, bundle: &AcceptedBundle, hash: &B256) -> Result<()> { let key = hash.to_string(); let payload = serde_json::to_vec(bundle)?; - self.queue.publish_raw(&self.topic, &key, &payload).await + self.queue.publish(&self.topic, &key, &payload).await } } @@ -132,7 +132,7 @@ mod tests { let start = Instant::now(); let result = publisher - .publish_raw( + .publish( "tips-ingress-rpc", bundle_hash.to_string().as_str(), &serde_json::to_vec(&accepted_bundle).unwrap(), diff --git a/crates/ingress-rpc/src/service.rs b/crates/ingress-rpc/src/service.rs index 43c5a4ad..2e245c61 100644 --- a/crates/ingress-rpc/src/service.rs +++ b/crates/ingress-rpc/src/service.rs @@ -456,7 +456,7 @@ mod tests { #[async_trait] impl MessageQueue for MockQueue { - async fn publish_raw(&self, _topic: &str, _key: &str, _payload: &[u8]) -> Result<()> { + async fn publish(&self, _topic: &str, _key: &str, _payload: &[u8]) -> Result<()> { Ok(()) } }