mina_node_testing/scenarios/solo_node/
bootstrap.rs

1use crate::{
2    hosts,
3    node::RustNodeTestingConfig,
4    scenario::{ListenerNode, ScenarioStep},
5    scenarios::ClusterRunner,
6};
7use mina_core::constants::constraint_constants;
8use node::transition_frontier::sync::TransitionFrontierSyncState;
9use redux::Instant;
10use std::time::Duration;
11
12/// Set up single Rust node and bootstrap snarked ledger, bootstrap ledger and blocks.
13///
14/// 1. Node will connect to replayer.
15/// 2. Observe that stacking ledger is synchronized.
16/// 3. Observe that next epoch ledger is synchronized.
17/// 4. Continue until transition frontier is synchronized.
18#[derive(documented::Documented, Default, Clone, Copy)]
19pub struct SoloNodeBootstrap;
20
21// TODO(tizoc): this is ugly, do a cleaner conversion or figure out a better way.
22// This test will fail if we don't start with this as the initial time because
23// the time validation for the first block will reject it.
24fn first_block_slot_timestamp_nanos(config: &RustNodeTestingConfig) -> u64 {
25    let first_block_global_slot = 2000000; // Update if replay changes
26    let protocol_constants = config.genesis.protocol_constants().unwrap();
27    let genesis_timestamp_ms = protocol_constants.genesis_state_timestamp.0.as_u64();
28    let milliseconds_per_slot = constraint_constants().block_window_duration_ms;
29    let first_block_global_slot_delta_ms = first_block_global_slot * milliseconds_per_slot;
30
31    // Convert to nanos
32    genesis_timestamp_ms
33        .checked_add(first_block_global_slot_delta_ms)
34        .unwrap()
35        .checked_mul(1_000_000)
36        .unwrap()
37}
38
39impl SoloNodeBootstrap {
40    pub async fn run(self, mut runner: ClusterRunner<'_>) {
41        std::env::set_var("MINA_DISCOVERY_FILTER_ADDR", "true");
42        use self::TransitionFrontierSyncState::*;
43
44        const TIMEOUT: Duration = Duration::from_secs(60 * 60);
45
46        let replayer = hosts::replayer();
47
48        let mut config = RustNodeTestingConfig::devnet_default();
49
50        config.initial_time = redux::Timestamp::new(first_block_slot_timestamp_nanos(&config));
51
52        let node_id =
53            runner.add_rust_node(config.initial_peers(vec![ListenerNode::Custom(replayer)]));
54        eprintln!("launch Mina Rust node with default configuration, id: {node_id}");
55
56        let mut timeout = TIMEOUT;
57        let mut last_time = Instant::now();
58        loop {
59            if runner
60                .wait_for_pending_events_with_timeout(Duration::from_secs(1))
61                .await
62            {
63                let steps = runner
64                    .pending_events(true)
65                    .flat_map(|(node_id, _, events)| {
66                        events.map(move |(_, event)| ScenarioStep::Event {
67                            node_id,
68                            event: event.to_string(),
69                        })
70                    })
71                    .collect::<Vec<_>>();
72
73                for step in steps {
74                    runner.exec_step(step).await.unwrap();
75                }
76            }
77
78            runner
79                .exec_step(ScenarioStep::CheckTimeouts { node_id })
80                .await
81                .unwrap();
82
83            let new = Instant::now();
84            let elapsed = new - last_time;
85            last_time = new;
86
87            match timeout.checked_sub(elapsed) {
88                Some(new_timeout) => timeout = new_timeout,
89                None => panic!("timeout"),
90            }
91
92            runner
93                .exec_step(ScenarioStep::AdvanceNodeTime {
94                    node_id,
95                    by_nanos: elapsed.as_nanos() as _,
96                })
97                .await
98                .unwrap();
99
100            let this = runner.node(node_id).unwrap();
101            let best_chain = &this.state().transition_frontier.best_chain;
102            let sync_state = &this.state().transition_frontier.sync;
103            if let Synced { time } = &sync_state {
104                if best_chain.len() > 1 {
105                    eprintln!(
106                        "node: {node_id}, is synced at {time:?}, total elapsed {:?}",
107                        TIMEOUT - timeout
108                    );
109
110                    break;
111                }
112            }
113        }
114    }
115}