From 89a36842023911f019e1df4027a912f382062b42 Mon Sep 17 00:00:00 2001 From: dancoombs Date: Mon, 25 Mar 2024 18:02:16 -0400 Subject: [PATCH] feat: unsafe mode for EP v0.6 --- bin/rundler/src/cli/builder.rs | 1 + bin/rundler/src/cli/mod.rs | 4 + bin/rundler/src/cli/pool.rs | 1 + bin/rundler/src/cli/rpc.rs | 1 + crates/builder/src/task.rs | 31 +++- crates/pool/src/task.rs | 72 +++++--- .../provider/src/ethers/entry_point/v0_6.rs | 14 +- .../provider/src/ethers/entry_point/v0_7.rs | 11 +- crates/provider/src/traits/entry_point.rs | 1 + crates/provider/src/traits/test_utils.rs | 1 + crates/rpc/src/eth/router.rs | 2 +- crates/rpc/src/task.rs | 2 + crates/sim/src/simulation/v0_6/mod.rs | 3 + crates/sim/src/simulation/v0_6/simulator.rs | 5 - crates/sim/src/simulation/v0_6/unsafe_sim.rs | 173 ++++++++++++++++++ 15 files changed, 275 insertions(+), 47 deletions(-) create mode 100644 crates/sim/src/simulation/v0_6/unsafe_sim.rs diff --git a/bin/rundler/src/cli/builder.rs b/bin/rundler/src/cli/builder.rs index afe26cc3b..f103bb7b1 100644 --- a/bin/rundler/src/cli/builder.rs +++ b/bin/rundler/src/cli/builder.rs @@ -209,6 +209,7 @@ impl BuilderArgs { mempool_configs, }], chain_spec, + unsafe_mode: common.unsafe_mode, rpc_url, private_key: self.private_key.clone(), aws_kms_key_ids: self.aws_kms_key_ids.clone(), diff --git a/bin/rundler/src/cli/mod.rs b/bin/rundler/src/cli/mod.rs index 982cc6d29..6085a5f5c 100644 --- a/bin/rundler/src/cli/mod.rs +++ b/bin/rundler/src/cli/mod.rs @@ -123,6 +123,10 @@ pub struct CommonArgs { )] node_http: Option, + /// Flag for turning unsafe bundling mode on + #[arg(long = "unsafe", env = "UNSAFE", global = true)] + unsafe_mode: bool, + #[arg( long = "max_verification_gas", name = "max_verification_gas", diff --git a/bin/rundler/src/cli/pool.rs b/bin/rundler/src/cli/pool.rs index 639103f2c..5e4169690 100644 --- a/bin/rundler/src/cli/pool.rs +++ b/bin/rundler/src/cli/pool.rs @@ -206,6 +206,7 @@ impl PoolArgs { Ok(PoolTaskArgs { chain_spec, + unsafe_mode: common.unsafe_mode, http_url: common .node_http .clone() diff --git a/bin/rundler/src/cli/rpc.rs b/bin/rundler/src/cli/rpc.rs index 475c6bc2d..64d5da482 100644 --- a/bin/rundler/src/cli/rpc.rs +++ b/bin/rundler/src/cli/rpc.rs @@ -97,6 +97,7 @@ impl RpcArgs { Ok(RpcTaskArgs { chain_spec, + unsafe_mode: common.unsafe_mode, port: self.port, host: self.host.clone(), rpc_url: common diff --git a/crates/builder/src/task.rs b/crates/builder/src/task.rs index 03b8b011e..4c2853249 100644 --- a/crates/builder/src/task.rs +++ b/crates/builder/src/task.rs @@ -26,7 +26,7 @@ use rundler_provider::{EntryPointProvider, EthersEntryPointV0_6, Provider}; use rundler_sim::{ simulation::v0_6::{ SimulateValidationTracerImpl as SimulateValidationTracerImplV0_6, - Simulator as SimulatorV0_6, + Simulator as SimulatorV0_6, UnsafeSimulator as UnsafeSimulatorV0_6, }, MempoolConfig, PriorityFeeMode, SimulationSettings, Simulator, }; @@ -61,6 +61,8 @@ pub struct Args { pub chain_spec: ChainSpec, /// Full node RPC url pub rpc_url: String, + /// True if using unsafe mode + pub unsafe_mode: bool, /// Private key to use for signing transactions /// If not provided, AWS KMS will be used pub private_key: Option, @@ -157,8 +159,16 @@ where info!("Mempool config for ep v0.6: {:?}", ep.mempool_configs); for i in 0..ep.num_bundle_builders { - let (spawn_guard, bundle_sender_action) = self - .create_bundle_builder( + let (spawn_guard, bundle_sender_action) = if self.args.unsafe_mode { + self.create_bundle_builder( + i + ep.bundle_builder_index_offset, + Arc::clone(&provider), + ep_v0_6.clone(), + self.create_unsafe_simulator_v0_6(Arc::clone(&provider), ep_v0_6.clone()), + ) + .await? + } else { + self.create_bundle_builder( i + ep.bundle_builder_index_offset, Arc::clone(&provider), ep_v0_6.clone(), @@ -168,7 +178,8 @@ where ep.mempool_configs.clone(), ), ) - .await?; + .await? + }; sender_handles.push(spawn_guard); bundle_sender_actions.push(bundle_sender_action); } @@ -384,4 +395,16 @@ where mempool_configs, ) } + + fn create_unsafe_simulator_v0_6( + &self, + provider: Arc, + ep: E, + ) -> UnsafeSimulatorV0_6 + where + C: Provider, + E: EntryPointProvider + Clone, + { + UnsafeSimulatorV0_6::new(Arc::clone(&provider), ep, self.args.sim_settings) + } } diff --git a/crates/pool/src/task.rs b/crates/pool/src/task.rs index 7dc39540d..ed8a13de4 100644 --- a/crates/pool/src/task.rs +++ b/crates/pool/src/task.rs @@ -39,6 +39,8 @@ use crate::{ pub struct Args { /// Chain specification. pub chain_spec: ChainSpec, + /// True if using unsafe mode. + pub unsafe_mode: bool, /// HTTP URL for the full node. pub http_url: String, /// Poll interval for full node requests. @@ -94,6 +96,7 @@ impl Task for PoolTask { let pool = PoolTask::create_mempool_v0_6( self.args.chain_spec.clone(), pool_config, + self.args.unsafe_mode, self.event_sender.clone(), provider.clone(), ) @@ -121,17 +124,6 @@ impl Task for PoolTask { ); } } - - let pool = PoolTask::create_mempool_v0_6( - self.args.chain_spec.clone(), - pool_config, - self.event_sender.clone(), - provider.clone(), - ) - .await - .context("should have created mempool")?; - - mempools.insert(pool_config.entry_point, pool); } let pool_handle = self.pool_builder.get_handle(); @@ -204,6 +196,7 @@ impl PoolTask { async fn create_mempool_v0_6( chain_spec: ChainSpec, pool_config: &PoolConfig, + unsafe_mode: bool, event_sender: broadcast::Sender>, provider: Arc

, ) -> anyhow::Result>> { @@ -216,16 +209,6 @@ impl PoolTask { pool_config.precheck_settings, ); - let simulate_validation_tracer = - sim_v0_6::SimulateValidationTracerImpl::new(Arc::clone(&provider), ep.clone()); - let simulator = sim_v0_6::Simulator::new( - Arc::clone(&provider), - ep.clone(), - simulate_validation_tracer, - pool_config.sim_settings, - pool_config.mempool_channel_configs.clone(), - ); - let reputation = Arc::new(AddressReputation::new( ReputationParams::new(pool_config.reputation_tracking_enabled), pool_config.blocklist.clone().unwrap_or_default(), @@ -246,15 +229,44 @@ impl PoolTask { ), ); - let uo_pool = UoPool::new( - pool_config.clone(), - event_sender, - prechecker, - simulator, - paymaster, - reputation, - ); + if unsafe_mode { + let simulator = sim_v0_6::UnsafeSimulator::new( + Arc::clone(&provider), + ep.clone(), + pool_config.sim_settings, + ); + + let uo_pool = UoPool::new( + pool_config.clone(), + event_sender, + prechecker, + simulator, + paymaster, + reputation, + ); - Ok(Arc::new(Box::new(uo_pool))) + Ok(Arc::new(Box::new(uo_pool))) + } else { + let simulate_validation_tracer = + sim_v0_6::SimulateValidationTracerImpl::new(Arc::clone(&provider), ep.clone()); + let simulator = sim_v0_6::Simulator::new( + Arc::clone(&provider), + ep.clone(), + simulate_validation_tracer, + pool_config.sim_settings, + pool_config.mempool_channel_configs.clone(), + ); + + let uo_pool = UoPool::new( + pool_config.clone(), + event_sender, + prechecker, + simulator, + paymaster, + reputation, + ); + + Ok(Arc::new(Box::new(uo_pool))) + } } } diff --git a/crates/provider/src/ethers/entry_point/v0_6.rs b/crates/provider/src/ethers/entry_point/v0_6.rs index 3bf96678b..b70c4212f 100644 --- a/crates/provider/src/ethers/entry_point/v0_6.rs +++ b/crates/provider/src/ethers/entry_point/v0_6.rs @@ -315,15 +315,19 @@ where &self, user_op: UserOperation, max_validation_gas: u64, + block_hash: Option, ) -> anyhow::Result { let pvg = user_op.pre_verification_gas; - match self + let blockless = self .i_entry_point .simulate_validation(user_op) - .gas(U256::from(max_validation_gas) + pvg) - .call() - .await - { + .gas(U256::from(max_validation_gas) + pvg); + let call = match block_hash { + Some(block_hash) => blockless.block(block_hash), + None => blockless, + }; + + match call.call().await { Ok(()) => anyhow::bail!("simulateValidation should always revert"), Err(ContractError::Revert(revert_data)) => ValidationOutput::decode_v0_6(revert_data) .context("entry point should return validation output"), diff --git a/crates/provider/src/ethers/entry_point/v0_7.rs b/crates/provider/src/ethers/entry_point/v0_7.rs index ef08cc44f..a30086590 100644 --- a/crates/provider/src/ethers/entry_point/v0_7.rs +++ b/crates/provider/src/ethers/entry_point/v0_7.rs @@ -310,6 +310,7 @@ where &self, user_op: UserOperation, max_validation_gas: u64, + block_hash: Option, ) -> anyhow::Result { let addr = self.i_entry_point.address(); let pvg = user_op.pre_verification_gas; @@ -319,9 +320,15 @@ where .code(ENTRYPOINTSIMULATIONS_BYTECODE.clone()); let ep_simulations = EntryPointSimulations::new(addr, Arc::clone(&self.provider)); - let result = ep_simulations + let blockless = ep_simulations .simulate_validation(user_op.pack()) - .gas(U256::from(max_validation_gas) + pvg) + .gas(U256::from(max_validation_gas) + pvg); + let call = match block_hash { + Some(block_hash) => blockless.block(block_hash), + None => blockless, + }; + + let result = call .call_raw() .state(&spoof_ep) .await diff --git a/crates/provider/src/traits/entry_point.rs b/crates/provider/src/traits/entry_point.rs index e2554cf89..e77c03fba 100644 --- a/crates/provider/src/traits/entry_point.rs +++ b/crates/provider/src/traits/entry_point.rs @@ -192,6 +192,7 @@ pub trait SimulationProvider: Send + Sync + 'static { &self, user_op: Self::UO, max_validation_gas: u64, + block_hash: Option, ) -> anyhow::Result; /// Call the entry point contract's `simulateHandleOps` function diff --git a/crates/provider/src/traits/test_utils.rs b/crates/provider/src/traits/test_utils.rs index a49ae96cd..08b90a8c6 100644 --- a/crates/provider/src/traits/test_utils.rs +++ b/crates/provider/src/traits/test_utils.rs @@ -61,6 +61,7 @@ mockall::mock! { &self, user_op: v0_6::UserOperation, max_validation_gas: u64, + block_hash: Option ) -> anyhow::Result; async fn call_spoofed_simulate_op( &self, diff --git a/crates/rpc/src/eth/router.rs b/crates/rpc/src/eth/router.rs index b4959907c..982d27f49 100644 --- a/crates/rpc/src/eth/router.rs +++ b/crates/rpc/src/eth/router.rs @@ -286,7 +286,7 @@ where ) -> anyhow::Result { let output = self .entry_point - .call_simulate_validation(uo.into(), max_verification_gas) + .call_simulate_validation(uo.into(), max_verification_gas, None) .await?; Ok(!output.return_info.account_sig_failed) diff --git a/crates/rpc/src/task.rs b/crates/rpc/src/task.rs index a72763b8d..2962ca2bf 100644 --- a/crates/rpc/src/task.rs +++ b/crates/rpc/src/task.rs @@ -48,6 +48,8 @@ use crate::{ pub struct Args { /// Chain spec pub chain_spec: ChainSpec, + /// True if using unsafe mode + pub unsafe_mode: bool, /// Port to listen on. pub port: u16, /// Host to listen on. diff --git a/crates/sim/src/simulation/v0_6/mod.rs b/crates/sim/src/simulation/v0_6/mod.rs index 40dbc3a14..cf71b0816 100644 --- a/crates/sim/src/simulation/v0_6/mod.rs +++ b/crates/sim/src/simulation/v0_6/mod.rs @@ -19,5 +19,8 @@ pub use simulator::Simulator; mod tracer; pub use tracer::{SimulateValidationTracer, SimulateValidationTracerImpl}; +mod unsafe_sim; +pub use unsafe_sim::UnsafeSimulator; + /// Required buffer for verification gas limit when targeting the 0.6 entrypoint contract pub(crate) const REQUIRED_VERIFICATION_GAS_LIMIT_BUFFER: U256 = U256([2000, 0, 0, 0]); diff --git a/crates/sim/src/simulation/v0_6/simulator.rs b/crates/sim/src/simulation/v0_6/simulator.rs index 515be71a6..c76c7e72c 100644 --- a/crates/sim/src/simulation/v0_6/simulator.rs +++ b/crates/sim/src/simulation/v0_6/simulator.rs @@ -113,11 +113,6 @@ where } } - /// Return the associated settings - pub fn settings(&self) -> &Settings { - &self.sim_settings - } - // Run the tracer and transform the output. // Any violations during this stage are errors. async fn create_context( diff --git a/crates/sim/src/simulation/v0_6/unsafe_sim.rs b/crates/sim/src/simulation/v0_6/unsafe_sim.rs new file mode 100644 index 000000000..032519e02 --- /dev/null +++ b/crates/sim/src/simulation/v0_6/unsafe_sim.rs @@ -0,0 +1,173 @@ +// This file is part of Rundler. +// +// Rundler is free software: you can redistribute it and/or modify it under the +// terms of the GNU Lesser General Public License as published by the Free Software +// Foundation, either version 3 of the License, or (at your option) any later version. +// +// Rundler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along with Rundler. +// If not, see https://www.gnu.org/licenses/. + +use std::sync::Arc; + +use ethers::types::H256; +use rundler_provider::{ + AggregatorOut, EntryPoint, Provider, SignatureAggregator, SimulationProvider, +}; +use rundler_types::{ + pool::SimulationViolation, v0_6::UserOperation, EntityInfo, EntityInfos, + UserOperation as UserOperationTrait, ValidTimeRange, +}; + +use crate::{ + SimulationError, SimulationResult, SimulationSettings as Settings, Simulator, ViolationError, +}; + +/// An unsafe simulator that can be used in place of a regular simulator +/// to extract the information needed from simulation while avoiding the use +/// of debug_traceCall. +/// +/// WARNING: This is "unsafe" for a reason. None of the ERC-7562 checks are +/// performed. +pub struct UnsafeSimulator { + provider: Arc

, + entry_point: E, + sim_settings: Settings, +} + +impl UnsafeSimulator { + /// Creates a new unsafe simulator + pub fn new(provider: Arc

, entry_point: E, sim_settings: Settings) -> Self { + Self { + provider, + entry_point, + sim_settings, + } + } +} + +#[async_trait::async_trait] +impl Simulator for UnsafeSimulator +where + P: Provider, + E: EntryPoint + + SimulationProvider + + SignatureAggregator + + Clone, +{ + type UO = UserOperation; + + // Run an unsafe simulation + // + // The only validation checks that are performed are signature checks + async fn simulate_validation( + &self, + op: UserOperation, + block_hash: Option, + _expected_code_hash: Option, + ) -> Result { + tracing::info!("Performing unsafe simulation"); + + let (block_hash, block_number) = match block_hash { + // If we are given a block_hash, we return a None block number, avoiding an extra call + Some(block_hash) => (block_hash, None), + None => { + let hash_and_num = self + .provider + .get_latest_block_hash_and_number() + .await + .map_err(anyhow::Error::from)?; + (hash_and_num.0, Some(hash_and_num.1.as_u64())) + } + }; + + // simulate the validation + let validation_result = self + .entry_point + .call_simulate_validation( + op.clone(), + self.sim_settings.max_verification_gas, + Some(block_hash), + ) + .await + .map_err(anyhow::Error::from)?; + + let pre_op_gas = validation_result.return_info.pre_op_gas; + let valid_time_range = ValidTimeRange::new( + validation_result.return_info.valid_after, + validation_result.return_info.valid_until, + ); + let requires_post_op = !validation_result.return_info.paymaster_context.is_empty(); + + let entity_infos = EntityInfos { + sender: EntityInfo { + address: op.sender(), + is_staked: false, + }, + factory: op.factory().map(|f| EntityInfo { + address: f, + is_staked: false, + }), + paymaster: op.paymaster().map(|p| EntityInfo { + address: p, + is_staked: false, + }), + aggregator: validation_result.aggregator_info.map(|a| EntityInfo { + address: a.address, + is_staked: false, + }), + }; + + let mut violations = vec![]; + + let aggregator = if let Some(aggregator_info) = validation_result.aggregator_info { + let agg_out = self + .entry_point + .validate_user_op_signature( + aggregator_info.address, + op, + self.sim_settings.max_verification_gas, + ) + .await?; + + match agg_out { + AggregatorOut::NotNeeded => None, + AggregatorOut::SuccessWithInfo(info) => Some(info), + AggregatorOut::ValidationReverted => { + violations.push(SimulationViolation::AggregatorValidationFailed); + None + } + } + } else { + None + }; + + if validation_result.return_info.account_sig_failed + || validation_result.return_info.paymaster_sig_failed + { + violations.push(SimulationViolation::InvalidSignature); + } + + if !violations.is_empty() { + Err(SimulationError { + violation_error: ViolationError::Violations(violations), + entity_infos: Some(entity_infos), + })? + } else { + Ok(SimulationResult { + mempools: vec![H256::zero()], + block_hash, + block_number, + pre_op_gas, + valid_time_range, + requires_post_op, + entity_infos, + aggregator, + ..Default::default() + }) + } + } +}