mina_node_testing/scenarios/
mod.rs

1//! # Mina Testing Scenarios
2//!
3//! This module contains scenario-based tests for the Mina Rust node implementation.
4//! Scenarios are deterministic, ordered sequences of steps that test complex
5//! multi-node blockchain interactions.
6//!
7//! ## Documentation
8//!
9//! For comprehensive documentation on the testing framework and available
10//! scenarios, see:
11//! - **[Scenario Tests](https://o1-labs.github.io/mina-rust/developers/testing/scenario-tests)** - Main scenario testing documentation
12//! - **[Testing Framework](https://o1-labs.github.io/mina-rust/developers/testing/testing-framework)** - Overall testing architecture
13//! - **[Network Connectivity](https://o1-labs.github.io/mina-rust/developers/testing/network-connectivity)** - Network connectivity testing details
14//!
15//! ## Usage
16//!
17//! List available scenarios:
18//! ```bash
19//! cargo run --release --bin mina-node-testing -- scenarios-list
20//! ```
21//!
22//! Run a specific scenario:
23//! ```bash
24//! cargo run --release --bin mina-node-testing -- scenarios-run --name scenario-name
25//! ```
26//!
27//! ## Test Categories
28//!
29//! ### Directory Structure and CI Integration
30//!
31//! The directory structure in this module maps to specific test execution contexts:
32//!
33//! - **`solo_node/`** - Single node scenarios, often testing OCaml interoperability
34//! - **`multi_node/`** - Multi-node Rust network scenarios
35//! - **`p2p/`** - Low-level P2P networking and protocol tests
36//! - **`record_replay/`** - Scenario recording and replay functionality
37//! - **`simulation/`** - Large-scale network simulations
38//!
39//! In CI environments, these directories determine which specialized test binaries
40//! are used for execution, while locally all scenarios are accessible through the
41//! unified `mina-node-testing` CLI using scenario names.
42//!
43//! ### Basic Connectivity Tests
44//!
45//! * Ensure new nodes can discover peers and establish initial connections.
46//! * Test how nodes handle scenarios when they are overwhelmed with too many
47//!   connections or data requests.
48//!
49//! TODO(vlad9486):
50//! - Reconnection: Validate that nodes can reconnect after both intentional and
51//!   unintentional disconnections.
52//! - Handling Latency: Nodes should remain connected and synchronize even under
53//!   high latency conditions.
54//! - Intermittent Connections: Nodes should be resilient to sporadic network
55//!   dropouts and still maintain synchronization.
56//! - Dynamic IP Handling: Nodes with frequently changing IP addresses should
57//!   maintain stable connections.
58
59pub mod multi_node;
60pub mod record_replay;
61pub mod simulation;
62pub mod solo_node;
63
64pub mod p2p;
65
66mod driver;
67pub use driver::*;
68
69pub use crate::cluster::runner::*;
70
71use mina_core::log::{debug, system_time, warn};
72use strum_macros::{EnumIter, EnumString, IntoStaticStr};
73
74use crate::{
75    cluster::{Cluster, ClusterConfig},
76    scenario::{Scenario, ScenarioId, ScenarioStep},
77};
78
79use self::{
80    multi_node::{
81        basic_connectivity_initial_joining::MultiNodeBasicConnectivityInitialJoining,
82        basic_connectivity_peer_discovery::MultiNodeBasicConnectivityPeerDiscovery,
83        connection_discovery::{
84            OCamlToRust, OCamlToRustViaSeed,
85            RustNodeAsSeed as P2pConnectionDiscoveryRustNodeAsSeed, RustToOCaml,
86            RustToOCamlViaSeed,
87        },
88        pubsub_advanced::MultiNodePubsubPropagateBlock,
89        sync_4_block_producers::MultiNodeSync4BlockProducers,
90        vrf_correct_ledgers::MultiNodeVrfGetCorrectLedgers,
91        vrf_correct_slots::MultiNodeVrfGetCorrectSlots,
92        vrf_epoch_bounds_correct_ledgers::MultiNodeVrfEpochBoundsCorrectLedger,
93        vrf_epoch_bounds_evaluation::MultiNodeVrfEpochBoundsEvaluation,
94    },
95    p2p::{
96        basic_connection_handling::{
97            AllNodesConnectionsAreSymmetric, MaxNumberOfPeersIncoming, MaxNumberOfPeersIs1,
98            SeedConnectionsAreSymmetric, SimultaneousConnections,
99        },
100        basic_incoming_connections::{AcceptIncomingConnection, AcceptMultipleIncomingConnections},
101        basic_outgoing_connections::{
102            ConnectToInitialPeers, ConnectToInitialPeersBecomeReady,
103            ConnectToUnavailableInitialPeers, DontConnectToInitialPeerWithSameId,
104            DontConnectToNodeWithSameId, DontConnectToSelfInitialPeer,
105            MakeMultipleOutgoingConnections, MakeOutgoingConnection,
106        },
107        kademlia::KademliaBootstrap,
108        pubsub::P2pReceiveMessage,
109        signaling::P2pSignaling,
110    },
111    record_replay::{
112        block_production::RecordReplayBlockProduction, bootstrap::RecordReplayBootstrap,
113    },
114    simulation::{small::SimulationSmall, small_forever_real_time::SimulationSmallForeverRealTime},
115    solo_node::{
116        basic_connectivity_accept_incoming::SoloNodeBasicConnectivityAcceptIncoming,
117        basic_connectivity_initial_joining::SoloNodeBasicConnectivityInitialJoining,
118        bootstrap::SoloNodeBootstrap, sync_root_snarked_ledger::SoloNodeSyncRootSnarkedLedger,
119        sync_to_genesis::SoloNodeSyncToGenesis,
120        sync_to_genesis_custom::SoloNodeSyncToGenesisCustom,
121    },
122};
123
124#[derive(EnumIter, EnumString, IntoStaticStr, derive_more::From, Clone, Copy)]
125#[strum(serialize_all = "kebab-case")]
126pub enum Scenarios {
127    SoloNodeSyncToGenesis(SoloNodeSyncToGenesis),
128    SoloNodeBootstrap(SoloNodeBootstrap),
129    SoloNodeSyncToGenesisCustom(SoloNodeSyncToGenesisCustom),
130    SoloNodeSyncRootSnarkedLedger(SoloNodeSyncRootSnarkedLedger),
131    SoloNodeBasicConnectivityInitialJoining(SoloNodeBasicConnectivityInitialJoining),
132    SoloNodeBasicConnectivityAcceptIncoming(SoloNodeBasicConnectivityAcceptIncoming),
133    MultiNodeSync4BlockProducers(MultiNodeSync4BlockProducers),
134    MultiNodeVrfGetCorrectLedgers(MultiNodeVrfGetCorrectLedgers),
135    MultiNodeVrfGetCorrectSlots(MultiNodeVrfGetCorrectSlots),
136    MultiNodeVrfEpochBoundsEvaluation(MultiNodeVrfEpochBoundsEvaluation),
137    MultiNodeVrfEpochBoundsCorrectLedger(MultiNodeVrfEpochBoundsCorrectLedger),
138    MultiNodeBasicConnectivityInitialJoining(MultiNodeBasicConnectivityInitialJoining),
139    MultiNodeBasicConnectivityPeerDiscovery(MultiNodeBasicConnectivityPeerDiscovery),
140    SimulationSmall(SimulationSmall),
141    SimulationSmallForeverRealTime(SimulationSmallForeverRealTime),
142    P2pReceiveMessage(P2pReceiveMessage),
143    P2pSignaling(P2pSignaling),
144    P2pConnectionDiscoveryRustNodeAsSeed(P2pConnectionDiscoveryRustNodeAsSeed),
145    MultiNodePubsubPropagateBlock(MultiNodePubsubPropagateBlock),
146    RecordReplayBootstrap(RecordReplayBootstrap),
147    RecordReplayBlockProduction(RecordReplayBlockProduction),
148
149    RustToOCaml(RustToOCaml),
150    OCamlToRust(OCamlToRust),
151    OCamlToRustViaSeed(OCamlToRustViaSeed),
152    RustToOCamlViaSeed(RustToOCamlViaSeed),
153    KademliaBootstrap(KademliaBootstrap),
154    AcceptIncomingConnection(AcceptIncomingConnection),
155    MakeOutgoingConnection(MakeOutgoingConnection),
156    AcceptMultipleIncomingConnections(AcceptMultipleIncomingConnections),
157    MakeMultipleOutgoingConnections(MakeMultipleOutgoingConnections),
158    DontConnectToNodeWithSameId(DontConnectToNodeWithSameId),
159    DontConnectToInitialPeerWithSameId(DontConnectToInitialPeerWithSameId),
160    DontConnectToSelfInitialPeer(DontConnectToSelfInitialPeer),
161    SimultaneousConnections(SimultaneousConnections),
162    ConnectToInitialPeers(ConnectToInitialPeers),
163    ConnectToUnavailableInitialPeers(ConnectToUnavailableInitialPeers),
164    AllNodesConnectionsAreSymmetric(AllNodesConnectionsAreSymmetric),
165    ConnectToInitialPeersBecomeReady(ConnectToInitialPeersBecomeReady),
166    SeedConnectionsAreSymmetric(SeedConnectionsAreSymmetric),
167    MaxNumberOfPeersIncoming(MaxNumberOfPeersIncoming),
168    MaxNumberOfPeersIs1(MaxNumberOfPeersIs1),
169}
170
171impl Scenarios {
172    // Turn off global test
173    pub fn iter() -> impl IntoIterator<Item = Scenarios> {
174        <Self as strum::IntoEnumIterator>::iter().filter(|s| !s.skip())
175    }
176
177    pub fn find_by_name(name: &str) -> Option<Self> {
178        <Self as strum::IntoEnumIterator>::iter().find(|v| v.to_str() == name)
179    }
180
181    fn skip(&self) -> bool {
182        match self {
183            Self::SoloNodeSyncToGenesis(_) => true,
184            Self::SoloNodeSyncToGenesisCustom(_) => true,
185            Self::SoloNodeBasicConnectivityAcceptIncoming(_) => cfg!(feature = "p2p-webrtc"),
186            Self::MultiNodeBasicConnectivityPeerDiscovery(_) => cfg!(feature = "p2p-webrtc"),
187            Self::SimulationSmall(_) => true,
188            Self::SimulationSmallForeverRealTime(_) => true,
189            Self::MultiNodePubsubPropagateBlock(_) => true, // in progress
190            Self::P2pSignaling(_) => !cfg!(feature = "p2p-webrtc"),
191            _ => false,
192        }
193    }
194
195    pub fn id(self) -> ScenarioId {
196        self.into()
197    }
198
199    pub fn to_str(self) -> &'static str {
200        self.into()
201    }
202
203    pub fn parent(self) -> Option<Self> {
204        match self {
205            Self::MultiNodeSync4BlockProducers(_) => Some(SoloNodeSyncToGenesis.into()),
206            Self::MultiNodeVrfGetCorrectLedgers(_) => Some(SoloNodeSyncToGenesisCustom.into()),
207            Self::MultiNodeVrfGetCorrectSlots(_) => Some(SoloNodeSyncToGenesisCustom.into()),
208            Self::MultiNodeVrfEpochBoundsEvaluation(_) => Some(SoloNodeSyncToGenesisCustom.into()),
209            Self::MultiNodeVrfEpochBoundsCorrectLedger(_) => {
210                Some(SoloNodeSyncToGenesisCustom.into())
211            }
212            _ => None,
213        }
214    }
215
216    pub fn parent_id(self) -> Option<ScenarioId> {
217        self.parent().map(Self::id)
218    }
219
220    pub fn description(self) -> &'static str {
221        use documented::Documented;
222        match self {
223            Self::SoloNodeSyncToGenesis(_) => SoloNodeSyncToGenesis::DOCS,
224            Self::SoloNodeBootstrap(_) => SoloNodeBootstrap::DOCS,
225            Self::SoloNodeSyncToGenesisCustom(_) => SoloNodeSyncToGenesis::DOCS,
226            Self::SoloNodeSyncRootSnarkedLedger(_) => SoloNodeSyncRootSnarkedLedger::DOCS,
227            Self::SoloNodeBasicConnectivityInitialJoining(_) => {
228                SoloNodeBasicConnectivityInitialJoining::DOCS
229            }
230            Self::SoloNodeBasicConnectivityAcceptIncoming(_) => {
231                SoloNodeBasicConnectivityAcceptIncoming::DOCS
232            }
233            Self::MultiNodeSync4BlockProducers(_) => MultiNodeSync4BlockProducers::DOCS,
234            Self::MultiNodeVrfGetCorrectLedgers(_) => MultiNodeVrfGetCorrectLedgers::DOCS,
235            Self::MultiNodeVrfGetCorrectSlots(_) => MultiNodeVrfGetCorrectSlots::DOCS,
236            Self::MultiNodeVrfEpochBoundsEvaluation(_) => MultiNodeVrfEpochBoundsEvaluation::DOCS,
237            Self::MultiNodeVrfEpochBoundsCorrectLedger(_) => {
238                MultiNodeVrfEpochBoundsCorrectLedger::DOCS
239            }
240            Self::MultiNodeBasicConnectivityInitialJoining(_) => {
241                MultiNodeBasicConnectivityInitialJoining::DOCS
242            }
243            Self::MultiNodeBasicConnectivityPeerDiscovery(_) => {
244                MultiNodeBasicConnectivityPeerDiscovery::DOCS
245            }
246            Self::SimulationSmall(_) => SimulationSmall::DOCS,
247            Self::SimulationSmallForeverRealTime(_) => SimulationSmallForeverRealTime::DOCS,
248            Self::P2pReceiveMessage(_) => P2pReceiveMessage::DOCS,
249            Self::P2pSignaling(_) => P2pSignaling::DOCS,
250            Self::P2pConnectionDiscoveryRustNodeAsSeed(_) => {
251                P2pConnectionDiscoveryRustNodeAsSeed::DOCS
252            }
253            Self::MultiNodePubsubPropagateBlock(_) => MultiNodePubsubPropagateBlock::DOCS,
254            Self::RecordReplayBootstrap(_) => RecordReplayBootstrap::DOCS,
255            Self::RecordReplayBlockProduction(_) => RecordReplayBlockProduction::DOCS,
256
257            Self::RustToOCaml(_) => RustToOCaml::DOCS,
258            Self::OCamlToRust(_) => OCamlToRust::DOCS,
259            Self::OCamlToRustViaSeed(_) => OCamlToRustViaSeed::DOCS,
260            Self::RustToOCamlViaSeed(_) => RustToOCamlViaSeed::DOCS,
261            Self::KademliaBootstrap(_) => KademliaBootstrap::DOCS,
262            Self::AcceptIncomingConnection(_) => AcceptIncomingConnection::DOCS,
263            Self::MakeOutgoingConnection(_) => MakeOutgoingConnection::DOCS,
264            Self::AcceptMultipleIncomingConnections(_) => AcceptMultipleIncomingConnections::DOCS,
265            Self::MakeMultipleOutgoingConnections(_) => MakeMultipleOutgoingConnections::DOCS,
266            Self::DontConnectToNodeWithSameId(_) => DontConnectToNodeWithSameId::DOCS,
267            Self::DontConnectToInitialPeerWithSameId(_) => DontConnectToInitialPeerWithSameId::DOCS,
268            Self::DontConnectToSelfInitialPeer(_) => DontConnectToSelfInitialPeer::DOCS,
269            Self::SimultaneousConnections(_) => SimultaneousConnections::DOCS,
270            Self::ConnectToInitialPeers(_) => ConnectToInitialPeers::DOCS,
271            Self::ConnectToUnavailableInitialPeers(_) => ConnectToUnavailableInitialPeers::DOCS,
272            Self::AllNodesConnectionsAreSymmetric(_) => AllNodesConnectionsAreSymmetric::DOCS,
273            Self::ConnectToInitialPeersBecomeReady(_) => ConnectToInitialPeersBecomeReady::DOCS,
274            Self::SeedConnectionsAreSymmetric(_) => SeedConnectionsAreSymmetric::DOCS,
275            Self::MaxNumberOfPeersIncoming(_) => MaxNumberOfPeersIncoming::DOCS,
276            Self::MaxNumberOfPeersIs1(_) => MaxNumberOfPeersIs1::DOCS,
277        }
278    }
279
280    pub fn default_cluster_config(self) -> Result<ClusterConfig, anyhow::Error> {
281        let config = ClusterConfig::new(None)
282            .map_err(|err| anyhow::anyhow!("failed to create cluster configuration: {err}"))?;
283
284        match self {
285            Self::P2pSignaling(v) => v.default_cluster_config(config),
286            _ => Ok(config),
287        }
288    }
289
290    pub fn blank_scenario(self) -> Scenario {
291        let mut scenario = Scenario::new(self.id(), self.parent_id());
292        scenario.set_description(self.description().to_owned());
293        scenario.info.nodes = Vec::new();
294
295        scenario
296    }
297
298    async fn run<F>(self, cluster: &mut Cluster, add_step: F)
299    where
300        F: Send + FnMut(&ScenarioStep),
301    {
302        let runner = ClusterRunner::new(cluster, add_step);
303        match self {
304            Self::SoloNodeSyncToGenesis(v) => v.run(runner).await,
305            Self::SoloNodeBootstrap(v) => v.run(runner).await,
306            Self::SoloNodeSyncToGenesisCustom(v) => v.run(runner).await,
307            Self::SoloNodeSyncRootSnarkedLedger(v) => v.run(runner).await,
308            Self::SoloNodeBasicConnectivityInitialJoining(v) => v.run(runner).await,
309            Self::SoloNodeBasicConnectivityAcceptIncoming(v) => v.run(runner).await,
310            Self::MultiNodeSync4BlockProducers(v) => v.run(runner).await,
311            Self::MultiNodeVrfGetCorrectLedgers(v) => v.run(runner).await,
312            Self::MultiNodeVrfGetCorrectSlots(v) => v.run(runner).await,
313            Self::MultiNodeVrfEpochBoundsEvaluation(v) => v.run(runner).await,
314            Self::MultiNodeVrfEpochBoundsCorrectLedger(v) => v.run(runner).await,
315            Self::MultiNodeBasicConnectivityInitialJoining(v) => v.run(runner).await,
316            Self::MultiNodeBasicConnectivityPeerDiscovery(v) => v.run(runner).await,
317            Self::SimulationSmall(v) => v.run(runner).await,
318            Self::SimulationSmallForeverRealTime(v) => v.run(runner).await,
319            Self::P2pReceiveMessage(v) => v.run(runner).await,
320            Self::P2pSignaling(v) => v.run(runner).await,
321            Self::P2pConnectionDiscoveryRustNodeAsSeed(v) => v.run(runner).await,
322            Self::MultiNodePubsubPropagateBlock(v) => v.run(runner).await,
323            Self::RecordReplayBootstrap(v) => v.run(runner).await,
324            Self::RecordReplayBlockProduction(v) => v.run(runner).await,
325
326            Self::RustToOCaml(v) => v.run(runner).await,
327            Self::OCamlToRust(v) => v.run(runner).await,
328            Self::OCamlToRustViaSeed(v) => v.run(runner).await,
329            Self::RustToOCamlViaSeed(v) => v.run(runner).await,
330            Self::KademliaBootstrap(v) => v.run(runner).await,
331            Self::AcceptIncomingConnection(v) => v.run(runner).await,
332            Self::MakeOutgoingConnection(v) => v.run(runner).await,
333            Self::AcceptMultipleIncomingConnections(v) => v.run(runner).await,
334            Self::MakeMultipleOutgoingConnections(v) => v.run(runner).await,
335            Self::DontConnectToNodeWithSameId(v) => v.run(runner).await,
336            Self::DontConnectToInitialPeerWithSameId(v) => v.run(runner).await,
337            Self::DontConnectToSelfInitialPeer(v) => v.run(runner).await,
338            Self::SimultaneousConnections(v) => v.run(runner).await,
339            Self::ConnectToInitialPeers(v) => v.run(runner).await,
340            Self::ConnectToUnavailableInitialPeers(v) => v.run(runner).await,
341            Self::AllNodesConnectionsAreSymmetric(v) => v.run(runner).await,
342            Self::ConnectToInitialPeersBecomeReady(v) => v.run(runner).await,
343            Self::SeedConnectionsAreSymmetric(v) => v.run(runner).await,
344            Self::MaxNumberOfPeersIncoming(v) => v.run(runner).await,
345            Self::MaxNumberOfPeersIs1(v) => v.run(runner).await,
346        }
347    }
348
349    pub async fn run_and_save(self, cluster: &mut Cluster) {
350        struct ScenarioSaveOnExit(Scenario);
351
352        impl Drop for ScenarioSaveOnExit {
353            fn drop(&mut self) {
354                let info = self.0.info.clone();
355                let steps = std::mem::take(&mut self.0.steps);
356                let scenario = Scenario { info, steps };
357
358                debug!(system_time(); "saving scenario({}) before exit...", scenario.info.id);
359                if let Err(err) = scenario.save_sync() {
360                    warn!(system_time();
361                        "failed to save scenario({})! error: {}",
362                        scenario.info.id, err
363                    );
364                }
365            }
366        }
367
368        debug!(system_time(); "run_and_save: {}", self.to_str());
369        let mut scenario = ScenarioSaveOnExit(self.blank_scenario());
370        self.run(cluster, |step| scenario.0.add_step(step.clone()).unwrap())
371            .await;
372        // drop to save it.
373        let _ = scenario;
374    }
375
376    pub async fn run_only(self, cluster: &mut Cluster) {
377        debug!(system_time(); "run_only: {}", self.to_str());
378        self.run(cluster, |_| {}).await
379    }
380
381    async fn build_cluster_and_run_parents(self, config: ClusterConfig) -> Cluster {
382        let mut parents = std::iter::repeat(())
383            .scan(self.parent(), |parent, _| {
384                let cur_parent = parent.take();
385                *parent = cur_parent.and_then(|p| p.parent());
386                cur_parent
387            })
388            .collect::<Vec<_>>();
389
390        let mut cluster = Cluster::new(config);
391        while let Some(scenario) = parents.pop() {
392            scenario.run_only(&mut cluster).await;
393        }
394
395        cluster
396    }
397
398    pub async fn run_and_save_from_scratch(self, config: ClusterConfig) {
399        let mut cluster = self.build_cluster_and_run_parents(config).await;
400        self.run_and_save(&mut cluster).await;
401    }
402
403    pub async fn run_only_from_scratch(self, config: ClusterConfig) {
404        let mut cluster = self.build_cluster_and_run_parents(config).await;
405        self.run_only(&mut cluster).await;
406    }
407}