From 48864f12594fa69254dcd57f428ac790d93d3e84 Mon Sep 17 00:00:00 2001 From: dancoombs Date: Fri, 31 Jan 2025 14:57:01 -0600 Subject: [PATCH] feat(builder): add support for a bundle submission proxy --- bin/rundler/src/cli/builder.rs | 132 ++++++++++++++++++---- bin/rundler/src/cli/mod.rs | 126 ++++++++++++++++++--- bin/rundler/src/cli/node/mod.rs | 20 +++- bin/rundler/src/cli/pool.rs | 11 +- crates/builder/src/bundle_sender.rs | 15 ++- crates/builder/src/lib.rs | 2 +- crates/builder/src/sender/bloxroute.rs | 3 +- crates/builder/src/sender/flashbots.rs | 1 - crates/builder/src/sender/mod.rs | 1 - crates/builder/src/sender/raw.rs | 3 +- crates/builder/src/task.rs | 40 ++++--- crates/builder/src/transaction_tracker.rs | 6 +- crates/rpc/src/eth/events/common.rs | 25 ++-- crates/types/src/chain.rs | 9 ++ docs/architecture/builder.md | 33 ++++++ docs/cli.md | 17 ++- 16 files changed, 354 insertions(+), 90 deletions(-) diff --git a/bin/rundler/src/cli/builder.rs b/bin/rundler/src/cli/builder.rs index ae3c748a3..c3fcab024 100644 --- a/bin/rundler/src/cli/builder.rs +++ b/bin/rundler/src/cli/builder.rs @@ -13,12 +13,13 @@ use std::net::SocketAddr; +use alloy_primitives::Address; use anyhow::{bail, Context}; use clap::Args; use rundler_builder::{ - self, BloxrouteSenderArgs, BuilderEvent, BuilderEventKind, BuilderTask, BuilderTaskArgs, - EntryPointBuilderSettings, FlashbotsSenderArgs, LocalBuilderBuilder, RawSenderArgs, - TransactionSenderArgs, TransactionSenderKind, + self, BloxrouteSenderArgs, BuilderEvent, BuilderEventKind, BuilderSettings, BuilderTask, + BuilderTaskArgs, EntryPointBuilderSettings, FlashbotsSenderArgs, LocalBuilderBuilder, + RawSenderArgs, TransactionSenderArgs, TransactionSenderKind, }; use rundler_pool::RemotePoolClient; use rundler_provider::Providers; @@ -29,9 +30,10 @@ use rundler_task::{ }; use rundler_types::{chain::ChainSpec, EntryPointVersion}; use rundler_utils::emit::{self, WithEntryPoint, EVENT_CHANNEL_CAPACITY}; +use serde::Deserialize; use tokio::sync::broadcast; -use super::{json::get_json_config, CommonArgs}; +use super::CommonArgs; const REQUEST_CHANNEL_CAPACITY: usize = 1024; @@ -245,15 +247,6 @@ pub struct BuilderArgs { default_value = "20" )] max_replacement_underpriced_blocks: u64, - - /// The index offset to apply to the builder index - #[arg( - long = "builder_index_offset", - name = "builder_index_offset", - env = "BUILDER_INDEX_OFFSET", - default_value = "0" - )] - pub builder_index_offset: u64, } impl BuilderArgs { @@ -264,6 +257,8 @@ impl BuilderArgs { chain_spec: ChainSpec, common: &CommonArgs, remote_address: Option, + mempool_configs: Option, + entry_point_builders: Option, ) -> anyhow::Result { let priority_fee_mode = PriorityFeeMode::try_from( common.priority_fee_mode_kind.as_str(), @@ -272,36 +267,58 @@ impl BuilderArgs { let rpc_url = common.node_http.clone().context("must provide node_http")?; - let mempool_configs = match &common.mempool_config_path { - Some(path) => get_json_config::(path) - .await - .with_context(|| format!("should load mempool configurations from {path}"))?, - None => MempoolConfigs::default(), - }; - + let mempool_configs = mempool_configs.unwrap_or_default(); let mut entry_points = vec![]; let mut num_builders = 0; if !common.disable_entry_point_v0_6 { + let builders = if let Some(ep_builder_configs) = &entry_point_builders { + ep_builder_configs + .entry_points + .iter() + .find(|ep| ep.address == chain_spec.entry_point_address_v0_6) + .map(|ep| ep.builders()) + .expect("should find entry point v0.6 builders") + } else { + builder_settings_from_cli( + common.builder_index_offset_v0_6, + common.num_builders_v0_6, + ) + }; + entry_points.push(EntryPointBuilderSettings { address: chain_spec.entry_point_address_v0_6, version: EntryPointVersion::V0_6, - num_bundle_builders: common.num_builders_v0_6, - bundle_builder_index_offset: self.builder_index_offset, mempool_configs: mempool_configs .get_for_entry_point(chain_spec.entry_point_address_v0_6), + builders, }); + num_builders += common.num_builders_v0_6; } if !common.disable_entry_point_v0_7 { + let builders = if let Some(ep_builder_configs) = &entry_point_builders { + ep_builder_configs + .entry_points + .iter() + .find(|ep| ep.address == chain_spec.entry_point_address_v0_7) + .map(|ep| ep.builders()) + .expect("should find entry point v0.7 builders") + } else { + builder_settings_from_cli( + common.builder_index_offset_v0_7, + common.num_builders_v0_7, + ) + }; + entry_points.push(EntryPointBuilderSettings { address: chain_spec.entry_point_address_v0_7, version: EntryPointVersion::V0_7, - num_bundle_builders: common.num_builders_v0_7, - bundle_builder_index_offset: self.builder_index_offset, mempool_configs: mempool_configs .get_for_entry_point(chain_spec.entry_point_address_v0_7), + builders, }); + num_builders += common.num_builders_v0_7; } @@ -413,6 +430,69 @@ impl BuilderArgs { } } +#[derive(Debug, Clone, Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub(crate) struct EntryPointBuilderConfigs { + // Builder configs per entry point + entry_points: Vec, +} + +#[derive(Debug, Clone, Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub(crate) struct EntryPointBuilderConfig { + // Entry point address + address: Address, + // Index offset for builders + index_offset: u64, + // Builder configs + builders: Vec, +} + +#[derive(Debug, Clone, Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub(crate) struct BuilderConfig { + // Number of builders using this config + count: u64, + // Submitter proxy to use for builders + proxy: Option
, +} + +impl EntryPointBuilderConfigs { + pub(crate) fn set_known_proxies(&self, chain_spec: &mut ChainSpec) { + for entry_point in &self.entry_points { + for builder in &entry_point.builders { + if let Some(proxy) = builder.proxy { + chain_spec.known_entry_point_proxies.push(proxy); + } + } + } + } +} + +impl EntryPointBuilderConfig { + pub fn builders(&self) -> Vec { + let mut index = self.index_offset; + let mut builders = vec![]; + for builder in &self.builders { + builders.extend((0..builder.count).map(|i| BuilderSettings { + index: index + i, + submitter_proxy: builder.proxy, + })); + index += builder.count; + } + builders + } +} + +fn builder_settings_from_cli(index_offset: u64, count: u64) -> Vec { + (0..count) + .map(|i| BuilderSettings { + index: index_offset + i, + submitter_proxy: None, + }) + .collect() +} + /// CLI options for the Builder server standalone #[derive(Args, Debug)] pub struct BuilderCliArgs { @@ -450,11 +530,15 @@ pub async fn spawn_tasks( )), ); + let (mempool_config, entry_point_builders) = super::load_configs(&common_args).await?; + let task_args = builder_args .to_args( chain_spec.clone(), &common_args, Some(format_socket_addr(&builder_args.host, builder_args.port).parse()?), + mempool_config, + entry_point_builders, ) .await?; diff --git a/bin/rundler/src/cli/mod.rs b/bin/rundler/src/cli/mod.rs index 66511a4b5..d87b7b47d 100644 --- a/bin/rundler/src/cli/mod.rs +++ b/bin/rundler/src/cli/mod.rs @@ -27,7 +27,8 @@ mod pool; mod rpc; mod tracing; -use builder::BuilderCliArgs; +use builder::{BuilderCliArgs, EntryPointBuilderConfigs}; +use json::get_json_config; use node::NodeCliArgs; use pool::PoolCliArgs; use reth_tasks::TaskManager; @@ -38,7 +39,8 @@ use rundler_provider::{ }; use rundler_rpc::{EthApiSettings, RundlerApiSettings}; use rundler_sim::{ - EstimationSettings, PrecheckSettings, PriorityFeeMode, SimulationSettings, MIN_CALL_GAS_LIMIT, + EstimationSettings, MempoolConfigs, PrecheckSettings, PriorityFeeMode, SimulationSettings, + MIN_CALL_GAS_LIMIT, }; use rundler_types::{ chain::ChainSpec, da::DAGasOracleType, v0_6::UserOperation as UserOperationV0_6, @@ -68,17 +70,40 @@ pub async fn run() -> anyhow::Result<()> { .context("metrics server should start")?; let mut cs = chain_spec::resolve_chain_spec(&opt.common.network, &opt.common.chain_spec); - tracing::info!("Chain spec: {:#?}", cs); + + let (mempool_configs, entry_point_builders) = load_configs(&opt.common).await?; + if let Some(entry_point_builders) = &entry_point_builders { + entry_point_builders.set_known_proxies(&mut cs); + } let providers = construct_providers(&opt.common, &cs)?; aggregator::instantiate_aggregators(&opt.common, &mut cs, &providers); + tracing::info!("Chain spec: {:#?}", cs); + match opt.command { Command::Node(args) => { - node::spawn_tasks(task_spawner.clone(), cs, *args, opt.common, providers).await? + node::spawn_tasks( + task_spawner.clone(), + cs, + *args, + opt.common, + providers, + mempool_configs, + entry_point_builders, + ) + .await? } Command::Pool(args) => { - pool::spawn_tasks(task_spawner.clone(), cs, args, opt.common, providers).await? + pool::spawn_tasks( + task_spawner.clone(), + cs, + args, + opt.common, + providers, + mempool_configs, + ) + .await? } Command::Rpc(args) => { rpc::spawn_tasks(task_spawner.clone(), cs, args, opt.common, providers).await? @@ -307,6 +332,15 @@ pub struct CommonArgs { )] pub mempool_config_path: Option, + /// Config path for entrypoint builders + #[arg( + long = "entry_point_builders_path", + name = "entry_point_builders_path", + env = "ENTRY_POINT_BUILDERS_PATH", + global = true + )] + entry_point_builders_path: Option, + #[arg( long = "disable_entry_point_v0_6", name = "disable_entry_point_v0_6", @@ -316,7 +350,17 @@ pub struct CommonArgs { )] pub disable_entry_point_v0_6: bool, + #[arg( + long = "disable_entry_point_v0_7", + name = "disable_entry_point_v0_7", + env = "DISABLE_ENTRY_POINT_V0_7", + default_value = "false", + global = true + )] + pub disable_entry_point_v0_7: bool, + // Ignored if entry_point_v0_6_enabled is false + // Ignored if entry_point_builders_path is set #[arg( long = "num_builders_v0_6", name = "num_builders_v0_6", @@ -326,16 +370,20 @@ pub struct CommonArgs { )] pub num_builders_v0_6: u64, + // Ignored if entry_point_v0_6_enabled is false + // Ignored if entry_point_builders_path is set + // The index offset to apply to the builder index #[arg( - long = "disable_entry_point_v0_7", - name = "disable_entry_point_v0_7", - env = "DISABLE_ENTRY_POINT_V0_7", - default_value = "false", + long = "builder_index_offset_v0_6", + name = "builder_index_offset_v0_6", + env = "BUILDER_INDEX_OFFSET_V0_6", + default_value = "0", global = true )] - pub disable_entry_point_v0_7: bool, + pub builder_index_offset_v0_6: u64, // Ignored if entry_point_v0_7_enabled is false + // Ignored if entry_point_builders_path is set #[arg( long = "num_builders_v0_7", name = "num_builders_v0_7", @@ -345,11 +393,24 @@ pub struct CommonArgs { )] pub num_builders_v0_7: u64, + // Ignored if entry_point_v0_7_enabled is false + // Ignored if entry_point_builders_path is set + // The index offset to apply to the builder index + #[arg( + long = "builder_index_offset_v0_7", + name = "builder_index_offset_v0_7", + env = "BUILDER_INDEX_OFFSET_V0_7", + default_value = "0", + global = true + )] + pub builder_index_offset_v0_7: u64, + #[arg( long = "da_gas_tracking_enabled", name = "da_gas_tracking_enabled", env = "DA_GAS_TRACKING_ENABLED", - default_value = "false" + default_value = "false", + global = true )] pub da_gas_tracking_enabled: bool, @@ -357,21 +418,24 @@ pub struct CommonArgs { long = "provider_client_timeout_seconds", name = "provider_client_timeout_seconds", env = "PROVIDER_CLIENT_TIMEOUT_SECONDS", - default_value = "10" + default_value = "10", + global = true )] pub provider_client_timeout_seconds: u64, #[arg( long = "max_expected_storage_slots", name = "max_expected_storage_slots", - env = "MAX_EXPECTED_STORAGE_SLOTS" + env = "MAX_EXPECTED_STORAGE_SLOTS", + global = true )] pub max_expected_storage_slots: Option, #[arg( long = "bls_aggregation_enabled", name = "bls_aggregation_enabled", - env = "BLS_AGGREGATION_ENABLED" + env = "BLS_AGGREGATION_ENABLED", + global = true )] pub bls_aggregation_enabled: bool, } @@ -678,3 +742,37 @@ fn lint_da_gas_tracking(da_gas_tracking_enabled: bool, chain_spec: &ChainSpec) - true } } + +async fn load_configs( + args: &CommonArgs, +) -> anyhow::Result<(Option, Option)> { + let mempool_configs = if let Some(mempool_config_path) = &args.mempool_config_path { + let mempool_configs = get_json_config::(mempool_config_path) + .await + .with_context(|| format!("should load mempool config from {mempool_config_path}"))?; + + tracing::info!("Mempool configs: {:?}", mempool_configs); + + Some(mempool_configs) + } else { + None + }; + + let entry_point_builders = + if let Some(entry_point_builders_path) = &args.entry_point_builders_path { + let entry_point_builders = + get_json_config::(entry_point_builders_path) + .await + .with_context(|| { + format!("should load entry point builders from {entry_point_builders_path}") + })?; + + tracing::info!("Entry point builders: {:?}", entry_point_builders); + + Some(entry_point_builders) + } else { + None + }; + + Ok((mempool_configs, entry_point_builders)) +} diff --git a/bin/rundler/src/cli/node/mod.rs b/bin/rundler/src/cli/node/mod.rs index c7ad0ed5c..599b8d4c4 100644 --- a/bin/rundler/src/cli/node/mod.rs +++ b/bin/rundler/src/cli/node/mod.rs @@ -16,12 +16,14 @@ use rundler_builder::{BuilderEvent, BuilderTask, LocalBuilderBuilder}; use rundler_pool::{LocalPoolBuilder, PoolEvent, PoolTask}; use rundler_provider::Providers; use rundler_rpc::RpcTask; +use rundler_sim::MempoolConfigs; use rundler_task::TaskSpawnerExt; use rundler_types::chain::ChainSpec; use rundler_utils::emit::{self, WithEntryPoint, EVENT_CHANNEL_CAPACITY}; use tokio::sync::broadcast; use self::events::Event; +use super::EntryPointBuilderConfigs; use crate::cli::{ builder::{self, BuilderArgs}, pool::PoolArgs, @@ -51,7 +53,10 @@ pub async fn spawn_tasks( bundler_args: NodeCliArgs, common_args: CommonArgs, providers: impl Providers + 'static, + mempool_configs: Option, + entry_point_builders: Option, ) -> anyhow::Result<()> { + let _ = mempool_configs; let NodeCliArgs { pool: pool_args, builder: builder_args, @@ -59,10 +64,21 @@ pub async fn spawn_tasks( } = bundler_args; let pool_task_args = pool_args - .to_args(chain_spec.clone(), &common_args, None) + .to_args( + chain_spec.clone(), + &common_args, + None, + mempool_configs.clone(), + ) .await?; let builder_task_args = builder_args - .to_args(chain_spec.clone(), &common_args, None) + .to_args( + chain_spec.clone(), + &common_args, + None, + mempool_configs, + entry_point_builders, + ) .await?; let rpc_task_args = rpc_args.to_args( chain_spec.clone(), diff --git a/bin/rundler/src/cli/pool.rs b/bin/rundler/src/cli/pool.rs index 2b4650a4e..ea0f1ee93 100644 --- a/bin/rundler/src/cli/pool.rs +++ b/bin/rundler/src/cli/pool.rs @@ -205,6 +205,7 @@ impl PoolArgs { chain_spec: ChainSpec, common: &CommonArgs, remote_address: Option, + mempool_configs: Option, ) -> anyhow::Result { let blocklist = match &self.blocklist_path { Some(blocklist) => Some(get_json_config(blocklist).await?), @@ -217,13 +218,7 @@ impl PoolArgs { tracing::info!("blocklist: {:?}", blocklist); tracing::info!("allowlist: {:?}", allowlist); - let mempool_channel_configs = match &common.mempool_config_path { - Some(path) => get_json_config::(path) - .await - .with_context(|| format!("should load mempool configurations from {path}"))?, - None => MempoolConfigs::default(), - }; - tracing::info!("Mempool channel configs: {:?}", mempool_channel_configs); + let mempool_channel_configs = mempool_configs.unwrap_or_default(); let da_gas_tracking_enabled = super::lint_da_gas_tracking(common.da_gas_tracking_enabled, &chain_spec); @@ -307,6 +302,7 @@ pub async fn spawn_tasks( pool_args: PoolCliArgs, common_args: CommonArgs, providers: impl Providers + 'static, + mempool_configs: Option, ) -> anyhow::Result<()> { let PoolCliArgs { pool: pool_args } = pool_args; let (event_sender, event_rx) = broadcast::channel(EVENT_CHANNEL_CAPACITY); @@ -315,6 +311,7 @@ pub async fn spawn_tasks( chain_spec.clone(), &common_args, Some(format!("{}:{}", pool_args.host, pool_args.port).parse()?), + mempool_configs, ) .await?; diff --git a/crates/builder/src/bundle_sender.rs b/crates/builder/src/bundle_sender.rs index e036bcd18..34e19d00f 100644 --- a/crates/builder/src/bundle_sender.rs +++ b/crates/builder/src/bundle_sender.rs @@ -66,6 +66,8 @@ pub(crate) struct BundleSenderImpl { bundle_action_receiver: Option>, chain_spec: ChainSpec, sender_eoa: Address, + // Optional submitter proxy - bundles are sent to this address instead of the entry point + submitter_proxy: Option
, proposer: P, entry_point: E, transaction_tracker: Option, @@ -181,6 +183,7 @@ where bundle_action_receiver: mpsc::Receiver, chain_spec: ChainSpec, sender_eoa: Address, + submitter_proxy: Option
, proposer: P, entry_point: E, transaction_tracker: T, @@ -193,6 +196,7 @@ where bundle_action_receiver: Some(bundle_action_receiver), chain_spec, sender_eoa, + submitter_proxy, proposer, transaction_tracker: Some(transaction_tracker), pool, @@ -428,7 +432,7 @@ where let cancel_res = state .transaction_tracker - .cancel_transaction(*self.entry_point.address(), estimated_fees) + .cancel_transaction(estimated_fees) .await; match cancel_res { @@ -691,12 +695,18 @@ where bundle.entity_updates.len() ); let op_hashes: Vec<_> = bundle.iter_ops().map(|op| self.op_hash(op)).collect(); + let mut tx = self.entry_point.get_send_bundle_transaction( bundle.ops_per_aggregator, self.sender_eoa, bundle.gas_estimate, bundle.gas_fees, ); + + if let Some(submitter_proxy) = self.submitter_proxy { + tx = tx.to(submitter_proxy); + } + tx = tx.nonce(nonce); Ok(Some(BundleTx { tx, @@ -1602,7 +1612,7 @@ mod tests { mock_tracker .expect_cancel_transaction() .once() - .returning(|_, _| Box::pin(async { Ok(Some(B256::ZERO)) })); + .returning(|_| Box::pin(async { Ok(Some(B256::ZERO)) })); mock_trigger.expect_last_block().return_const(NewHead { block_number: 0, @@ -1785,6 +1795,7 @@ mod tests { mpsc::channel(1000).1, ChainSpec::default(), Address::default(), + None, mock_proposer, mock_entry_point, MockTransactionTracker::new(), diff --git a/crates/builder/src/lib.rs b/crates/builder/src/lib.rs index ceee246fc..b6a6b8fb6 100644 --- a/crates/builder/src/lib.rs +++ b/crates/builder/src/lib.rs @@ -37,6 +37,6 @@ pub use server::{LocalBuilderBuilder, LocalBuilderHandle, RemoteBuilderClient}; mod signer; mod task; -pub use task::{Args as BuilderTaskArgs, BuilderTask, EntryPointBuilderSettings}; +pub use task::{Args as BuilderTaskArgs, BuilderSettings, BuilderTask, EntryPointBuilderSettings}; mod transaction_tracker; diff --git a/crates/builder/src/sender/bloxroute.rs b/crates/builder/src/sender/bloxroute.rs index 7687b87ed..fcb601c8d 100644 --- a/crates/builder/src/sender/bloxroute.rs +++ b/crates/builder/src/sender/bloxroute.rs @@ -56,13 +56,12 @@ where &self, _tx_hash: B256, nonce: u64, - to: Address, gas_fees: GasFees, ) -> Result { // Cannot cancel transactions on polygon bloxroute private, however, the transaction may have been // propagated to the public network, and can be cancelled via a public transaction. - let tx = create_hard_cancel_tx(to, nonce, gas_fees); + let tx = create_hard_cancel_tx(self.signer.address(), nonce, gas_fees); let (raw_tx, _) = self.signer.fill_and_sign(tx).await?; diff --git a/crates/builder/src/sender/flashbots.rs b/crates/builder/src/sender/flashbots.rs index fe2669778..8e183e584 100644 --- a/crates/builder/src/sender/flashbots.rs +++ b/crates/builder/src/sender/flashbots.rs @@ -63,7 +63,6 @@ where &self, tx_hash: B256, _nonce: u64, - _to: Address, _gas_fees: GasFees, ) -> Result { let success = self diff --git a/crates/builder/src/sender/mod.rs b/crates/builder/src/sender/mod.rs index 60a97a3e5..1fe9adbf5 100644 --- a/crates/builder/src/sender/mod.rs +++ b/crates/builder/src/sender/mod.rs @@ -94,7 +94,6 @@ pub(crate) trait TransactionSender: Send + Sync { &self, tx_hash: B256, nonce: u64, - to: Address, gas_fees: GasFees, ) -> Result; diff --git a/crates/builder/src/sender/raw.rs b/crates/builder/src/sender/raw.rs index d04596481..8f0445fda 100644 --- a/crates/builder/src/sender/raw.rs +++ b/crates/builder/src/sender/raw.rs @@ -67,10 +67,9 @@ where &self, _tx_hash: B256, nonce: u64, - to: Address, gas_fees: GasFees, ) -> Result { - let tx = create_hard_cancel_tx(to, nonce, gas_fees); + let tx = create_hard_cancel_tx(self.signer.address(), nonce, gas_fees); let (raw_tx, _) = self.signer.fill_and_sign(tx).await?; diff --git a/crates/builder/src/task.rs b/crates/builder/src/task.rs index f93755b31..7e58b01e9 100644 --- a/crates/builder/src/task.rs +++ b/crates/builder/src/task.rs @@ -95,6 +95,15 @@ pub struct Args { pub max_expected_storage_slots: usize, } +/// Builder settings +#[derive(Debug, Clone)] +pub struct BuilderSettings { + /// Index of this builder + pub index: u64, + /// Optional submitter proxy to use for this builder + pub submitter_proxy: Option
, +} + /// Builder settings for an entrypoint #[derive(Debug)] pub struct EntryPointBuilderSettings { @@ -102,12 +111,10 @@ pub struct EntryPointBuilderSettings { pub address: Address, /// Entry point version pub version: EntryPointVersion, - /// Number of bundle builders to start - pub num_bundle_builders: u64, - /// Index offset for bundle builders - pub bundle_builder_index_offset: u64, /// Mempool configs pub mempool_configs: HashMap, + /// Builder settings + pub builders: Vec, } /// Builder task @@ -217,11 +224,11 @@ where .clone() .context("entry point v0.6 not supplied")?; let mut bundle_sender_actions = vec![]; - for i in 0..ep.num_bundle_builders { + for settings in &ep.builders { let bundle_sender_action = if self.args.unsafe_mode { self.create_bundle_builder( task_spawner, - i + ep.bundle_builder_index_offset, + settings, ep_providers.clone(), UnsafeSimulator::new( ep_providers.entry_point().clone(), @@ -233,7 +240,7 @@ where } else { self.create_bundle_builder( task_spawner, - i + ep.bundle_builder_index_offset, + settings, ep_providers.clone(), simulation::new_v0_6_simulator( ep_providers.evm().clone(), @@ -267,11 +274,11 @@ where .clone() .context("entry point v0.7 not supplied")?; let mut bundle_sender_actions = vec![]; - for i in 0..ep.num_bundle_builders { + for settings in &ep.builders { let bundle_sender_action = if self.args.unsafe_mode { self.create_bundle_builder( task_spawner, - i + ep.bundle_builder_index_offset, + settings, ep_providers.clone(), UnsafeSimulator::new( ep_providers.entry_point().clone(), @@ -283,7 +290,7 @@ where } else { self.create_bundle_builder( task_spawner, - i + ep.bundle_builder_index_offset, + settings, ep_providers.clone(), simulation::new_v0_7_simulator( ep_providers.evm().clone(), @@ -303,7 +310,7 @@ where async fn create_bundle_builder( &self, task_spawner: &T, - index: u64, + builder_settings: &BuilderSettings, ep_providers: EP, simulator: S, pk_iter: &mut I, @@ -380,11 +387,11 @@ where ep_providers.evm().clone(), transaction_sender, tracker_settings, - index, + builder_settings.index, ) .await?; - let builder_settings = bundle_sender::Settings { + let sender_settings = bundle_sender::Settings { max_replacement_underpriced_blocks: self.args.max_replacement_underpriced_blocks, max_cancellation_fee_increases: self.args.max_cancellation_fee_increases, max_blocks_to_wait_for_mine: self.args.max_blocks_to_wait_for_mine, @@ -400,7 +407,7 @@ where ); let proposer = BundleProposerImpl::new( - index, + builder_settings.index, ep_providers.clone(), BundleProposerProviders::new(self.pool.clone(), simulator, fee_estimator), proposer_settings, @@ -408,15 +415,16 @@ where ); let builder = BundleSenderImpl::new( - index, + builder_settings.index, send_bundle_rx, self.args.chain_spec.clone(), sender_eoa, + builder_settings.submitter_proxy, proposer, ep_providers.entry_point().clone(), transaction_tracker, self.pool.clone(), - builder_settings, + sender_settings, self.event_sender.clone(), ); diff --git a/crates/builder/src/transaction_tracker.rs b/crates/builder/src/transaction_tracker.rs index c29e557f8..5327ddaf5 100644 --- a/crates/builder/src/transaction_tracker.rs +++ b/crates/builder/src/transaction_tracker.rs @@ -12,7 +12,7 @@ // If not, see https://www.gnu.org/licenses/. use alloy_consensus::Transaction; -use alloy_primitives::{Address, B256}; +use alloy_primitives::B256; use anyhow::{bail, Context}; use async_trait::async_trait; use metrics::Gauge; @@ -57,7 +57,6 @@ pub(crate) trait TransactionTracker: Send + Sync { /// is empty, then either no transaction was cancelled or the cancellation was a "soft-cancel." async fn cancel_transaction( &mut self, - to: Address, estimated_fees: GasFees, ) -> TransactionTrackerResult>; @@ -347,7 +346,6 @@ where async fn cancel_transaction( &mut self, - to: Address, estimated_fees: GasFees, ) -> TransactionTrackerResult> { let (tx_hash, gas_fees) = match self.transactions.last() { @@ -370,7 +368,7 @@ where let cancel_res = self .sender - .cancel_transaction(tx_hash, self.nonce, to, gas_fees) + .cancel_transaction(tx_hash, self.nonce, gas_fees) .await; match cancel_res { diff --git a/crates/rpc/src/eth/events/common.rs b/crates/rpc/src/eth/events/common.rs index bcf54175f..7e134fcfa 100644 --- a/crates/rpc/src/eth/events/common.rs +++ b/crates/rpc/src/eth/events/common.rs @@ -94,17 +94,20 @@ where let input = tx.input(); - let user_operation = if E::address(&self.chain_spec) == to { - E::get_user_operations_from_tx_data(input.clone(), &self.chain_spec) - .into_iter() - .find(|op| op.hash(to, self.chain_spec.id) == hash) - .context("matching user operation should be found in tx data")? - } else { - self.trace_find_user_operation(transaction_hash, hash) - .await - .context("error running trace")? - .context("should have found user operation in trace")? - }; + let ep_address = E::address(&self.chain_spec); + let user_operation = + if ep_address == to || self.chain_spec.known_entry_point_proxies.contains(&to) { + E::get_user_operations_from_tx_data(input.clone(), &self.chain_spec) + .into_iter() + .find(|op| op.hash(ep_address, self.chain_spec.id) == hash) + .context("matching user operation should be found in tx data")? + } else { + tracing::debug!("Unknown entrypoint {to:?}, falling back to trace"); + self.trace_find_user_operation(transaction_hash, hash) + .await + .context("error running trace")? + .context("should have found user operation in trace")? + }; Ok(Some(RpcUserOperationByHash { user_operation: user_operation.into().into(), diff --git a/crates/types/src/chain.rs b/crates/types/src/chain.rs index d6c73a396..a245bf677 100644 --- a/crates/types/src/chain.rs +++ b/crates/types/src/chain.rs @@ -128,6 +128,14 @@ pub struct ChainSpec { /// Registry of signature aggregators #[serde(skip)] pub signature_aggregators: Arc, + + /* + * Proxies + */ + /// A list of known entry point proxies - these have the same interface as the + /// entry point, and are used to proxy bundles to the entry point. + #[serde(skip)] + pub known_entry_point_proxies: Vec
, } /// Type of oracle for estimating priority fees @@ -173,6 +181,7 @@ impl Default for ChainSpec { bloxroute_enabled: false, chain_history_size: 64, signature_aggregators: Arc::new(SignatureAggregatorRegistry::default()), + known_entry_point_proxies: vec![], } } } diff --git a/docs/architecture/builder.md b/docs/architecture/builder.md index 767770880..c54c60a45 100644 --- a/docs/architecture/builder.md +++ b/docs/architecture/builder.md @@ -135,3 +135,36 @@ stateDiagram-v2 CancelPending --> Cancelling: Cancellation dropped/abandoned CancelPending --> Building: Cancellation mined/aborted ``` + +## Builders Configuration + +Rundler supports running multiple builder instances per entry point, per node. To configure set `--num_builders_v0_7` (or corresponding for other entry point) to the number of builders the node should run. Ensure that you have provisioned enough private or KMS keys such that at each builder has access to a distinct key. + +If deploying multiple builder nodes all targeting the same mempool, users must ensure that each builder has a distinct index. To control this, set the `--builder_index_offset_v0_7` (or corresponding for other entry point) such that there are no overlaps or gaps in the builder indexes. + +### Custom + +Most deployments of Rundler should be able to use the configuration above. Rundler does support more detailed configuration for certain use cases. See [`EntryPointBuilderConfigs`](../../bin/rundler/src/cli/builder.rs) for the exact schema of the custom configuration file. Set the path for this file via `--entry_point_builders_path`. When this feature is used the normal CLI-based configuration options are ignored and Rundler will configure builders according to the file. + +Currently, this covers the following use cases: + +* Entry point submission proxies: Set a separate contract address that the builder should submit bundles through. The contract at this address MUST have the same ABI as `IEntryPoint` for all methods used: `handleOps` and `handleAggregatedOps` if using non-aggregated/aggregated ops respectively. + +Example: + +``` +{ + "entryPoints": [ + { + "address": "0x0000000071727De22E5E9d8BAf0edAc6f37da032", + "indexOffset": 0, + "builders": [ + { + "count": 1, + "proxy": "0xA7BD3A9Eb1238842DDB86458aF7dd2a9e166747A" + } + ] + } + ] +} +``` diff --git a/docs/cli.md b/docs/cli.md index 96fb92078..43fddf7eb 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -63,18 +63,31 @@ See [chain spec](./architecture/chain_spec.md) for a detailed description of cha - (*Only required if using other AWS features*) - `--unsafe`: Flag for unsafe bundling mode. When set Rundler will skip checking simulation rules (and any `debug_traceCall`). (default: `false`). - env: *UNSAFE* -- `--mempool_config_path`: Path to the mempool configuration file. (example: `mempool-config.json`, `s3://my-bucket/mempool-config.json`) +- `--mempool_config_path`: Path to the mempool configuration file. (example: `mempool-config.json`, `s3://my-bucket/mempool-config.json`). (default: `None`) - This path can either be a local file path or an S3 url. If using an S3 url, Make sure your machine has access to this file. - env: *MEMPOOL_CONFIG_PATH* - See [here](./architecture/pool.md#alternative-mempools-in-preview) for details. +- `--entry_point_builders_path`: Path to the entry point builders configuration file (example: `builders.json`, `s3://my-bucket/builders.json`). (default: `None`) + - This path can either be a local file path or an S3 url. If using an S3 url, Make sure your machine has access to this file. + - env: *ENTRY_POINT_BUILDERS_PATH* + - NOTE: most deployments can ignore this and use the settings below. + - See [here](./architecture/builder.md#custom) for details. - `--disable_entry_point_v0_6`: Disable entry point v0.6 support. (default: `false`). - env: *DISABLE_ENTRY_POINT_V0_6* - `--num_builders_v0_6`: The number of bundle builders to run on entry point v0.6 (default: `1`) - env: *NUM_BUILDERS_V0_6* + - NOTE: ignored if `entry_point_builders_path` is set +- `--builder_index_offset_v0_6`: If running multiple builder processes, this is the index offset to assign unique indexes to each bundle sender. (default: 0) + - env: *BUILDER_INDEX_OFFSET_V0_6* + - NOTE: ignored if `entry_point_builders_path` is set - `--disable_entry_point_v0_7`: Disable entry point v0.7 support. (default: `false`). - env: *DISABLE_ENTRY_POINT_V0_7* - `--num_builders_v0_7`: The number of bundle builders to run on entry point v0.7 (default: `1`) - env: *NUM_BUILDERS_V0_7* + - NOTE: ignored if `entry_point_builders_path` is set +- `--builder_index_offset_v0_7`: If running multiple builder processes, this is the index offset to assign unique indexes to each bundle sender. (default: 0) + - env: *BUILDER_INDEX_OFFSET_V0_7* + - NOTE: ignored if `entry_point_builders_path` is set - `--da_gas_tracking_enabled`: Enable the DA gas tracking feature of the mempool (default: `false`) - env: *DA_GAS_TRACKING_ENABLED* - `--max_expected_storage_slots`: Optionally set the maximum number of expected storage slots to submit with a conditional transaction. (default: `None`) @@ -223,8 +236,6 @@ List of command line options for configuring the Builder. - env: *BUILDER_FLASHBOTS_RELAY_AUTH_KEY* - `--builder.bloxroute_auth_header`: Only used/required if builder.sender == "polygon_bloxroute." If using the bloxroute transaction sender on Polygon, this is the auth header to supply with the requests. (default: None) - env: *BUILDER_BLOXROUTE_AUTH_HEADER* -- `--builder.index_offset`: If running multiple builder processes, this is the index offset to assign unique indexes to each bundle sender. (default: 0) - - env: *BUILDER_INDEX_OFFSET* - `--builder.pool_url`: If running in distributed mode, the URL of the pool server to use. - env: *BUILDER_POOL_URL* - *Only required when running in distributed mode*