/ crates / cli / src / main.rs
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  }