openmina_node_testing/
main.rs

1use clap::Parser;
2
3use node::p2p::webrtc::Host;
4use openmina_node_testing::{
5    cluster::{Cluster, ClusterConfig},
6    exit_with_error,
7    scenario::Scenario,
8    scenarios::Scenarios,
9    server, setup,
10};
11
12pub type CommandError = anyhow::Error;
13
14#[derive(Debug, clap::Parser)]
15#[command(name = "openmina-testing", about = "Openmina Testing Cli")]
16pub struct OpenminaTestingCli {
17    #[command(subcommand)]
18    pub command: Command,
19}
20
21#[derive(Debug, clap::Subcommand)]
22pub enum Command {
23    Server(CommandServer),
24
25    ScenariosGenerate(CommandScenariosGenerate),
26    ScenariosRun(CommandScenariosRun),
27}
28
29#[derive(Debug, clap::Args)]
30pub struct CommandServer {
31    #[arg(long, short, default_value = "127.0.0.1")]
32    pub host: Host,
33
34    #[arg(long, short, default_value = "11000")]
35    pub port: u16,
36    #[arg(long, short)]
37    pub ssl_port: Option<u16>,
38}
39
40#[derive(Debug, clap::Args)]
41pub struct CommandScenariosGenerate {
42    #[arg(long, short)]
43    pub name: Option<String>,
44    #[arg(long, short)]
45    pub use_debugger: bool,
46    #[arg(long, short)]
47    pub webrtc: bool,
48}
49
50/// Run scenario located at `res/scenarios`.
51#[derive(Debug, clap::Args)]
52pub struct CommandScenariosRun {
53    /// Name of the scenario.
54    ///
55    /// Must match filename in `res/scenarios` (without an extension).
56    #[arg(long, short)]
57    pub name: String,
58}
59
60impl Command {
61    pub fn run(self) -> Result<(), crate::CommandError> {
62        let rt = setup();
63        let _rt_guard = rt.enter();
64
65        let (shutdown_tx, shutdown_rx) = openmina_core::channels::oneshot::channel();
66        let mut shutdown_tx = Some(shutdown_tx);
67
68        ctrlc::set_handler(move || match shutdown_tx.take() {
69            Some(tx) => {
70                let _ = tx.send(());
71            }
72            None => {
73                std::process::exit(1);
74            }
75        })
76        .expect("Error setting Ctrl-C handler");
77
78        match self {
79            Self::Server(args) => {
80                server(rt, args.host, args.port, args.ssl_port);
81                Ok(())
82            }
83            Self::ScenariosGenerate(cmd) => {
84                #[cfg(feature = "scenario-generators")]
85                {
86                    let run_scenario = |scenario: Scenarios| -> Result<_, anyhow::Error> {
87                        let mut config = scenario.default_cluster_config()?;
88                        if cmd.use_debugger {
89                            config.use_debugger();
90                        }
91                        if cmd.webrtc {
92                            config.set_all_rust_to_rust_use_webrtc();
93                        }
94                        Ok(scenario.run_only_from_scratch(config))
95                    };
96                    let fut = async move {
97                        if let Some(name) = cmd.name {
98                            if let Some(scenario) = Scenarios::find_by_name(&name) {
99                                run_scenario(scenario)?.await;
100                            } else {
101                                anyhow::bail!("no such scenario: \"{name}\"");
102                            }
103                        } else {
104                            for scenario in Scenarios::iter() {
105                                run_scenario(scenario)?.await;
106                            }
107                        }
108                        Ok(())
109                    };
110                    rt.block_on(async {
111                        tokio::select! {
112                            res = fut => res,
113                            _ = shutdown_rx => {
114                                anyhow::bail!("Received ctrl-c signal! shutting down...");
115                            }
116                        }
117                    })
118                }
119                #[cfg(not(feature = "scenario-generators"))]
120                Err("binary not compiled with `scenario-generators` feature"
121                    .to_owned()
122                    .into())
123            }
124            Self::ScenariosRun(cmd) => {
125                let mut config = ClusterConfig::new(None).map_err(|err| {
126                    anyhow::anyhow!("failed to create cluster configuration: {err}")
127                })?;
128                config.set_replay();
129
130                let id = cmd.name.parse()?;
131                let fut = async move {
132                    let mut cluster = Cluster::new(config);
133                    cluster.start(Scenario::load(&id).await?).await?;
134                    cluster.exec_to_end().await?;
135                    for (node_id, node) in cluster.nodes_iter() {
136                        let Some(best_tip) = node.state().transition_frontier.best_tip() else {
137                            continue;
138                        };
139
140                        eprintln!(
141                            "[node_status] node_{node_id} {} - {} [{}]",
142                            best_tip.height(),
143                            best_tip.hash(),
144                            best_tip.producer()
145                        );
146                    }
147                    Ok(())
148                };
149                rt.block_on(async {
150                    tokio::select! {
151                        res = fut => res,
152                        _ = shutdown_rx => {
153                            anyhow::bail!("Received ctrl-c signal! shutting down...");
154                        }
155                    }
156                })
157            }
158        }
159    }
160}
161
162pub fn main() {
163    match OpenminaTestingCli::parse().command.run() {
164        Ok(_) => {}
165        Err(err) => exit_with_error(err),
166    }
167}