mina_node_testing/
main.rs1use clap::Parser;
26
27use mina_node_testing::{
28 cluster::{Cluster, ClusterConfig},
29 exit_with_error,
30 scenario::Scenario,
31 scenarios::Scenarios,
32 server, setup,
33};
34use node::p2p::webrtc::Host;
35
36pub type CommandError = anyhow::Error;
37
38#[derive(Debug, clap::Parser)]
39#[command(name = "mina-testing", about = "Mina Testing Cli")]
40pub struct MinaTestingCli {
41 #[command(subcommand)]
42 pub command: Command,
43}
44
45#[derive(Debug, clap::Subcommand)]
46pub enum Command {
47 Server(CommandServer),
48
49 ScenariosGenerate(CommandScenariosGenerate),
50 ScenariosRun(CommandScenariosRun),
51 ScenariosList(CommandScenariosList),
52}
53
54#[derive(Debug, clap::Args)]
55pub struct CommandServer {
56 #[arg(long, short, default_value = "127.0.0.1")]
57 pub host: Host,
58
59 #[arg(long, short, default_value = "11000")]
60 pub port: u16,
61 #[arg(long, short)]
62 pub ssl_port: Option<u16>,
63}
64
65#[derive(Debug, clap::Args)]
66pub struct CommandScenariosGenerate {
67 #[arg(long, short)]
68 pub name: Option<String>,
69 #[arg(long, short)]
70 pub use_debugger: bool,
71 #[arg(long, short)]
72 pub webrtc: bool,
73 #[arg(long, short = 'o', default_value = "stdout", value_enum)]
74 pub output: OutputFormat,
75}
76
77#[derive(Debug, Clone, clap::ValueEnum)]
78pub enum OutputFormat {
79 Stdout,
80 Json,
81}
82
83#[derive(Debug, clap::Args)]
85pub struct CommandScenariosRun {
86 #[arg(long, short)]
90 pub name: String,
91}
92
93#[derive(Debug, clap::Args)]
94pub struct CommandScenariosList {}
95
96impl Command {
97 pub fn run(self) -> Result<(), crate::CommandError> {
98 let rt = setup();
99 let _rt_guard = rt.enter();
100
101 let (shutdown_tx, shutdown_rx) = mina_core::channels::oneshot::channel();
102 let mut shutdown_tx = Some(shutdown_tx);
103
104 ctrlc::set_handler(move || match shutdown_tx.take() {
105 Some(tx) => {
106 let _ = tx.send(());
107 }
108 None => {
109 std::process::exit(1);
110 }
111 })
112 .expect("Error setting Ctrl-C handler");
113
114 match self {
115 Self::Server(args) => {
116 server(rt, args.host, args.port, args.ssl_port);
117 Ok(())
118 }
119 Self::ScenariosGenerate(cmd) => {
120 #[cfg(feature = "scenario-generators")]
121 {
122 let output_format = cmd.output.clone();
123 let run_scenario = |scenario: Scenarios| -> Result<_, anyhow::Error> {
124 let mut config = scenario.default_cluster_config()?;
125 if cmd.use_debugger {
126 config.use_debugger();
127 }
128 if cmd.webrtc {
129 config.set_all_rust_to_rust_use_webrtc();
130 }
131 Ok((scenario, config))
132 };
133 let fut = async move {
134 if let Some(name) = cmd.name {
135 if let Some(scenario) = Scenarios::find_by_name(&name) {
136 let (scenario, config) = run_scenario(scenario)?;
137 match output_format {
138 OutputFormat::Json => {
139 scenario.run_and_save_from_scratch(config).await
140 }
141 OutputFormat::Stdout => {
142 scenario.run_only_from_scratch(config).await
143 }
144 }
145 } else {
146 anyhow::bail!("no such scenario: \"{name}\"");
147 }
148 } else {
149 for scenario in Scenarios::iter() {
150 let (scenario, config) = run_scenario(scenario)?;
151 match output_format {
152 OutputFormat::Json => {
153 scenario.run_and_save_from_scratch(config).await
154 }
155 OutputFormat::Stdout => {
156 scenario.run_only_from_scratch(config).await
157 }
158 }
159 }
160 }
161 Ok(())
162 };
163 rt.block_on(async {
164 tokio::select! {
165 res = fut => res,
166 _ = shutdown_rx => {
167 anyhow::bail!("Received ctrl-c signal! shutting down...");
168 }
169 }
170 })
171 }
172 #[cfg(not(feature = "scenario-generators"))]
173 Err("binary not compiled with `scenario-generators` feature"
174 .to_owned()
175 .into())
176 }
177 Self::ScenariosRun(cmd) => {
178 let mut config = ClusterConfig::new(None).map_err(|err| {
179 anyhow::anyhow!("failed to create cluster configuration: {err}")
180 })?;
181 config.set_replay();
182
183 let id = cmd.name.parse()?;
184 let fut = async move {
185 let mut cluster = Cluster::new(config);
186 cluster.start(Scenario::load(&id).await?).await?;
187 cluster.exec_to_end().await?;
188 for (node_id, node) in cluster.nodes_iter() {
189 let Some(best_tip) = node.state().transition_frontier.best_tip() else {
190 continue;
191 };
192
193 eprintln!(
194 "[node_status] node_{node_id} {} - {} [{}]",
195 best_tip.height(),
196 best_tip.hash(),
197 best_tip.producer()
198 );
199 }
200 Ok(())
201 };
202 rt.block_on(async {
203 tokio::select! {
204 res = fut => res,
205 _ = shutdown_rx => {
206 anyhow::bail!("Received ctrl-c signal! shutting down...");
207 }
208 }
209 })
210 }
211 Self::ScenariosList(_) => {
212 println!("Available scenarios:");
213 for scenario in Scenarios::iter() {
214 println!(" {}", scenario.to_str())
215 }
216 Ok(())
217 }
218 }
219 }
220}
221
222pub fn main() {
223 match MinaTestingCli::parse().command.run() {
224 Ok(_) => {}
225 Err(err) => exit_with_error(err),
226 }
227}