@@ -51,15 +51,6 @@ pub struct CampaignCliArgs {
5151 ) ]
5252 pub pending_timeout : u64 ,
5353
54- /// The number of accounts to generate for each agent (`from_pool` in scenario files)
55- #[ arg(
56- short,
57- long,
58- visible_aliases = [ "na" , "accounts" ] ,
59- default_value_t = 10
60- ) ]
61- pub accounts_per_agent : u64 ,
62-
6354 /// Max number of txs to send in a single json-rpc batch request.
6455 #[ arg(
6556 long = "rpc-batch-size" ,
@@ -159,37 +150,41 @@ pub async fn run_campaign(
159150 . or_else ( || campaign. spam . seed . map ( |s| s. to_string ( ) ) )
160151 . unwrap_or ( load_seedfile ( ) ?) ;
161152
162- // Setup phase. Skip builtin scenarios since they do their own setup at spam time.
163- let provider = args. eth_json_rpc_args . new_rpc_provider ( ) ?;
153+ // Setup phase: run setup for each (stage, mix) with the same derived seed that spam will use.
154+ // This ensures setup creates accounts matching what spam expects.
155+ // Skip builtin scenarios since they do their own setup at spam time.
164156 if !args. skip_setup {
165- for scenario_label in campaign. setup_scenarios ( ) {
166- let scenario = match parse_builtin_reference ( & scenario_label) {
167- Some ( builtin) => SpamScenario :: Builtin (
168- builtin
169- . to_builtin_scenario (
170- & provider,
171- & create_spam_cli_args ( None , & args, CampaignMode :: Tps , 1 , 1 ) ,
172- /* TODO: KLUDGE:
173- - I don't think a `BuiltinScenarioCli` *needs* `rate` or `duration` -- that's for the spammer.
174- - we should use a different interface for `to_builtin_scenario` (replace `SpamCliArgs`)
175- */
176- )
177- . await ?,
178- ) ,
179- None => SpamScenario :: Testfile ( scenario_label. to_owned ( ) ) ,
180- } ;
181- let mut setup_args = args. eth_json_rpc_args . clone ( ) ;
182- setup_args. seed = Some ( base_seed. clone ( ) ) ;
183- let setup_cmd = SetupCommandArgs :: new ( scenario, setup_args) ?;
184- commands:: setup ( db, setup_cmd) . await ?;
157+ for stage in & stages {
158+ let stage_seed = bump_seed ( & base_seed, & stage. name ) ;
159+ for ( mix_idx, mix) in stage. mix . iter ( ) . enumerate ( ) {
160+ if mix. rate == 0 {
161+ continue ;
162+ }
163+ // Skip builtins - they do their own setup during spam
164+ if parse_builtin_reference ( & mix. scenario ) . is_some ( ) {
165+ continue ;
166+ }
167+
168+ let scenario_seed = bump_seed ( & stage_seed, & mix_idx. to_string ( ) ) ;
169+ let scenario = SpamScenario :: Testfile ( mix. scenario . clone ( ) ) ;
170+
171+ let mut setup_args = args. eth_json_rpc_args . clone ( ) ;
172+ setup_args. seed = Some ( scenario_seed) ;
173+ // Ensure accounts_per_agent uses campaign default (10) if not explicitly set
174+ if setup_args. accounts_per_agent . is_none ( ) {
175+ setup_args. accounts_per_agent = Some ( 10 ) ;
176+ }
177+ let setup_cmd = SetupCommandArgs :: new ( scenario, setup_args) ?;
178+ commands:: setup ( db, setup_cmd) . await ?;
179+ }
185180 }
186181 }
187182
188183 let mut run_ids = vec ! [ ] ;
189184
190185 loop {
191186 tokio:: select! {
192- _ = async {
187+ result = async {
193188 for ( stage_idx, stage) in stages. iter( ) . enumerate( ) {
194189 info!(
195190 campaign_id = %campaign_id,
@@ -240,6 +235,8 @@ pub async fn run_campaign(
240235 }
241236 Ok :: <_, CliError >( ( ) )
242237 } => {
238+ // Propagate any error from the campaign execution
239+ result?;
243240 if args. run_forever {
244241 info!( "Campaign {campaign_id} completed. Running again due to --forever flag." ) ;
245242 continue ;
@@ -316,6 +313,8 @@ fn create_spam_cli_args(
316313 spam_mode : CampaignMode ,
317314 spam_rate : u64 ,
318315 spam_duration : u64 ,
316+ skip_setup : bool ,
317+ redeploy : bool ,
319318) -> SpamCliArgs {
320319 SpamCliArgs {
321320 eth_json_rpc_args : ScenarioSendTxsCliArgs {
@@ -337,13 +336,12 @@ fn create_spam_cli_args(
337336 duration : spam_duration,
338337 pending_timeout : args. pending_timeout ,
339338 run_forever : false ,
340- accounts_per_agent : args. accounts_per_agent ,
341339 } ,
342340 ignore_receipts : args. ignore_receipts ,
343341 optimistic_nonces : args. optimistic_nonces ,
344342 gen_report : false ,
345- redeploy : args . redeploy ,
346- skip_setup : true ,
343+ redeploy,
344+ skip_setup,
347345 rpc_batch_size : args. rpc_batch_size ,
348346 spam_timeout : args. spam_timeout ,
349347 }
@@ -383,12 +381,21 @@ async fn execute_stage(
383381 args. eth_json_rpc_args . seed = Some ( scenario_seed. clone ( ) ) ;
384382 debug ! ( "mix {mix_idx} seed: {}" , scenario_seed) ;
385383
384+ // Check if this is a builtin scenario to determine skip_setup/redeploy behavior:
385+ // - Builtins: respect campaign's flags (they do their own setup during spam)
386+ // - Toml scenarios: always skip setup (ran in Phase 1), redeploy not applicable
387+ let is_builtin = parse_builtin_reference ( & mix. scenario ) . is_some ( ) ;
388+ let skip_setup = if is_builtin { args. skip_setup } else { true } ;
389+ let redeploy = if is_builtin { args. redeploy } else { false } ;
390+
386391 let spam_cli_args = create_spam_cli_args (
387392 Some ( mix. scenario . clone ( ) ) ,
388393 & args,
389394 campaign. spam . mode ,
390395 mix. rate ,
391396 stage. duration ,
397+ skip_setup,
398+ redeploy,
392399 ) ;
393400
394401 let spam_scenario = if let Some ( builtin_cli) = parse_builtin_reference ( & mix. scenario ) {
0 commit comments