From d13854a3475ecd5497deedbd9ef287a067e508b9 Mon Sep 17 00:00:00 2001 From: Shamit Surana Date: Wed, 3 Dec 2025 13:36:16 -0800 Subject: [PATCH 1/3] Add Native Bundler Configuration to Block Builder --- crates/op-rbuilder/src/args/op.rs | 21 +++ crates/op-rbuilder/src/builders/mod.rs | 34 +++++ crates/op-rbuilder/src/tests/mod.rs | 6 + .../op-rbuilder/src/tests/native_bundler.rs | 135 ++++++++++++++++++ .../src/tests/native_bundler_config.rs | 125 ++++++++++++++++ 5 files changed, 321 insertions(+) create mode 100644 crates/op-rbuilder/src/tests/native_bundler.rs create mode 100644 crates/op-rbuilder/src/tests/native_bundler_config.rs diff --git a/crates/op-rbuilder/src/args/op.rs b/crates/op-rbuilder/src/args/op.rs index 4dc0663ca..99bc7c487 100644 --- a/crates/op-rbuilder/src/args/op.rs +++ b/crates/op-rbuilder/src/args/op.rs @@ -75,6 +75,27 @@ pub struct OpRbuilderArgs { pub flashtestations: FlashtestationsArgs, #[command(flatten)] pub gas_limiter: GasLimiterArgs, + + /// Native Bundler Configuration + /// Enable native bundler in block builder + #[arg(long = "builder.enable-native-bundler", default_value = "false", env = "ENABLE_NATIVE_BUNDLER")] + pub enable_native_bundler: bool, + + /// Secret key for bundler transactions + #[arg(long = "bundler.signer-key", env = "BUNDLER_SIGNER_KEY")] + pub bundler_signer: Option, + + /// Percentage of block gas to reserve for bundles after threshold + #[arg(long = "bundler.gas-reserve-percentage", default_value = "20", env = "BUNDLER_GAS_RESERVE_PERCENTAGE")] + pub bundler_gas_reserve_percentage: u8, + + /// Threshold percentage of block gas before starting bundle reservation + #[arg(long = "bundler.gas-threshold", default_value = "80", env = "BUNDLER_GAS_THRESHOLD")] + pub bundler_gas_threshold: u8, + + /// UserOperation pool URL (if not provided, bundling is disabled) + #[arg(long = "bundler.pool-url", env = "BUNDLER_POOL_URL")] + pub bundler_pool_url: Option, } impl Default for OpRbuilderArgs { diff --git a/crates/op-rbuilder/src/builders/mod.rs b/crates/op-rbuilder/src/builders/mod.rs index 48ce625b8..39c81866f 100644 --- a/crates/op-rbuilder/src/builders/mod.rs +++ b/crates/op-rbuilder/src/builders/mod.rs @@ -130,6 +130,22 @@ pub struct BuilderConfig { /// Resource metering context pub resource_metering: ResourceMetering, + + /// Native Bundler Configuration + /// Whether native bundler is enabled + pub enable_native_bundler: bool, + + /// Bundler signer for bundle transactions + pub bundler_signer: Option, + + /// Percentage of gas to reserve for bundles + pub bundler_gas_reserve_percentage: u8, + + /// Threshold before reserving gas for bundles + pub bundler_gas_threshold: u8, + + /// UserOperation pool connection URL + pub bundler_pool_url: Option, } impl core::fmt::Debug for BuilderConfig { @@ -152,6 +168,14 @@ impl core::fmt::Debug for BuilderConfig { .field("specific", &self.specific) .field("max_gas_per_txn", &self.max_gas_per_txn) .field("gas_limiter_config", &self.gas_limiter_config) + .field("enable_native_bundler", &self.enable_native_bundler) + .field( + "bundler_signer", + &self.bundler_signer.as_ref().map(|s| s.address.to_string()).unwrap_or_else(|| "None".to_string()), + ) + .field("bundler_gas_reserve_percentage", &self.bundler_gas_reserve_percentage) + .field("bundler_gas_threshold", &self.bundler_gas_threshold) + .field("bundler_pool_url", &self.bundler_pool_url) .finish() } } @@ -171,6 +195,11 @@ impl Default for BuilderConfig { max_gas_per_txn: None, gas_limiter_config: GasLimiterArgs::default(), resource_metering: ResourceMetering::default(), + enable_native_bundler: false, + bundler_signer: None, + bundler_gas_reserve_percentage: 20, + bundler_gas_threshold: 80, + bundler_pool_url: None, } } } @@ -197,6 +226,11 @@ where args.enable_resource_metering, args.resource_metering_buffer_size, ), + enable_native_bundler: args.enable_native_bundler, + bundler_signer: args.bundler_signer.clone(), + bundler_gas_reserve_percentage: args.bundler_gas_reserve_percentage, + bundler_gas_threshold: args.bundler_gas_threshold, + bundler_pool_url: args.bundler_pool_url.clone(), specific: S::try_from(args)?, }) } diff --git a/crates/op-rbuilder/src/tests/mod.rs b/crates/op-rbuilder/src/tests/mod.rs index fd202a89b..ea18b1732 100644 --- a/crates/op-rbuilder/src/tests/mod.rs +++ b/crates/op-rbuilder/src/tests/mod.rs @@ -31,6 +31,12 @@ mod txpool; #[cfg(test)] mod forks; + +#[cfg(test)] +mod native_bundler; + +#[cfg(test)] +mod native_bundler_config; // If the order of deployment from the signer changes the address will change #[cfg(test)] const FLASHBLOCKS_NUMBER_ADDRESS: alloy_primitives::Address = diff --git a/crates/op-rbuilder/src/tests/native_bundler.rs b/crates/op-rbuilder/src/tests/native_bundler.rs new file mode 100644 index 000000000..c34976495 --- /dev/null +++ b/crates/op-rbuilder/src/tests/native_bundler.rs @@ -0,0 +1,135 @@ +//! Integration tests for native bundler functionality + +use crate::{ + args::OpRbuilderArgs, + tests::{LocalInstance, TransactionBuilderExt}, +}; +use macros::rb_test; + +/// Test that native bundler is disabled by default +#[rb_test] +async fn native_bundler_disabled_by_default(rbuilder: LocalInstance) -> eyre::Result<()> { + // The default config should have native bundler disabled + // This test verifies that existing functionality is not affected + // when the feature flag is off + + let driver = rbuilder.driver().await?; + + // Build a block without any special bundler logic + let block = driver.build_new_block_with_current_timestamp(None).await?; + + // Should only have the standard transactions (deposit + builder tx) + // No bundle transactions should be present + assert!( + block.transactions.len() >= 2, + "Block should have at least deposit and builder transactions" + ); + + Ok(()) +} + +/// Test native bundler with feature flag enabled +/// This test will be expanded once pool connection is implemented +#[rb_test(args = OpRbuilderArgs { + enable_native_bundler: true, + bundler_gas_reserve_percentage: 25, + bundler_gas_threshold: 75, + // bundler_pool_url will be None, so it uses mock pool + ..Default::default() +})] +async fn native_bundler_with_mock_pool(rbuilder: LocalInstance) -> eyre::Result<()> { + let driver = rbuilder.driver().await?; + + // Send some regular transactions to fill up the block + for _ in 0..5 { + driver + .create_transaction() + .random_valid_transfer() + .send() + .await?; + } + + // Build a block - with mock pool, no bundles will be created yet + // This just tests that the feature flag doesn't break block building + let block = driver.build_new_block_with_current_timestamp(None).await?; + + // Should have regular transactions + assert!( + block.transactions.len() >= 7, // 5 user txs + deposit + builder tx + "Block should include sent transactions" + ); + + // TODO: (BA-3414) Once pool connection is implemented, we would test: + // - Gas reservation occurs at threshold + // - Bundle transaction is included + // - Proper gas accounting + + Ok(()) +} + +/// Test gas reservation threshold +/// This will be properly implemented in BA-3417 +#[rb_test(args = OpRbuilderArgs { + enable_native_bundler: true, + bundler_gas_reserve_percentage: 20, + bundler_gas_threshold: 80, + ..Default::default() +})] +async fn native_bundler_gas_reservation(_rbuilder: LocalInstance) -> eyre::Result<()> { + // TODO: Implement in BA-3417 + // This will test that: + // 1. Regular txs process until 80% gas used + // 2. Remaining 20% is reserved for bundles + // 3. Bundle transactions get included in reserved space + + Ok(()) +} + +#[cfg(test)] +mod cli_tests { + use crate::args::{Cli, CliExt, OpRbuilderArgs}; + use clap::Parser; + + #[test] + fn test_native_bundler_cli_parsing() { + // Test parsing with feature flag enabled + let cli = Cli::parse_from([ + "test", + "node", + "--builder.enable-native-bundler", + "--bundler.gas-reserve-percentage=30", + "--bundler.gas-threshold=70", + "--bundler.pool-url=http://localhost:50051", + ]); + + if let reth_optimism_cli::commands::Commands::Node(node_command) = cli.command { + let args = node_command.ext; + assert!(args.enable_native_bundler); + assert_eq!(args.bundler_gas_reserve_percentage, 30); + assert_eq!(args.bundler_gas_threshold, 70); + assert_eq!(args.bundler_pool_url, Some("http://localhost:50051".to_string())); + } else { + panic!("Expected node command"); + } + } + + #[test] + fn test_native_bundler_cli_defaults() { + // Test that defaults work correctly when only enabling the feature + let cli = Cli::parse_from([ + "test", + "node", + "--builder.enable-native-bundler", + ]); + + if let reth_optimism_cli::commands::Commands::Node(node_command) = cli.command { + let args = node_command.ext; + assert!(args.enable_native_bundler); + assert_eq!(args.bundler_gas_reserve_percentage, 20); // default + assert_eq!(args.bundler_gas_threshold, 80); // default + assert!(args.bundler_pool_url.is_none()); + } else { + panic!("Expected node command"); + } + } +} diff --git a/crates/op-rbuilder/src/tests/native_bundler_config.rs b/crates/op-rbuilder/src/tests/native_bundler_config.rs new file mode 100644 index 000000000..9f70c3fbe --- /dev/null +++ b/crates/op-rbuilder/src/tests/native_bundler_config.rs @@ -0,0 +1,125 @@ +//! Unit tests for native bundler configuration + +#[cfg(test)] +mod tests { + use crate::{ + args::OpRbuilderArgs, + builders::BuilderConfig, + tx_signer::Signer, + }; + + #[test] + fn test_builder_config_defaults() { + // Test that default args produce expected config + let args = OpRbuilderArgs::default(); + let config = BuilderConfig::<()>::try_from(args).unwrap(); + + assert!(!config.enable_native_bundler); + assert_eq!(config.bundler_gas_reserve_percentage, 20); + assert_eq!(config.bundler_gas_threshold, 80); + assert!(config.bundler_signer.is_none()); + assert!(config.bundler_pool_url.is_none()); + } + + #[test] + fn test_builder_config_with_bundler_enabled() { + // Test conversion with all bundler fields set + let mut args = OpRbuilderArgs::default(); + args.enable_native_bundler = true; + args.bundler_gas_reserve_percentage = 25; + args.bundler_gas_threshold = 75; + args.bundler_pool_url = Some("http://localhost:50051".to_string()); + args.bundler_signer = Some(Signer::random()); + + let config = BuilderConfig::<()>::try_from(args.clone()).unwrap(); + + assert!(config.enable_native_bundler); + assert_eq!(config.bundler_gas_reserve_percentage, 25); + assert_eq!(config.bundler_gas_threshold, 75); + assert_eq!(config.bundler_pool_url, Some("http://localhost:50051".to_string())); + assert!(config.bundler_signer.is_some()); + + // Verify the signer was properly cloned + if let (Some(arg_signer), Some(config_signer)) = (&args.bundler_signer, &config.bundler_signer) { + assert_eq!(arg_signer.address, config_signer.address); + } + } + + #[test] + fn test_builder_config_boundary_values() { + // Test with maximum percentage values (100%) + let mut args = OpRbuilderArgs::default(); + args.bundler_gas_reserve_percentage = 100; + args.bundler_gas_threshold = 100; + + let config = BuilderConfig::<()>::try_from(args).unwrap(); + + assert_eq!(config.bundler_gas_reserve_percentage, 100); + assert_eq!(config.bundler_gas_threshold, 100); + + // Test with minimum percentage values (0%) + let mut args = OpRbuilderArgs::default(); + args.bundler_gas_reserve_percentage = 0; + args.bundler_gas_threshold = 0; + + let config = BuilderConfig::<()>::try_from(args).unwrap(); + + assert_eq!(config.bundler_gas_reserve_percentage, 0); + assert_eq!(config.bundler_gas_threshold, 0); + } + + #[test] + fn test_builder_config_partial_settings() { + // Test with only some bundler settings + let mut args = OpRbuilderArgs::default(); + args.enable_native_bundler = true; + args.bundler_gas_reserve_percentage = 15; + // Leave other fields as defaults + + let config = BuilderConfig::<()>::try_from(args).unwrap(); + + assert!(config.enable_native_bundler); + assert_eq!(config.bundler_gas_reserve_percentage, 15); + assert_eq!(config.bundler_gas_threshold, 80); // default + assert!(config.bundler_signer.is_none()); + assert!(config.bundler_pool_url.is_none()); + } + + #[test] + fn test_builder_config_debug_impl() { + // Test that Debug implementation doesn't expose sensitive data + let mut args = OpRbuilderArgs::default(); + args.enable_native_bundler = true; + args.bundler_signer = Some(Signer::random()); + + let config = BuilderConfig::<()>::try_from(args).unwrap(); + let debug_str = format!("{:?}", config); + + // Should contain the field names + assert!(debug_str.contains("enable_native_bundler")); + assert!(debug_str.contains("bundler_gas_reserve_percentage")); + assert!(debug_str.contains("bundler_signer")); + + // The bundler_signer should show the address, not expose the private key + // The Debug impl should use the custom formatting + if let Some(signer) = &config.bundler_signer { + // Should show address, not the full signer struct + assert!(debug_str.contains(&signer.address.to_string())); + } + } + + #[test] + fn test_builder_config_clone_behavior() { + // Test that cloning args doesn't affect config + let mut args = OpRbuilderArgs::default(); + args.bundler_pool_url = Some("http://original.com".to_string()); + + let config = BuilderConfig::<()>::try_from(args.clone()).unwrap(); + + // Modify the original args after creating config + args.bundler_pool_url = Some("http://modified.com".to_string()); + + // Config should retain original value + assert_eq!(config.bundler_pool_url, Some("http://original.com".to_string())); + } +} From d0bb5d00f8e3a151796b522abf63c4127d0079f3 Mon Sep 17 00:00:00 2001 From: Shamit Surana Date: Wed, 3 Dec 2025 14:22:12 -0800 Subject: [PATCH 2/3] format, lint and test --- crates/op-rbuilder/src/args/op.rs | 18 ++++++-- crates/op-rbuilder/src/builders/mod.rs | 21 ++++++---- .../op-rbuilder/src/tests/native_bundler.rs | 35 ++++++++-------- .../src/tests/native_bundler_config.rs | 42 ++++++++++--------- 4 files changed, 69 insertions(+), 47 deletions(-) diff --git a/crates/op-rbuilder/src/args/op.rs b/crates/op-rbuilder/src/args/op.rs index 99bc7c487..b67664ccc 100644 --- a/crates/op-rbuilder/src/args/op.rs +++ b/crates/op-rbuilder/src/args/op.rs @@ -78,7 +78,11 @@ pub struct OpRbuilderArgs { /// Native Bundler Configuration /// Enable native bundler in block builder - #[arg(long = "builder.enable-native-bundler", default_value = "false", env = "ENABLE_NATIVE_BUNDLER")] + #[arg( + long = "builder.enable-native-bundler", + default_value = "false", + env = "ENABLE_NATIVE_BUNDLER" + )] pub enable_native_bundler: bool, /// Secret key for bundler transactions @@ -86,11 +90,19 @@ pub struct OpRbuilderArgs { pub bundler_signer: Option, /// Percentage of block gas to reserve for bundles after threshold - #[arg(long = "bundler.gas-reserve-percentage", default_value = "20", env = "BUNDLER_GAS_RESERVE_PERCENTAGE")] + #[arg( + long = "bundler.gas-reserve-percentage", + default_value = "20", + env = "BUNDLER_GAS_RESERVE_PERCENTAGE" + )] pub bundler_gas_reserve_percentage: u8, /// Threshold percentage of block gas before starting bundle reservation - #[arg(long = "bundler.gas-threshold", default_value = "80", env = "BUNDLER_GAS_THRESHOLD")] + #[arg( + long = "bundler.gas-threshold", + default_value = "80", + env = "BUNDLER_GAS_THRESHOLD" + )] pub bundler_gas_threshold: u8, /// UserOperation pool URL (if not provided, bundling is disabled) diff --git a/crates/op-rbuilder/src/builders/mod.rs b/crates/op-rbuilder/src/builders/mod.rs index 39c81866f..7ebff5979 100644 --- a/crates/op-rbuilder/src/builders/mod.rs +++ b/crates/op-rbuilder/src/builders/mod.rs @@ -134,16 +134,16 @@ pub struct BuilderConfig { /// Native Bundler Configuration /// Whether native bundler is enabled pub enable_native_bundler: bool, - + /// Bundler signer for bundle transactions pub bundler_signer: Option, - + /// Percentage of gas to reserve for bundles pub bundler_gas_reserve_percentage: u8, - + /// Threshold before reserving gas for bundles pub bundler_gas_threshold: u8, - + /// UserOperation pool connection URL pub bundler_pool_url: Option, } @@ -171,9 +171,16 @@ impl core::fmt::Debug for BuilderConfig { .field("enable_native_bundler", &self.enable_native_bundler) .field( "bundler_signer", - &self.bundler_signer.as_ref().map(|s| s.address.to_string()).unwrap_or_else(|| "None".to_string()), + &self + .bundler_signer + .as_ref() + .map(|s| s.address.to_string()) + .unwrap_or_else(|| "None".to_string()), + ) + .field( + "bundler_gas_reserve_percentage", + &self.bundler_gas_reserve_percentage, ) - .field("bundler_gas_reserve_percentage", &self.bundler_gas_reserve_percentage) .field("bundler_gas_threshold", &self.bundler_gas_threshold) .field("bundler_pool_url", &self.bundler_pool_url) .finish() @@ -227,7 +234,7 @@ where args.resource_metering_buffer_size, ), enable_native_bundler: args.enable_native_bundler, - bundler_signer: args.bundler_signer.clone(), + bundler_signer: args.bundler_signer, bundler_gas_reserve_percentage: args.bundler_gas_reserve_percentage, bundler_gas_threshold: args.bundler_gas_threshold, bundler_pool_url: args.bundler_pool_url.clone(), diff --git a/crates/op-rbuilder/src/tests/native_bundler.rs b/crates/op-rbuilder/src/tests/native_bundler.rs index c34976495..27e7e3775 100644 --- a/crates/op-rbuilder/src/tests/native_bundler.rs +++ b/crates/op-rbuilder/src/tests/native_bundler.rs @@ -12,19 +12,19 @@ async fn native_bundler_disabled_by_default(rbuilder: LocalInstance) -> eyre::Re // The default config should have native bundler disabled // This test verifies that existing functionality is not affected // when the feature flag is off - + let driver = rbuilder.driver().await?; - + // Build a block without any special bundler logic let block = driver.build_new_block_with_current_timestamp(None).await?; - + // Should only have the standard transactions (deposit + builder tx) // No bundle transactions should be present assert!( block.transactions.len() >= 2, "Block should have at least deposit and builder transactions" ); - + Ok(()) } @@ -39,7 +39,7 @@ async fn native_bundler_disabled_by_default(rbuilder: LocalInstance) -> eyre::Re })] async fn native_bundler_with_mock_pool(rbuilder: LocalInstance) -> eyre::Result<()> { let driver = rbuilder.driver().await?; - + // Send some regular transactions to fill up the block for _ in 0..5 { driver @@ -48,22 +48,22 @@ async fn native_bundler_with_mock_pool(rbuilder: LocalInstance) -> eyre::Result< .send() .await?; } - + // Build a block - with mock pool, no bundles will be created yet // This just tests that the feature flag doesn't break block building let block = driver.build_new_block_with_current_timestamp(None).await?; - + // Should have regular transactions assert!( block.transactions.len() >= 7, // 5 user txs + deposit + builder tx "Block should include sent transactions" ); - + // TODO: (BA-3414) Once pool connection is implemented, we would test: // - Gas reservation occurs at threshold // - Bundle transaction is included // - Proper gas accounting - + Ok(()) } @@ -81,7 +81,7 @@ async fn native_bundler_gas_reservation(_rbuilder: LocalInstance) -> eyre::Resul // 1. Regular txs process until 80% gas used // 2. Remaining 20% is reserved for bundles // 3. Bundle transactions get included in reserved space - + Ok(()) } @@ -101,13 +101,16 @@ mod cli_tests { "--bundler.gas-threshold=70", "--bundler.pool-url=http://localhost:50051", ]); - + if let reth_optimism_cli::commands::Commands::Node(node_command) = cli.command { let args = node_command.ext; assert!(args.enable_native_bundler); assert_eq!(args.bundler_gas_reserve_percentage, 30); assert_eq!(args.bundler_gas_threshold, 70); - assert_eq!(args.bundler_pool_url, Some("http://localhost:50051".to_string())); + assert_eq!( + args.bundler_pool_url, + Some("http://localhost:50051".to_string()) + ); } else { panic!("Expected node command"); } @@ -116,12 +119,8 @@ mod cli_tests { #[test] fn test_native_bundler_cli_defaults() { // Test that defaults work correctly when only enabling the feature - let cli = Cli::parse_from([ - "test", - "node", - "--builder.enable-native-bundler", - ]); - + let cli = Cli::parse_from(["test", "node", "--builder.enable-native-bundler"]); + if let reth_optimism_cli::commands::Commands::Node(node_command) = cli.command { let args = node_command.ext; assert!(args.enable_native_bundler); diff --git a/crates/op-rbuilder/src/tests/native_bundler_config.rs b/crates/op-rbuilder/src/tests/native_bundler_config.rs index 9f70c3fbe..f37c7e3ce 100644 --- a/crates/op-rbuilder/src/tests/native_bundler_config.rs +++ b/crates/op-rbuilder/src/tests/native_bundler_config.rs @@ -2,18 +2,14 @@ #[cfg(test)] mod tests { - use crate::{ - args::OpRbuilderArgs, - builders::BuilderConfig, - tx_signer::Signer, - }; + use crate::{args::OpRbuilderArgs, builders::BuilderConfig, tx_signer::Signer}; #[test] fn test_builder_config_defaults() { // Test that default args produce expected config let args = OpRbuilderArgs::default(); let config = BuilderConfig::<()>::try_from(args).unwrap(); - + assert!(!config.enable_native_bundler); assert_eq!(config.bundler_gas_reserve_percentage, 20); assert_eq!(config.bundler_gas_threshold, 80); @@ -32,15 +28,20 @@ mod tests { args.bundler_signer = Some(Signer::random()); let config = BuilderConfig::<()>::try_from(args.clone()).unwrap(); - + assert!(config.enable_native_bundler); assert_eq!(config.bundler_gas_reserve_percentage, 25); assert_eq!(config.bundler_gas_threshold, 75); - assert_eq!(config.bundler_pool_url, Some("http://localhost:50051".to_string())); + assert_eq!( + config.bundler_pool_url, + Some("http://localhost:50051".to_string()) + ); assert!(config.bundler_signer.is_some()); - + // Verify the signer was properly cloned - if let (Some(arg_signer), Some(config_signer)) = (&args.bundler_signer, &config.bundler_signer) { + if let (Some(arg_signer), Some(config_signer)) = + (&args.bundler_signer, &config.bundler_signer) + { assert_eq!(arg_signer.address, config_signer.address); } } @@ -53,7 +54,7 @@ mod tests { args.bundler_gas_threshold = 100; let config = BuilderConfig::<()>::try_from(args).unwrap(); - + assert_eq!(config.bundler_gas_reserve_percentage, 100); assert_eq!(config.bundler_gas_threshold, 100); @@ -63,7 +64,7 @@ mod tests { args.bundler_gas_threshold = 0; let config = BuilderConfig::<()>::try_from(args).unwrap(); - + assert_eq!(config.bundler_gas_reserve_percentage, 0); assert_eq!(config.bundler_gas_threshold, 0); } @@ -77,7 +78,7 @@ mod tests { // Leave other fields as defaults let config = BuilderConfig::<()>::try_from(args).unwrap(); - + assert!(config.enable_native_bundler); assert_eq!(config.bundler_gas_reserve_percentage, 15); assert_eq!(config.bundler_gas_threshold, 80); // default @@ -94,12 +95,12 @@ mod tests { let config = BuilderConfig::<()>::try_from(args).unwrap(); let debug_str = format!("{:?}", config); - + // Should contain the field names assert!(debug_str.contains("enable_native_bundler")); assert!(debug_str.contains("bundler_gas_reserve_percentage")); assert!(debug_str.contains("bundler_signer")); - + // The bundler_signer should show the address, not expose the private key // The Debug impl should use the custom formatting if let Some(signer) = &config.bundler_signer { @@ -113,13 +114,16 @@ mod tests { // Test that cloning args doesn't affect config let mut args = OpRbuilderArgs::default(); args.bundler_pool_url = Some("http://original.com".to_string()); - + let config = BuilderConfig::<()>::try_from(args.clone()).unwrap(); - + // Modify the original args after creating config args.bundler_pool_url = Some("http://modified.com".to_string()); - + // Config should retain original value - assert_eq!(config.bundler_pool_url, Some("http://original.com".to_string())); + assert_eq!( + config.bundler_pool_url, + Some("http://original.com".to_string()) + ); } } From 38951e310d5740000ad985a54ea03dbed2abca01 Mon Sep 17 00:00:00 2001 From: Shamit Surana Date: Wed, 3 Dec 2025 16:35:50 -0800 Subject: [PATCH 3/3] refactor: Use aa_ prefix for Account Abstraction bundler configuration - Renamed enable_native_bundler to enable_aa_bundler - Renamed bundler_* fields to aa_bundler_* and aa_* fields - Updated CLI args to use --builder.enable-aa-bundler and --aa.* flags - Updated all tests to use new naming convention This avoids confusion with transaction bundles in op-rbuilder --- crates/op-rbuilder/src/args/op.rs | 38 ++++----- crates/op-rbuilder/src/builders/mod.rs | 55 ++++++------ .../op-rbuilder/src/tests/native_bundler.rs | 43 +++++----- .../src/tests/native_bundler_config.rs | 85 +++++++++---------- 4 files changed, 106 insertions(+), 115 deletions(-) diff --git a/crates/op-rbuilder/src/args/op.rs b/crates/op-rbuilder/src/args/op.rs index b67664ccc..6cb05de25 100644 --- a/crates/op-rbuilder/src/args/op.rs +++ b/crates/op-rbuilder/src/args/op.rs @@ -76,38 +76,38 @@ pub struct OpRbuilderArgs { #[command(flatten)] pub gas_limiter: GasLimiterArgs, - /// Native Bundler Configuration - /// Enable native bundler in block builder + /// Account Abstraction (AA) Native Bundler Configuration + /// Enable AA native bundler in block builder #[arg( - long = "builder.enable-native-bundler", + long = "builder.enable-aa-bundler", default_value = "false", - env = "ENABLE_NATIVE_BUNDLER" + env = "ENABLE_AA_BUNDLER" )] - pub enable_native_bundler: bool, + pub enable_aa_bundler: bool, - /// Secret key for bundler transactions - #[arg(long = "bundler.signer-key", env = "BUNDLER_SIGNER_KEY")] - pub bundler_signer: Option, + /// Secret key for AA bundle transactions + #[arg(long = "aa.bundler-signer-key", env = "AA_BUNDLER_SIGNER_KEY")] + pub aa_bundler_signer: Option, - /// Percentage of block gas to reserve for bundles after threshold + /// Percentage of block gas to reserve for AA bundles after threshold #[arg( - long = "bundler.gas-reserve-percentage", + long = "aa.gas-reserve-percentage", default_value = "20", - env = "BUNDLER_GAS_RESERVE_PERCENTAGE" + env = "AA_GAS_RESERVE_PERCENTAGE" )] - pub bundler_gas_reserve_percentage: u8, + pub aa_gas_reserve_percentage: u8, - /// Threshold percentage of block gas before starting bundle reservation + /// Threshold percentage of block gas before starting AA bundle reservation #[arg( - long = "bundler.gas-threshold", + long = "aa.gas-threshold", default_value = "80", - env = "BUNDLER_GAS_THRESHOLD" + env = "AA_GAS_THRESHOLD" )] - pub bundler_gas_threshold: u8, + pub aa_gas_threshold: u8, - /// UserOperation pool URL (if not provided, bundling is disabled) - #[arg(long = "bundler.pool-url", env = "BUNDLER_POOL_URL")] - pub bundler_pool_url: Option, + /// UserOperation pool URL (if not provided, AA bundling is disabled) + #[arg(long = "aa.pool-url", env = "AA_POOL_URL")] + pub aa_pool_url: Option, } impl Default for OpRbuilderArgs { diff --git a/crates/op-rbuilder/src/builders/mod.rs b/crates/op-rbuilder/src/builders/mod.rs index 7ebff5979..0aa05039c 100644 --- a/crates/op-rbuilder/src/builders/mod.rs +++ b/crates/op-rbuilder/src/builders/mod.rs @@ -131,21 +131,21 @@ pub struct BuilderConfig { /// Resource metering context pub resource_metering: ResourceMetering, - /// Native Bundler Configuration - /// Whether native bundler is enabled - pub enable_native_bundler: bool, + /// Account Abstraction (AA) Native Bundler Configuration + /// Whether AA native bundler is enabled + pub enable_aa_bundler: bool, - /// Bundler signer for bundle transactions - pub bundler_signer: Option, + /// AA bundler signer for bundle transactions + pub aa_bundler_signer: Option, - /// Percentage of gas to reserve for bundles - pub bundler_gas_reserve_percentage: u8, + /// Percentage of gas to reserve for AA bundles + pub aa_gas_reserve_percentage: u8, - /// Threshold before reserving gas for bundles - pub bundler_gas_threshold: u8, + /// Threshold before reserving gas for AA bundles + pub aa_gas_threshold: u8, /// UserOperation pool connection URL - pub bundler_pool_url: Option, + pub aa_pool_url: Option, } impl core::fmt::Debug for BuilderConfig { @@ -168,21 +168,18 @@ impl core::fmt::Debug for BuilderConfig { .field("specific", &self.specific) .field("max_gas_per_txn", &self.max_gas_per_txn) .field("gas_limiter_config", &self.gas_limiter_config) - .field("enable_native_bundler", &self.enable_native_bundler) + .field("enable_aa_bundler", &self.enable_aa_bundler) .field( - "bundler_signer", + "aa_bundler_signer", &self - .bundler_signer + .aa_bundler_signer .as_ref() .map(|s| s.address.to_string()) .unwrap_or_else(|| "None".to_string()), ) - .field( - "bundler_gas_reserve_percentage", - &self.bundler_gas_reserve_percentage, - ) - .field("bundler_gas_threshold", &self.bundler_gas_threshold) - .field("bundler_pool_url", &self.bundler_pool_url) + .field("aa_gas_reserve_percentage", &self.aa_gas_reserve_percentage) + .field("aa_gas_threshold", &self.aa_gas_threshold) + .field("aa_pool_url", &self.aa_pool_url) .finish() } } @@ -202,11 +199,11 @@ impl Default for BuilderConfig { max_gas_per_txn: None, gas_limiter_config: GasLimiterArgs::default(), resource_metering: ResourceMetering::default(), - enable_native_bundler: false, - bundler_signer: None, - bundler_gas_reserve_percentage: 20, - bundler_gas_threshold: 80, - bundler_pool_url: None, + enable_aa_bundler: false, + aa_bundler_signer: None, + aa_gas_reserve_percentage: 20, + aa_gas_threshold: 80, + aa_pool_url: None, } } } @@ -233,11 +230,11 @@ where args.enable_resource_metering, args.resource_metering_buffer_size, ), - enable_native_bundler: args.enable_native_bundler, - bundler_signer: args.bundler_signer, - bundler_gas_reserve_percentage: args.bundler_gas_reserve_percentage, - bundler_gas_threshold: args.bundler_gas_threshold, - bundler_pool_url: args.bundler_pool_url.clone(), + enable_aa_bundler: args.enable_aa_bundler, + aa_bundler_signer: args.aa_bundler_signer, + aa_gas_reserve_percentage: args.aa_gas_reserve_percentage, + aa_gas_threshold: args.aa_gas_threshold, + aa_pool_url: args.aa_pool_url.clone(), specific: S::try_from(args)?, }) } diff --git a/crates/op-rbuilder/src/tests/native_bundler.rs b/crates/op-rbuilder/src/tests/native_bundler.rs index 27e7e3775..cec3f2bb8 100644 --- a/crates/op-rbuilder/src/tests/native_bundler.rs +++ b/crates/op-rbuilder/src/tests/native_bundler.rs @@ -31,10 +31,10 @@ async fn native_bundler_disabled_by_default(rbuilder: LocalInstance) -> eyre::Re /// Test native bundler with feature flag enabled /// This test will be expanded once pool connection is implemented #[rb_test(args = OpRbuilderArgs { - enable_native_bundler: true, - bundler_gas_reserve_percentage: 25, - bundler_gas_threshold: 75, - // bundler_pool_url will be None, so it uses mock pool + enable_aa_bundler: true, + aa_gas_reserve_percentage: 25, + aa_gas_threshold: 75, + // aa_pool_url will be None, so it uses mock pool ..Default::default() })] async fn native_bundler_with_mock_pool(rbuilder: LocalInstance) -> eyre::Result<()> { @@ -70,9 +70,9 @@ async fn native_bundler_with_mock_pool(rbuilder: LocalInstance) -> eyre::Result< /// Test gas reservation threshold /// This will be properly implemented in BA-3417 #[rb_test(args = OpRbuilderArgs { - enable_native_bundler: true, - bundler_gas_reserve_percentage: 20, - bundler_gas_threshold: 80, + enable_aa_bundler: true, + aa_gas_reserve_percentage: 20, + aa_gas_threshold: 80, ..Default::default() })] async fn native_bundler_gas_reservation(_rbuilder: LocalInstance) -> eyre::Result<()> { @@ -96,21 +96,18 @@ mod cli_tests { let cli = Cli::parse_from([ "test", "node", - "--builder.enable-native-bundler", - "--bundler.gas-reserve-percentage=30", - "--bundler.gas-threshold=70", - "--bundler.pool-url=http://localhost:50051", + "--builder.enable-aa-bundler", + "--aa.gas-reserve-percentage=30", + "--aa.gas-threshold=70", + "--aa.pool-url=http://localhost:50051", ]); if let reth_optimism_cli::commands::Commands::Node(node_command) = cli.command { let args = node_command.ext; - assert!(args.enable_native_bundler); - assert_eq!(args.bundler_gas_reserve_percentage, 30); - assert_eq!(args.bundler_gas_threshold, 70); - assert_eq!( - args.bundler_pool_url, - Some("http://localhost:50051".to_string()) - ); + assert!(args.enable_aa_bundler); + assert_eq!(args.aa_gas_reserve_percentage, 30); + assert_eq!(args.aa_gas_threshold, 70); + assert_eq!(args.aa_pool_url, Some("http://localhost:50051".to_string())); } else { panic!("Expected node command"); } @@ -119,14 +116,14 @@ mod cli_tests { #[test] fn test_native_bundler_cli_defaults() { // Test that defaults work correctly when only enabling the feature - let cli = Cli::parse_from(["test", "node", "--builder.enable-native-bundler"]); + let cli = Cli::parse_from(["test", "node", "--builder.enable-aa-bundler"]); if let reth_optimism_cli::commands::Commands::Node(node_command) = cli.command { let args = node_command.ext; - assert!(args.enable_native_bundler); - assert_eq!(args.bundler_gas_reserve_percentage, 20); // default - assert_eq!(args.bundler_gas_threshold, 80); // default - assert!(args.bundler_pool_url.is_none()); + assert!(args.enable_aa_bundler); + assert_eq!(args.aa_gas_reserve_percentage, 20); // default + assert_eq!(args.aa_gas_threshold, 80); // default + assert!(args.aa_pool_url.is_none()); } else { panic!("Expected node command"); } diff --git a/crates/op-rbuilder/src/tests/native_bundler_config.rs b/crates/op-rbuilder/src/tests/native_bundler_config.rs index f37c7e3ce..c034be5d2 100644 --- a/crates/op-rbuilder/src/tests/native_bundler_config.rs +++ b/crates/op-rbuilder/src/tests/native_bundler_config.rs @@ -10,37 +10,37 @@ mod tests { let args = OpRbuilderArgs::default(); let config = BuilderConfig::<()>::try_from(args).unwrap(); - assert!(!config.enable_native_bundler); - assert_eq!(config.bundler_gas_reserve_percentage, 20); - assert_eq!(config.bundler_gas_threshold, 80); - assert!(config.bundler_signer.is_none()); - assert!(config.bundler_pool_url.is_none()); + assert!(!config.enable_aa_bundler); + assert_eq!(config.aa_gas_reserve_percentage, 20); + assert_eq!(config.aa_gas_threshold, 80); + assert!(config.aa_bundler_signer.is_none()); + assert!(config.aa_pool_url.is_none()); } #[test] fn test_builder_config_with_bundler_enabled() { // Test conversion with all bundler fields set let mut args = OpRbuilderArgs::default(); - args.enable_native_bundler = true; - args.bundler_gas_reserve_percentage = 25; - args.bundler_gas_threshold = 75; - args.bundler_pool_url = Some("http://localhost:50051".to_string()); - args.bundler_signer = Some(Signer::random()); + args.enable_aa_bundler = true; + args.aa_gas_reserve_percentage = 25; + args.aa_gas_threshold = 75; + args.aa_pool_url = Some("http://localhost:50051".to_string()); + args.aa_bundler_signer = Some(Signer::random()); let config = BuilderConfig::<()>::try_from(args.clone()).unwrap(); - assert!(config.enable_native_bundler); - assert_eq!(config.bundler_gas_reserve_percentage, 25); - assert_eq!(config.bundler_gas_threshold, 75); + assert!(config.enable_aa_bundler); + assert_eq!(config.aa_gas_reserve_percentage, 25); + assert_eq!(config.aa_gas_threshold, 75); assert_eq!( - config.bundler_pool_url, + config.aa_pool_url, Some("http://localhost:50051".to_string()) ); - assert!(config.bundler_signer.is_some()); + assert!(config.aa_bundler_signer.is_some()); // Verify the signer was properly cloned if let (Some(arg_signer), Some(config_signer)) = - (&args.bundler_signer, &config.bundler_signer) + (&args.aa_bundler_signer, &config.aa_bundler_signer) { assert_eq!(arg_signer.address, config_signer.address); } @@ -50,60 +50,60 @@ mod tests { fn test_builder_config_boundary_values() { // Test with maximum percentage values (100%) let mut args = OpRbuilderArgs::default(); - args.bundler_gas_reserve_percentage = 100; - args.bundler_gas_threshold = 100; + args.aa_gas_reserve_percentage = 100; + args.aa_gas_threshold = 100; let config = BuilderConfig::<()>::try_from(args).unwrap(); - assert_eq!(config.bundler_gas_reserve_percentage, 100); - assert_eq!(config.bundler_gas_threshold, 100); + assert_eq!(config.aa_gas_reserve_percentage, 100); + assert_eq!(config.aa_gas_threshold, 100); // Test with minimum percentage values (0%) let mut args = OpRbuilderArgs::default(); - args.bundler_gas_reserve_percentage = 0; - args.bundler_gas_threshold = 0; + args.aa_gas_reserve_percentage = 0; + args.aa_gas_threshold = 0; let config = BuilderConfig::<()>::try_from(args).unwrap(); - assert_eq!(config.bundler_gas_reserve_percentage, 0); - assert_eq!(config.bundler_gas_threshold, 0); + assert_eq!(config.aa_gas_reserve_percentage, 0); + assert_eq!(config.aa_gas_threshold, 0); } #[test] fn test_builder_config_partial_settings() { // Test with only some bundler settings let mut args = OpRbuilderArgs::default(); - args.enable_native_bundler = true; - args.bundler_gas_reserve_percentage = 15; + args.enable_aa_bundler = true; + args.aa_gas_reserve_percentage = 15; // Leave other fields as defaults let config = BuilderConfig::<()>::try_from(args).unwrap(); - assert!(config.enable_native_bundler); - assert_eq!(config.bundler_gas_reserve_percentage, 15); - assert_eq!(config.bundler_gas_threshold, 80); // default - assert!(config.bundler_signer.is_none()); - assert!(config.bundler_pool_url.is_none()); + assert!(config.enable_aa_bundler); + assert_eq!(config.aa_gas_reserve_percentage, 15); + assert_eq!(config.aa_gas_threshold, 80); // default + assert!(config.aa_bundler_signer.is_none()); + assert!(config.aa_pool_url.is_none()); } #[test] fn test_builder_config_debug_impl() { // Test that Debug implementation doesn't expose sensitive data let mut args = OpRbuilderArgs::default(); - args.enable_native_bundler = true; - args.bundler_signer = Some(Signer::random()); + args.enable_aa_bundler = true; + args.aa_bundler_signer = Some(Signer::random()); let config = BuilderConfig::<()>::try_from(args).unwrap(); let debug_str = format!("{:?}", config); // Should contain the field names - assert!(debug_str.contains("enable_native_bundler")); - assert!(debug_str.contains("bundler_gas_reserve_percentage")); - assert!(debug_str.contains("bundler_signer")); + assert!(debug_str.contains("enable_aa_bundler")); + assert!(debug_str.contains("aa_gas_reserve_percentage")); + assert!(debug_str.contains("aa_bundler_signer")); - // The bundler_signer should show the address, not expose the private key + // The aa_bundler_signer should show the address, not expose the private key // The Debug impl should use the custom formatting - if let Some(signer) = &config.bundler_signer { + if let Some(signer) = &config.aa_bundler_signer { // Should show address, not the full signer struct assert!(debug_str.contains(&signer.address.to_string())); } @@ -113,17 +113,14 @@ mod tests { fn test_builder_config_clone_behavior() { // Test that cloning args doesn't affect config let mut args = OpRbuilderArgs::default(); - args.bundler_pool_url = Some("http://original.com".to_string()); + args.aa_pool_url = Some("http://original.com".to_string()); let config = BuilderConfig::<()>::try_from(args.clone()).unwrap(); // Modify the original args after creating config - args.bundler_pool_url = Some("http://modified.com".to_string()); + args.aa_pool_url = Some("http://modified.com".to_string()); // Config should retain original value - assert_eq!( - config.bundler_pool_url, - Some("http://original.com".to_string()) - ); + assert_eq!(config.aa_pool_url, Some("http://original.com".to_string())); } }