main.rs
1 /// CLI interface for adnet-testbots 2 use adnet_testbot::{Bot, BotContext, ExecutionContext, Identity, IdentityGenerator, Wallet}; 3 use adnet_testbot_distributed::{Coordinator, Worker}; 4 use adnet_testbot_metrics::{EventRecorder, MetricsAggregator}; 5 use adnet_testbot_roles::{GeneralUserBot, TraderBot}; 6 use adnet_testbot_scenarios::ScenarioRunner; 7 use clap::{Parser, Subcommand}; 8 use tracing_subscriber; 9 10 #[derive(Parser)] 11 #[command(name = "adnet-testbots")] 12 #[command(version = "0.1.0")] 13 #[command(about = "Production-grade bot testing infrastructure for Alpha/Delta protocol")] 14 struct Cli { 15 #[command(subcommand)] 16 command: Commands, 17 } 18 19 #[derive(Subcommand)] 20 enum Commands { 21 /// Run a scenario 22 Run { 23 /// Scenario name 24 scenario: String, 25 26 /// Run in distributed mode 27 #[arg(long)] 28 distributed: bool, 29 30 /// Duration limit in seconds 31 #[arg(long)] 32 duration: Option<u64>, 33 34 /// Number of bots 35 #[arg(long, default_value = "10")] 36 bots: usize, 37 }, 38 39 /// Start coordinator server 40 Coordinator { 41 /// Bind address 42 #[arg(long, default_value = "0.0.0.0:50051")] 43 bind: String, 44 }, 45 46 /// Start worker daemon 47 Worker { 48 /// Coordinator address 49 #[arg(long)] 50 coordinator: String, 51 52 /// Maximum number of bots 53 #[arg(long, default_value = "100")] 54 max_bots: u32, 55 56 /// Worker ID (auto-generated if not provided) 57 #[arg(long)] 58 worker_id: Option<String>, 59 }, 60 61 /// Show status 62 Status { 63 /// Show worker details 64 #[arg(long)] 65 show_workers: bool, 66 }, 67 68 /// Run a simple test 69 Test { 70 /// Test type: "simple-transfer", "identity", "wallet" 71 test_type: String, 72 }, 73 } 74 75 #[tokio::main] 76 async fn main() -> anyhow::Result<()> { 77 tracing_subscriber::fmt() 78 .with_target(false) 79 .with_level(true) 80 .init(); 81 82 let cli = Cli::parse(); 83 84 match cli.command { 85 Commands::Run { 86 scenario, 87 distributed, 88 duration, 89 bots, 90 } => { 91 run_scenario(&scenario, distributed, duration, bots).await?; 92 } 93 94 Commands::Coordinator { bind } => { 95 println!("๐ Starting coordinator on: {}", bind); 96 let coordinator = Coordinator::new(); 97 coordinator.serve(bind).await?; 98 } 99 100 Commands::Worker { 101 coordinator, 102 max_bots, 103 worker_id, 104 } => { 105 let worker_id = worker_id.unwrap_or_else(|| { 106 format!("worker-{}", uuid::Uuid::new_v4().to_string()[..8].to_string()) 107 }); 108 109 println!( 110 "๐ค Starting worker: {} (coordinator: {}, max_bots: {})", 111 worker_id, coordinator, max_bots 112 ); 113 114 let worker = Worker::new(worker_id, coordinator, max_bots); 115 worker.run().await?; 116 } 117 118 Commands::Status { show_workers } => { 119 show_status(show_workers).await?; 120 } 121 122 Commands::Test { test_type } => { 123 run_test(&test_type).await?; 124 } 125 } 126 127 Ok(()) 128 } 129 130 async fn run_scenario( 131 scenario: &str, 132 distributed: bool, 133 duration: Option<u64>, 134 bot_count: usize, 135 ) -> anyhow::Result<()> { 136 println!("๐ Running scenario: {}", scenario); 137 println!(" Mode: {}", if distributed { "distributed" } else { "local" }); 138 println!(" Bots: {}", bot_count); 139 140 if let Some(d) = duration { 141 println!(" Duration: {}s", d); 142 } 143 144 // For Phase 1, run a simple local scenario 145 if !distributed { 146 run_local_scenario(scenario, bot_count).await?; 147 } else { 148 println!("โ ๏ธ Distributed mode not fully implemented in Phase 1"); 149 println!(" Use 'coordinator' and 'worker' commands to set up cluster first"); 150 } 151 152 Ok(()) 153 } 154 155 async fn run_local_scenario(scenario: &str, bot_count: usize) -> anyhow::Result<()> { 156 println!("\n๐๏ธ Setting up local scenario..."); 157 158 // Create metrics infrastructure 159 let recorder = EventRecorder::new(); 160 let aggregator = MetricsAggregator::new(); 161 162 // Generate bot identities 163 println!("๐ Generating {} bot identities...", bot_count); 164 let generator = IdentityGenerator::new(); 165 let mut bots: Vec<Box<dyn Bot>> = Vec::new(); 166 167 for i in 0..bot_count { 168 let bot_id = format!("bot-{}", i); 169 let identity = generator.generate(bot_id.clone())?; 170 171 // Create bot based on scenario 172 let bot: Box<dyn Bot> = match scenario { 173 "alpha-transfer" | "simple-transfer" => { 174 Box::new(GeneralUserBot::new(bot_id)) 175 } 176 "delta-trade" | "spot-trade" => { 177 Box::new(TraderBot::new(bot_id)) 178 } 179 _ => { 180 // Default to general user 181 Box::new(GeneralUserBot::new(bot_id)) 182 } 183 }; 184 185 bots.push(bot); 186 } 187 188 println!("โ Created {} bots", bots.len()); 189 190 // Setup bots 191 println!("\nโ๏ธ Setting up bots..."); 192 for (i, bot) in bots.iter_mut().enumerate() { 193 let identity = generator.generate(format!("bot-{}", i))?; 194 let wallet = Wallet::new(format!("bot-{}", i)); 195 196 let execution_context = ExecutionContext::new( 197 format!("bot-{}", i), 198 "general_user".to_string(), 199 adnet_testbot::context::NetworkEndpoints { 200 alphaos_rest: "http://localhost:3030".to_string(), 201 deltaos_rest: "http://localhost:3031".to_string(), 202 adnet_unified: "http://localhost:3000".to_string(), 203 }, 204 ); 205 206 let context = BotContext::new(execution_context, identity, wallet); 207 bot.setup(&context).await?; 208 } 209 210 println!("โ All bots ready"); 211 212 // Execute behaviors 213 println!("\n๐ฌ Executing scenario: {}", scenario); 214 215 let start_time = std::time::Instant::now(); 216 217 for (i, bot) in bots.iter_mut().enumerate() { 218 let behavior_id = match scenario { 219 "alpha-transfer" | "simple-transfer" => "transfer", 220 "delta-trade" | "spot-trade" => "spot_trade", 221 _ => "default", 222 }; 223 224 match bot.execute_behavior(behavior_id).await { 225 Ok(result) => { 226 if result.success { 227 print!("."); 228 } else { 229 print!("F"); 230 } 231 } 232 Err(_) => { 233 print!("E"); 234 } 235 } 236 237 if (i + 1) % 50 == 0 { 238 println!(" {}/{}", i + 1, bots.len()); 239 } 240 } 241 242 println!(); 243 244 let elapsed = start_time.elapsed(); 245 246 // Teardown 247 println!("\n๐งน Cleaning up..."); 248 for bot in bots.iter_mut() { 249 let _ = bot.teardown().await; 250 } 251 252 // Print summary 253 println!("\n๐ Summary:"); 254 println!(" Total bots: {}", bot_count); 255 println!(" Duration: {:.2}s", elapsed.as_secs_f64()); 256 println!(" Ops/bot: 1"); 257 println!(" Total ops: {}", bot_count); 258 println!(" TPS: {:.2}", bot_count as f64 / elapsed.as_secs_f64()); 259 260 println!("\nโ Scenario complete"); 261 262 Ok(()) 263 } 264 265 async fn show_status(show_workers: bool) -> anyhow::Result<()> { 266 println!("๐ adnet-testbots Status\n"); 267 268 // TODO: Query coordinator for real status 269 println!("Coordinator: Not connected"); 270 println!("Workers: 0 active, 0 down"); 271 println!("Total bots: 0"); 272 println!("TPS: 0.00"); 273 274 if show_workers { 275 println!("\n๐ผ Worker Details:"); 276 println!(" No workers registered"); 277 } 278 279 Ok(()) 280 } 281 282 async fn run_test(test_type: &str) -> anyhow::Result<()> { 283 println!("๐งช Running test: {}\n", test_type); 284 285 match test_type { 286 "identity" => { 287 println!("Testing identity generation..."); 288 let generator = IdentityGenerator::new(); 289 let identity = generator.generate("test-bot".to_string())?; 290 291 println!("โ Identity created:"); 292 println!(" ID: {}", identity.id); 293 println!(" Alpha: {}", identity.alpha_address); 294 println!(" Delta: {}", identity.delta_address); 295 println!(" Can sign: {}", identity.can_sign()); 296 } 297 298 "wallet" => { 299 println!("Testing wallet operations..."); 300 let mut wallet = Wallet::new("test-bot".to_string()); 301 302 use adnet_testbot::{Balance, Token}; 303 304 wallet.credit(Token::AX, Balance::new(1000))?; 305 println!("โ Credited 1000 AX"); 306 307 wallet.debit(Token::AX, Balance::new(300))?; 308 println!("โ Debited 300 AX"); 309 310 println!(" Final balance: {} AX", wallet.balance(&Token::AX)); 311 } 312 313 "simple-transfer" => { 314 println!("Testing simple bot lifecycle..."); 315 let mut bot = GeneralUserBot::new("test-bot".to_string()); 316 317 let generator = IdentityGenerator::new(); 318 let identity = generator.generate("test-bot".to_string())?; 319 let wallet = Wallet::new("test-bot".to_string()); 320 321 let execution_context = ExecutionContext::new( 322 "test-bot".to_string(), 323 "general_user".to_string(), 324 adnet_testbot::context::NetworkEndpoints { 325 alphaos_rest: "http://localhost:3030".to_string(), 326 deltaos_rest: "http://localhost:3031".to_string(), 327 adnet_unified: "http://localhost:3000".to_string(), 328 }, 329 ); 330 331 let context = BotContext::new(execution_context, identity, wallet); 332 333 bot.setup(&context).await?; 334 println!("โ Bot setup complete"); 335 336 let result = bot.execute_behavior("transfer").await?; 337 println!("โ Behavior executed: {}", result.message); 338 339 bot.teardown().await?; 340 println!("โ Bot teardown complete"); 341 } 342 343 _ => { 344 println!("โ Unknown test type: {}", test_type); 345 println!(" Available: identity, wallet, simple-transfer"); 346 } 347 } 348 349 println!("\nโ Test complete"); 350 351 Ok(()) 352 }