snarkos_cli/commands/
start.rs

1// Copyright (c) 2019-2025 Provable Inc.
2// This file is part of the snarkOS library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use snarkos_account::Account;
17use snarkos_display::Display;
18use snarkos_node::{Node, bft::MEMORY_POOL_PORT, router::messages::NodeType};
19use snarkvm::{
20    console::{
21        account::{Address, PrivateKey},
22        algorithms::Hash,
23        network::{CanaryV0, MainnetV0, Network, TestnetV0},
24    },
25    ledger::{
26        block::Block,
27        committee::{Committee, MIN_DELEGATOR_STAKE, MIN_VALIDATOR_STAKE},
28        store::{ConsensusStore, helpers::memory::ConsensusMemory},
29    },
30    prelude::{FromBytes, ToBits, ToBytes},
31    synthesizer::VM,
32    utilities::to_bytes_le,
33};
34
35use aleo_std::StorageMode;
36use anyhow::{Context, Result, bail, ensure};
37use clap::Parser;
38use colored::Colorize;
39use core::str::FromStr;
40use indexmap::IndexMap;
41use rand::{Rng, SeedableRng};
42use rand_chacha::ChaChaRng;
43use serde::{Deserialize, Serialize};
44use std::{
45    net::SocketAddr,
46    path::PathBuf,
47    sync::{Arc, atomic::AtomicBool},
48};
49use tokio::runtime::{self, Runtime};
50
51/// The recommended minimum number of 'open files' limit for a validator.
52/// Validators should be able to handle at least 1000 concurrent connections, each requiring 2 sockets.
53#[cfg(target_family = "unix")]
54const RECOMMENDED_MIN_NOFILES_LIMIT: u64 = 2048;
55
56/// The development mode RNG seed.
57const DEVELOPMENT_MODE_RNG_SEED: u64 = 1234567890u64;
58
59/// The development mode number of genesis committee members.
60const DEVELOPMENT_MODE_NUM_GENESIS_COMMITTEE_MEMBERS: u16 = 4;
61
62/// The CDN base url.
63pub(crate) const CDN_BASE_URL: &str = "https://cdn.provable.com";
64
65/// A mapping of `staker_address` to `(validator_address, withdrawal_address, amount)`.
66#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
67pub struct BondedBalances(IndexMap<String, (String, String, u64)>);
68
69impl FromStr for BondedBalances {
70    type Err = serde_json::Error;
71
72    fn from_str(s: &str) -> Result<Self, Self::Err> {
73        serde_json::from_str(s)
74    }
75}
76
77/// Starts the snarkOS node.
78#[derive(Clone, Debug, Parser)]
79#[command(group(
80    // Ensure at most one node type is specified
81    clap::ArgGroup::new("node_type").required(false).multiple(false)
82))]
83pub struct Start {
84    /// Specify the network ID of this node (0 = mainnet, 1 = testnet, 2 = canary)
85    #[clap(default_value_t=MainnetV0::ID, long = "network", value_parser = clap::value_parser!(u16).range((MainnetV0::ID as i64)..=(CanaryV0::ID as i64)))]
86    pub network: u16,
87
88    /// Start the node as a validator
89    #[clap(long = "validator", group = "node_type")]
90    pub validator: bool,
91    /// Start the node as a prover
92    #[clap(long = "prover", group = "node_type")]
93    pub prover: bool,
94    /// Start the node as a client (default)
95    #[clap(long = "client", group = "node_type")]
96    pub client: bool,
97
98    /// Specify the account private key of the node
99    #[clap(long = "private-key")]
100    pub private_key: Option<String>,
101    /// Specify the path to a file containing the account private key of the node
102    #[clap(long = "private-key-file")]
103    pub private_key_file: Option<PathBuf>,
104
105    /// Specify the IP address and port for the node server
106    #[clap(long = "node")]
107    pub node: Option<SocketAddr>,
108    /// Specify the IP address and port for the BFT
109    #[clap(long = "bft")]
110    pub bft: Option<SocketAddr>,
111    /// Specify the IP address and port of the peer(s) to connect to
112    #[clap(default_value = "", long = "peers")]
113    pub peers: String,
114    /// Specify the IP address and port of the validator(s) to connect to
115    #[clap(default_value = "", long = "validators")]
116    pub validators: String,
117    /// If the flag is set, a validator will allow untrusted peers to connect.
118    /// Client and Prover nodes ignore the flag and always allow untrusted peers
119    /// to connect.
120    #[clap(long = "allow-external-peers")]
121    pub allow_external_peers: bool,
122    /// If the flag is set, a client will periodically evict more external peers
123    #[clap(long = "rotate-external-peers")]
124    pub rotate_external_peers: bool,
125
126    /// Specify the IP address and port for the REST server
127    #[clap(long = "rest")]
128    pub rest: Option<SocketAddr>,
129    /// Specify the requests per second (RPS) rate limit per IP for the REST server
130    #[clap(default_value = "10", long = "rest-rps")]
131    pub rest_rps: u32,
132    /// If the flag is set, the node will not initialize the REST server
133    #[clap(long)]
134    pub norest: bool,
135
136    /// If the flag is set, the node will not render the display
137    #[clap(long)]
138    pub nodisplay: bool,
139    /// Specify the verbosity of the node [options: 0, 1, 2, 3, 4]
140    #[clap(default_value = "1", long = "verbosity")]
141    pub verbosity: u8,
142    /// Specify the path to the file where logs will be stored
143    #[clap(default_value_os_t = std::env::temp_dir().join("snarkos.log"), long = "logfile")]
144    pub logfile: PathBuf,
145
146    /// Enables the metrics exporter
147    #[cfg(feature = "metrics")]
148    #[clap(default_value = "false", long = "metrics")]
149    pub metrics: bool,
150    /// Specify the IP address and port for the metrics exporter
151    #[cfg(feature = "metrics")]
152    #[clap(long = "metrics-ip")]
153    pub metrics_ip: Option<SocketAddr>,
154
155    /// Specify the path to a directory containing the storage database for the ledger. Overrides
156    /// the default path (also for dev).
157    #[clap(long = "storage")]
158    pub storage: Option<PathBuf>,
159    /// Enables the node to prefetch initial blocks from a CDN
160    #[clap(long = "cdn")]
161    pub cdn: Option<String>,
162    /// If the flag is set, the node will not prefetch from a CDN
163    #[clap(long)]
164    pub nocdn: bool,
165
166    /// Enables development mode, specify a unique ID for this node
167    #[clap(long)]
168    pub dev: Option<u16>,
169    /// If development mode is enabled, specify the number of genesis validators (default: 4)
170    #[clap(long)]
171    pub dev_num_validators: Option<u16>,
172    /// If development mode is enabled, specify whether node 0 should generate traffic to drive the network
173    #[clap(default_value = "false", long = "no-dev-txs")]
174    pub no_dev_txs: bool,
175    /// If development mode is enabled, specify the custom bonded balances as a JSON object (default: None)
176    #[clap(long)]
177    pub dev_bonded_balances: Option<BondedBalances>,
178}
179
180impl Start {
181    /// Starts the snarkOS node.
182    pub fn parse(self) -> Result<String> {
183        // Prepare the shutdown flag.
184        let shutdown: Arc<AtomicBool> = Default::default();
185
186        // Initialize the logger.
187        let log_receiver =
188            crate::helpers::initialize_logger(self.verbosity, self.nodisplay, self.logfile.clone(), shutdown.clone());
189        // Initialize the runtime.
190        Self::runtime().block_on(async move {
191            // Error messages.
192            let node_parse_error = || "Failed to parse node arguments";
193            let display_start_error = || "Failed to initialize the display";
194
195            // Clone the configurations.
196            let mut cli = self.clone();
197            // Parse the network.
198            match cli.network {
199                MainnetV0::ID => {
200                    // Parse the node from the configurations.
201                    let node = cli.parse_node::<MainnetV0>(shutdown.clone()).await.with_context(node_parse_error)?;
202                    // If the display is enabled, render the display.
203                    if !cli.nodisplay {
204                        // Initialize the display.
205                        Display::start(node, log_receiver).with_context(display_start_error)?;
206                    }
207                }
208                TestnetV0::ID => {
209                    // Parse the node from the configurations.
210                    let node = cli.parse_node::<TestnetV0>(shutdown.clone()).await.with_context(node_parse_error)?;
211                    // If the display is enabled, render the display.
212                    if !cli.nodisplay {
213                        // Initialize the display.
214                        Display::start(node, log_receiver).with_context(display_start_error)?;
215                    }
216                }
217                CanaryV0::ID => {
218                    // Parse the node from the configurations.
219                    let node = cli.parse_node::<CanaryV0>(shutdown.clone()).await.with_context(node_parse_error)?;
220                    // If the display is enabled, render the display.
221                    if !cli.nodisplay {
222                        // Initialize the display.
223                        Display::start(node, log_receiver).with_context(display_start_error)?;
224                    }
225                }
226                _ => panic!("Invalid network ID specified"),
227            };
228            // Note: Do not move this. The pending await must be here otherwise
229            // other snarkOS commands will not exit.
230            std::future::pending::<()>().await;
231            Ok(String::new())
232        })
233    }
234}
235
236impl Start {
237    /// Returns the initial peer(s) to connect to, from the given configurations.
238    fn parse_trusted_peers(&self) -> Result<Vec<SocketAddr>> {
239        match self.peers.is_empty() {
240            true => Ok(vec![]),
241            false => Ok(self
242                .peers
243                .split(',')
244                .flat_map(|ip| match ip.parse::<SocketAddr>() {
245                    Ok(ip) => Some(ip),
246                    Err(e) => {
247                        eprintln!("The IP supplied to --peers ('{ip}') is malformed: {e}");
248                        None
249                    }
250                })
251                .collect()),
252        }
253    }
254
255    /// Returns the initial validator(s) to connect to, from the given configurations.
256    fn parse_trusted_validators(&self) -> Result<Vec<SocketAddr>> {
257        match self.validators.is_empty() {
258            true => Ok(vec![]),
259            false => Ok(self
260                .validators
261                .split(',')
262                .flat_map(|ip| match ip.parse::<SocketAddr>() {
263                    Ok(ip) => Some(ip),
264                    Err(e) => {
265                        eprintln!("The IP supplied to --validators ('{ip}') is malformed: {e}");
266                        None
267                    }
268                })
269                .collect()),
270        }
271    }
272
273    /// Returns the CDN to prefetch initial blocks from, from the given configurations.
274    fn parse_cdn<N: Network>(&self) -> Option<String> {
275        // Determine if the node type is not declared.
276        let is_no_node_type = !(self.validator || self.prover || self.client);
277
278        // Disable CDN if:
279        //  1. The node is in development mode.
280        //  2. The user has explicitly disabled CDN.
281        //  3. The node is a prover (no need to sync).
282        //  4. The node type is not declared (defaults to client) (no need to sync).
283        if self.dev.is_some() || self.nocdn || self.prover || is_no_node_type {
284            None
285        }
286        // Enable the CDN otherwise.
287        else {
288            // Determine the CDN URL.
289            match &self.cdn {
290                // Use the provided CDN URL if it is not empty.
291                Some(cdn) => match cdn.is_empty() {
292                    true => None,
293                    false => Some(cdn.clone()),
294                },
295                // If no CDN URL is provided, determine the CDN URL based on the network ID.
296                None => match N::ID {
297                    MainnetV0::ID => Some(format!("{CDN_BASE_URL}/v0/blocks/mainnet")),
298                    TestnetV0::ID => Some(format!("{CDN_BASE_URL}/v0/blocks/testnet")),
299                    CanaryV0::ID => Some(format!("{CDN_BASE_URL}/v0/blocks/canary")),
300                    _ => None,
301                },
302            }
303        }
304    }
305
306    /// Read the private key directly from an argument or from a filesystem location,
307    /// returning the Aleo account.
308    fn parse_private_key<N: Network>(&self) -> Result<Account<N>> {
309        match self.dev {
310            None => match (&self.private_key, &self.private_key_file) {
311                // Parse the private key directly.
312                (Some(private_key), None) => Account::from_str(private_key.trim()),
313                // Parse the private key from a file.
314                (None, Some(path)) => {
315                    check_permissions(path)?;
316                    Account::from_str(std::fs::read_to_string(path)?.trim())
317                }
318                // Ensure the private key is provided to the CLI, except for clients or nodes in development mode.
319                (None, None) => match self.client {
320                    true => Account::new(&mut rand::thread_rng()),
321                    false => bail!("Missing the '--private-key' or '--private-key-file' argument"),
322                },
323                // Ensure only one private key flag is provided to the CLI.
324                (Some(_), Some(_)) => {
325                    bail!("Cannot use '--private-key' and '--private-key-file' simultaneously, please use only one")
326                }
327            },
328            Some(dev) => {
329                // Sample the private key of this node.
330                Account::try_from({
331                    // Initialize the (fixed) RNG.
332                    let mut rng = ChaChaRng::seed_from_u64(DEVELOPMENT_MODE_RNG_SEED);
333                    // Iterate through 'dev' address instances to match the account.
334                    for _ in 0..dev {
335                        let _ = PrivateKey::<N>::new(&mut rng)?;
336                    }
337                    let private_key = PrivateKey::<N>::new(&mut rng)?;
338                    println!("🔑 Your development private key for node {dev} is {}.\n", private_key.to_string().bold());
339                    private_key
340                })
341            }
342        }
343    }
344
345    /// Updates the configurations if the node is in development mode.
346    fn parse_development(
347        &mut self,
348        trusted_peers: &mut Vec<SocketAddr>,
349        trusted_validators: &mut Vec<SocketAddr>,
350    ) -> Result<()> {
351        // If `--dev` is set, assume the dev nodes are initialized from 0 to `dev`,
352        // and add each of them to the trusted peers. In addition, set the node IP to `4130 + dev`,
353        // and the REST IP to `3030 + dev`.
354        if let Some(dev) = self.dev {
355            // Add the dev nodes to the trusted peers.
356            if trusted_peers.is_empty() {
357                for i in 0..dev {
358                    trusted_peers.push(SocketAddr::from_str(&format!("127.0.0.1:{}", 4130 + i))?);
359                }
360            }
361            // Add the dev nodes to the trusted validators.
362            if trusted_validators.is_empty() {
363                // To avoid ambiguity, we define the first few nodes to be the trusted validators to connect to.
364                for i in 0..2 {
365                    if i != dev {
366                        trusted_validators.push(SocketAddr::from_str(&format!("127.0.0.1:{}", MEMORY_POOL_PORT + i))?);
367                    }
368                }
369            }
370            // Set the node IP to `4130 + dev`.
371            //
372            // Note: the `node` flag is an option to detect remote devnet testing.
373            if self.node.is_none() {
374                self.node = Some(SocketAddr::from_str(&format!("0.0.0.0:{}", 4130 + dev))?);
375            }
376
377            // If the `norest` flag is not set and the REST IP is not already specified set the REST IP to `3030 + dev`.
378            if !self.norest && self.rest.is_none() {
379                self.rest = Some(SocketAddr::from_str(&format!("0.0.0.0:{}", 3030 + dev)).unwrap());
380            }
381        }
382        Ok(())
383    }
384
385    /// Returns an alternative genesis block if the node is in development mode.
386    /// Otherwise, returns the actual genesis block.
387    fn parse_genesis<N: Network>(&self) -> Result<Block<N>> {
388        if self.dev.is_some() {
389            // Determine the number of genesis committee members.
390            let num_committee_members = match self.dev_num_validators {
391                Some(num_committee_members) => num_committee_members,
392                None => DEVELOPMENT_MODE_NUM_GENESIS_COMMITTEE_MEMBERS,
393            };
394            ensure!(
395                num_committee_members >= DEVELOPMENT_MODE_NUM_GENESIS_COMMITTEE_MEMBERS,
396                "Number of genesis committee members is too low"
397            );
398
399            // Initialize the (fixed) RNG.
400            let mut rng = ChaChaRng::seed_from_u64(DEVELOPMENT_MODE_RNG_SEED);
401            // Initialize the development private keys.
402            let dev_keys =
403                (0..num_committee_members).map(|_| PrivateKey::<N>::new(&mut rng)).collect::<Result<Vec<_>>>()?;
404            // Initialize the development addresses.
405            let development_addresses = dev_keys.iter().map(Address::<N>::try_from).collect::<Result<Vec<_>>>()?;
406
407            // Construct the committee based on the state of the bonded balances.
408            let (committee, bonded_balances) = match &self.dev_bonded_balances {
409                Some(bonded_balances) => {
410                    // Parse the bonded balances.
411                    let bonded_balances = bonded_balances
412                        .0
413                        .iter()
414                        .map(|(staker_address, (validator_address, withdrawal_address, amount))| {
415                            let staker_addr = Address::<N>::from_str(staker_address)?;
416                            let validator_addr = Address::<N>::from_str(validator_address)?;
417                            let withdrawal_addr = Address::<N>::from_str(withdrawal_address)?;
418                            Ok((staker_addr, (validator_addr, withdrawal_addr, *amount)))
419                        })
420                        .collect::<Result<IndexMap<_, _>>>()?;
421
422                    // Construct the committee members.
423                    let mut members = IndexMap::new();
424                    for (staker_address, (validator_address, _, amount)) in bonded_balances.iter() {
425                        // Ensure that the staking amount is sufficient.
426                        match staker_address == validator_address {
427                            true => ensure!(amount >= &MIN_VALIDATOR_STAKE, "Validator stake is too low"),
428                            false => ensure!(amount >= &MIN_DELEGATOR_STAKE, "Delegator stake is too low"),
429                        }
430
431                        // Ensure that the validator address is included in the list of development addresses.
432                        ensure!(
433                            development_addresses.contains(validator_address),
434                            "Validator address {validator_address} is not included in the list of development addresses"
435                        );
436
437                        // Add or update the validator entry in the list of members
438                        members.entry(*validator_address).and_modify(|(stake, _, _)| *stake += amount).or_insert((
439                            *amount,
440                            true,
441                            rng.gen_range(0..100),
442                        ));
443                    }
444                    // Construct the committee.
445                    let committee = Committee::<N>::new(0u64, members)?;
446                    (committee, bonded_balances)
447                }
448                None => {
449                    // Calculate the committee stake per member.
450                    let stake_per_member =
451                        N::STARTING_SUPPLY.saturating_div(2).saturating_div(num_committee_members as u64);
452                    ensure!(stake_per_member >= MIN_VALIDATOR_STAKE, "Committee stake per member is too low");
453
454                    // Construct the committee members and distribute stakes evenly among committee members.
455                    let members = development_addresses
456                        .iter()
457                        .map(|address| (*address, (stake_per_member, true, rng.gen_range(0..100))))
458                        .collect::<IndexMap<_, _>>();
459
460                    // Construct the bonded balances.
461                    // Note: The withdrawal address is set to the staker address.
462                    let bonded_balances = members
463                        .iter()
464                        .map(|(address, (stake, _, _))| (*address, (*address, *address, *stake)))
465                        .collect::<IndexMap<_, _>>();
466                    // Construct the committee.
467                    let committee = Committee::<N>::new(0u64, members)?;
468
469                    (committee, bonded_balances)
470                }
471            };
472
473            // Ensure that the number of committee members is correct.
474            ensure!(
475                committee.members().len() == num_committee_members as usize,
476                "Number of committee members {} does not match the expected number of members {num_committee_members}",
477                committee.members().len()
478            );
479
480            // Calculate the public balance per validator.
481            let remaining_balance = N::STARTING_SUPPLY.saturating_sub(committee.total_stake());
482            let public_balance_per_validator = remaining_balance.saturating_div(num_committee_members as u64);
483
484            // Construct the public balances with fairly equal distribution.
485            let mut public_balances = dev_keys
486                .iter()
487                .map(|private_key| Ok((Address::try_from(private_key)?, public_balance_per_validator)))
488                .collect::<Result<indexmap::IndexMap<_, _>>>()?;
489
490            // If there is some leftover balance, add it to the 0-th validator.
491            let leftover =
492                remaining_balance.saturating_sub(public_balance_per_validator * num_committee_members as u64);
493            if leftover > 0 {
494                let (_, balance) = public_balances.get_index_mut(0).unwrap();
495                *balance += leftover;
496            }
497
498            // Check if the sum of committee stakes and public balances equals the total starting supply.
499            let public_balances_sum: u64 = public_balances.values().copied().sum();
500            if committee.total_stake() + public_balances_sum != N::STARTING_SUPPLY {
501                bail!("Sum of committee stakes and public balances does not equal total starting supply.");
502            }
503
504            // Construct the genesis block.
505            load_or_compute_genesis(dev_keys[0], committee, public_balances, bonded_balances, &mut rng)
506        } else {
507            // If the `dev_num_validators` flag is set, inform the user that it is ignored.
508            if self.dev_num_validators.is_some() {
509                eprintln!("The '--dev-num-validators' flag is ignored because '--dev' is not set");
510            }
511
512            Block::from_bytes_le(N::genesis_bytes())
513        }
514    }
515
516    /// Returns the node type specified in the command-line arguments.
517    /// This will return `NodeType::Client` if no node type was specified by the user.
518    const fn parse_node_type(&self) -> NodeType {
519        if self.validator {
520            NodeType::Validator
521        } else if self.prover {
522            NodeType::Prover
523        } else {
524            NodeType::Client
525        }
526    }
527
528    /// Returns the node type corresponding to the given configurations.
529    #[rustfmt::skip]
530    async fn parse_node<N: Network>(&mut self, shutdown: Arc<AtomicBool>) -> Result<Node<N>> {
531        // Print the welcome.
532        println!("{}", crate::helpers::welcome_message());
533
534        // Check if we are running with the lower coinbase and proof targets. This should only be
535        // allowed in --dev mode and should not be allowed in mainnet mode.
536        if cfg!(feature = "test_network") && self.dev.is_none() {
537            bail!("The 'test_network' feature is enabled, but the '--dev' flag is not set");
538        }
539        if cfg!(feature = "test_network") && N::ID == MainnetV0::ID {
540            bail!("The 'test_network' feature is enabled, but you are trying to use mainnet. This is not supported.");
541        }
542
543        // Parse the trusted peers to connect to.
544        let mut trusted_peers = self.parse_trusted_peers()?;
545        // Parse the trusted validators to connect to.
546        let mut trusted_validators = self.parse_trusted_validators()?;
547        // Parse the development configurations.
548        self.parse_development(&mut trusted_peers, &mut trusted_validators)?;
549
550        // Parse the CDN.
551        let cdn = self.parse_cdn::<N>();
552
553        // Parse the genesis block.
554        let genesis = self.parse_genesis::<N>()?;
555        // Parse the private key of the node.
556        let account = self.parse_private_key::<N>()?;
557        // Parse the node type.
558        let node_type = self.parse_node_type();
559
560        // Parse the node IP.
561        let node_ip = match self.node {
562            Some(node_ip) => node_ip,
563            None => SocketAddr::from_str("0.0.0.0:4130").unwrap(),
564        };
565
566        // Parse the REST IP.
567        let rest_ip = match self.norest {
568            true => None,
569            false => self.rest.or_else(|| Some("0.0.0.0:3030".parse().unwrap())),
570        };
571
572        // If the display is not enabled, render the welcome message.
573        if self.nodisplay {
574            // Print the Aleo address.
575            println!("👛 Your Aleo address is {}.\n", account.address().to_string().bold());
576            // Print the node type and network.
577            println!(
578                "🧭 Starting {} on {} at {}.\n",
579                node_type.description().bold(),
580                N::NAME.bold(),
581                node_ip.to_string().bold()
582            );
583
584            // If the node is running a REST server, print the REST IP and JWT.
585            if node_type.is_validator() {
586                if let Some(rest_ip) = rest_ip {
587                    println!("🌐 Starting the REST server at {}.\n", rest_ip.to_string().bold());
588
589                    if let Ok(jwt_token) = snarkos_node_rest::Claims::new(account.address()).to_jwt_string() {
590                        println!("🔑 Your one-time JWT token is {}\n", jwt_token.dimmed());
591                    }
592                }
593            }
594        }
595
596        // If the node is a validator, check if the open files limit is lower than recommended.
597        #[cfg(target_family = "unix")]
598        if node_type.is_validator() {
599            crate::helpers::check_open_files_limit(RECOMMENDED_MIN_NOFILES_LIMIT);
600        }
601        // Check if the machine meets the minimum requirements for a validator.
602        crate::helpers::check_validator_machine(node_type);
603
604        // Initialize the metrics.
605        #[cfg(feature = "metrics")]
606        if self.metrics {
607            metrics::initialize_metrics(self.metrics_ip);
608        }
609
610        // Initialize the storage mode.
611        let storage_mode = match &self.storage {
612            Some(path) => StorageMode::Custom(path.clone()),
613            None => match self.dev {
614                Some(id) => StorageMode::Development(id),
615                None => StorageMode::Production,
616            },
617        };
618
619        // Determine whether to generate background transactions in dev mode.
620        let dev_txs = match self.dev {
621            Some(_) => !self.no_dev_txs,
622            None => {
623                // If the `no_dev_txs` flag is set, inform the user that it is ignored.
624                if self.no_dev_txs {
625                    eprintln!("The '--no-dev-txs' flag is ignored because '--dev' is not set");
626                }
627                false
628            }
629        };
630
631        // Initialize the node.
632        match node_type {
633            NodeType::Validator => Node::new_validator(node_ip, self.bft, rest_ip, self.rest_rps, account, &trusted_peers, &trusted_validators, genesis, cdn, storage_mode, self.allow_external_peers, dev_txs, shutdown.clone()).await,
634            NodeType::Prover => Node::new_prover(node_ip, account, &trusted_peers, genesis, storage_mode, shutdown.clone()).await,
635            NodeType::Client => Node::new_client(node_ip, rest_ip, self.rest_rps, account, &trusted_peers, genesis, cdn, storage_mode, self.rotate_external_peers, shutdown).await,
636        }
637    }
638
639    /// Returns a runtime for the node.
640    fn runtime() -> Runtime {
641        // Retrieve the number of cores.
642        let num_cores = num_cpus::get();
643
644        // Initialize the number of tokio worker threads, max tokio blocking threads, and rayon cores.
645        // Note: We intentionally set the number of tokio worker threads and number of rayon cores to be
646        // more than the number of physical cores, because the node is expected to be I/O-bound.
647        let (num_tokio_worker_threads, max_tokio_blocking_threads, num_rayon_cores_global) =
648            (2 * num_cores, 512, num_cores);
649
650        // Initialize the parallelization parameters.
651        rayon::ThreadPoolBuilder::new()
652            .stack_size(8 * 1024 * 1024)
653            .num_threads(num_rayon_cores_global)
654            .build_global()
655            .unwrap();
656
657        // Initialize the runtime configuration.
658        runtime::Builder::new_multi_thread()
659            .enable_all()
660            .thread_stack_size(8 * 1024 * 1024)
661            .worker_threads(num_tokio_worker_threads)
662            .max_blocking_threads(max_tokio_blocking_threads)
663            .build()
664            .expect("Failed to initialize a runtime for the router")
665    }
666}
667
668fn check_permissions(path: &PathBuf) -> Result<(), snarkvm::prelude::Error> {
669    #[cfg(target_family = "unix")]
670    {
671        use std::os::unix::fs::PermissionsExt;
672        ensure!(path.exists(), "The file '{:?}' does not exist", path);
673        crate::check_parent_permissions(path)?;
674        let permissions = path.metadata()?.permissions().mode();
675        ensure!(permissions & 0o777 == 0o600, "The file {:?} must be readable only by the owner (0600)", path);
676    }
677    Ok(())
678}
679
680/// Loads or computes the genesis block.
681fn load_or_compute_genesis<N: Network>(
682    genesis_private_key: PrivateKey<N>,
683    committee: Committee<N>,
684    public_balances: indexmap::IndexMap<Address<N>, u64>,
685    bonded_balances: indexmap::IndexMap<Address<N>, (Address<N>, Address<N>, u64)>,
686    rng: &mut ChaChaRng,
687) -> Result<Block<N>> {
688    // Construct the preimage.
689    let mut preimage = Vec::new();
690
691    // Input the network ID.
692    preimage.extend(&N::ID.to_le_bytes());
693    // Input the genesis coinbase target.
694    preimage.extend(&to_bytes_le![N::GENESIS_COINBASE_TARGET]?);
695    // Input the genesis proof target.
696    preimage.extend(&to_bytes_le![N::GENESIS_PROOF_TARGET]?);
697
698    // Input the genesis private key, committee, and public balances.
699    preimage.extend(genesis_private_key.to_bytes_le()?);
700    preimage.extend(committee.to_bytes_le()?);
701    preimage.extend(&to_bytes_le![public_balances.iter().collect::<Vec<(_, _)>>()]?);
702    preimage.extend(&to_bytes_le![
703        bonded_balances
704            .iter()
705            .flat_map(|(staker, (validator, withdrawal, amount))| to_bytes_le![staker, validator, withdrawal, amount])
706            .collect::<Vec<_>>()
707    ]?);
708
709    // Input the parameters' metadata based on network
710    match N::ID {
711        snarkvm::console::network::MainnetV0::ID => {
712            preimage.extend(snarkvm::parameters::mainnet::BondValidatorVerifier::METADATA.as_bytes());
713            preimage.extend(snarkvm::parameters::mainnet::BondPublicVerifier::METADATA.as_bytes());
714            preimage.extend(snarkvm::parameters::mainnet::UnbondPublicVerifier::METADATA.as_bytes());
715            preimage.extend(snarkvm::parameters::mainnet::ClaimUnbondPublicVerifier::METADATA.as_bytes());
716            preimage.extend(snarkvm::parameters::mainnet::SetValidatorStateVerifier::METADATA.as_bytes());
717            preimage.extend(snarkvm::parameters::mainnet::TransferPrivateVerifier::METADATA.as_bytes());
718            preimage.extend(snarkvm::parameters::mainnet::TransferPublicVerifier::METADATA.as_bytes());
719            preimage.extend(snarkvm::parameters::mainnet::TransferPrivateToPublicVerifier::METADATA.as_bytes());
720            preimage.extend(snarkvm::parameters::mainnet::TransferPublicToPrivateVerifier::METADATA.as_bytes());
721            preimage.extend(snarkvm::parameters::mainnet::FeePrivateVerifier::METADATA.as_bytes());
722            preimage.extend(snarkvm::parameters::mainnet::FeePublicVerifier::METADATA.as_bytes());
723            preimage.extend(snarkvm::parameters::mainnet::InclusionVerifier::METADATA.as_bytes());
724        }
725        snarkvm::console::network::TestnetV0::ID => {
726            preimage.extend(snarkvm::parameters::testnet::BondValidatorVerifier::METADATA.as_bytes());
727            preimage.extend(snarkvm::parameters::testnet::BondPublicVerifier::METADATA.as_bytes());
728            preimage.extend(snarkvm::parameters::testnet::UnbondPublicVerifier::METADATA.as_bytes());
729            preimage.extend(snarkvm::parameters::testnet::ClaimUnbondPublicVerifier::METADATA.as_bytes());
730            preimage.extend(snarkvm::parameters::testnet::SetValidatorStateVerifier::METADATA.as_bytes());
731            preimage.extend(snarkvm::parameters::testnet::TransferPrivateVerifier::METADATA.as_bytes());
732            preimage.extend(snarkvm::parameters::testnet::TransferPublicVerifier::METADATA.as_bytes());
733            preimage.extend(snarkvm::parameters::testnet::TransferPrivateToPublicVerifier::METADATA.as_bytes());
734            preimage.extend(snarkvm::parameters::testnet::TransferPublicToPrivateVerifier::METADATA.as_bytes());
735            preimage.extend(snarkvm::parameters::testnet::FeePrivateVerifier::METADATA.as_bytes());
736            preimage.extend(snarkvm::parameters::testnet::FeePublicVerifier::METADATA.as_bytes());
737            preimage.extend(snarkvm::parameters::testnet::InclusionVerifier::METADATA.as_bytes());
738        }
739        snarkvm::console::network::CanaryV0::ID => {
740            preimage.extend(snarkvm::parameters::canary::BondValidatorVerifier::METADATA.as_bytes());
741            preimage.extend(snarkvm::parameters::canary::BondPublicVerifier::METADATA.as_bytes());
742            preimage.extend(snarkvm::parameters::canary::UnbondPublicVerifier::METADATA.as_bytes());
743            preimage.extend(snarkvm::parameters::canary::ClaimUnbondPublicVerifier::METADATA.as_bytes());
744            preimage.extend(snarkvm::parameters::canary::SetValidatorStateVerifier::METADATA.as_bytes());
745            preimage.extend(snarkvm::parameters::canary::TransferPrivateVerifier::METADATA.as_bytes());
746            preimage.extend(snarkvm::parameters::canary::TransferPublicVerifier::METADATA.as_bytes());
747            preimage.extend(snarkvm::parameters::canary::TransferPrivateToPublicVerifier::METADATA.as_bytes());
748            preimage.extend(snarkvm::parameters::canary::TransferPublicToPrivateVerifier::METADATA.as_bytes());
749            preimage.extend(snarkvm::parameters::canary::FeePrivateVerifier::METADATA.as_bytes());
750            preimage.extend(snarkvm::parameters::canary::FeePublicVerifier::METADATA.as_bytes());
751            preimage.extend(snarkvm::parameters::canary::InclusionVerifier::METADATA.as_bytes());
752        }
753        _ => {
754            // Unrecognized Network ID
755            bail!("Unrecognized Network ID: {}", N::ID);
756        }
757    }
758
759    // Initialize the hasher.
760    let hasher = snarkvm::console::algorithms::BHP256::<N>::setup("aleo.dev.block")?;
761    // Compute the hash.
762    // NOTE: this is a fast-to-compute but *IMPERFECT* identifier for the genesis block;
763    //       to know the actual genesis block hash, you need to compute the block itself.
764    let hash = hasher.hash(&preimage.to_bits_le())?.to_string();
765
766    // A closure to load the block.
767    let load_block = |file_path| -> Result<Block<N>> {
768        // Attempts to load the genesis block file locally.
769        let buffer = std::fs::read(file_path)?;
770        // Return the genesis block.
771        Block::from_bytes_le(&buffer)
772    };
773
774    // Construct the file path.
775    let file_path = std::env::temp_dir().join(hash);
776    // Check if the genesis block exists.
777    if file_path.exists() {
778        // If the block loads successfully, return it.
779        if let Ok(block) = load_block(&file_path) {
780            return Ok(block);
781        }
782    }
783
784    /* Otherwise, compute the genesis block and store it. */
785
786    // Initialize a new VM.
787    let vm = VM::from(ConsensusStore::<N, ConsensusMemory<N>>::open(StorageMode::new_test(None))?)?;
788    // Initialize the genesis block.
789    let block = vm.genesis_quorum(&genesis_private_key, committee, public_balances, bonded_balances, rng)?;
790    // Write the genesis block to the file.
791    std::fs::write(&file_path, block.to_bytes_le()?)?;
792    // Return the genesis block.
793    Ok(block)
794}
795
796#[cfg(test)]
797mod tests {
798    use super::*;
799    use crate::commands::{CLI, Command};
800    use snarkvm::prelude::MainnetV0;
801
802    type CurrentNetwork = MainnetV0;
803
804    #[test]
805    fn test_parse_trusted_peers() {
806        let config = Start::try_parse_from(["snarkos", "--peers", ""].iter()).unwrap();
807        assert!(config.parse_trusted_peers().is_ok());
808        assert!(config.parse_trusted_peers().unwrap().is_empty());
809
810        let config = Start::try_parse_from(["snarkos", "--peers", "1.2.3.4:5"].iter()).unwrap();
811        assert!(config.parse_trusted_peers().is_ok());
812        assert_eq!(config.parse_trusted_peers().unwrap(), vec![SocketAddr::from_str("1.2.3.4:5").unwrap()]);
813
814        let config = Start::try_parse_from(["snarkos", "--peers", "1.2.3.4:5,6.7.8.9:0"].iter()).unwrap();
815        assert!(config.parse_trusted_peers().is_ok());
816        assert_eq!(config.parse_trusted_peers().unwrap(), vec![
817            SocketAddr::from_str("1.2.3.4:5").unwrap(),
818            SocketAddr::from_str("6.7.8.9:0").unwrap()
819        ]);
820    }
821
822    #[test]
823    fn test_parse_trusted_validators() {
824        let config = Start::try_parse_from(["snarkos", "--validators", ""].iter()).unwrap();
825        assert!(config.parse_trusted_validators().is_ok());
826        assert!(config.parse_trusted_validators().unwrap().is_empty());
827
828        let config = Start::try_parse_from(["snarkos", "--validators", "1.2.3.4:5"].iter()).unwrap();
829        assert!(config.parse_trusted_validators().is_ok());
830        assert_eq!(config.parse_trusted_validators().unwrap(), vec![SocketAddr::from_str("1.2.3.4:5").unwrap()]);
831
832        let config = Start::try_parse_from(["snarkos", "--validators", "1.2.3.4:5,6.7.8.9:0"].iter()).unwrap();
833        assert!(config.parse_trusted_validators().is_ok());
834        assert_eq!(config.parse_trusted_validators().unwrap(), vec![
835            SocketAddr::from_str("1.2.3.4:5").unwrap(),
836            SocketAddr::from_str("6.7.8.9:0").unwrap()
837        ]);
838    }
839
840    #[test]
841    fn test_parse_cdn() {
842        // Validator (Prod)
843        let config = Start::try_parse_from(["snarkos", "--validator", "--private-key", "aleo1xx"].iter()).unwrap();
844        assert!(config.parse_cdn::<CurrentNetwork>().is_some());
845        let config =
846            Start::try_parse_from(["snarkos", "--validator", "--private-key", "aleo1xx", "--cdn", "url"].iter())
847                .unwrap();
848        assert!(config.parse_cdn::<CurrentNetwork>().is_some());
849        let config =
850            Start::try_parse_from(["snarkos", "--validator", "--private-key", "aleo1xx", "--cdn", ""].iter()).unwrap();
851        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
852
853        // Validator (Dev)
854        let config =
855            Start::try_parse_from(["snarkos", "--dev", "0", "--validator", "--private-key", "aleo1xx"].iter()).unwrap();
856        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
857        let config = Start::try_parse_from(
858            ["snarkos", "--dev", "0", "--validator", "--private-key", "aleo1xx", "--cdn", "url"].iter(),
859        )
860        .unwrap();
861        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
862        let config = Start::try_parse_from(
863            ["snarkos", "--dev", "0", "--validator", "--private-key", "aleo1xx", "--cdn", ""].iter(),
864        )
865        .unwrap();
866        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
867
868        // Prover (Prod)
869        let config = Start::try_parse_from(["snarkos", "--prover", "--private-key", "aleo1xx"].iter()).unwrap();
870        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
871        let config =
872            Start::try_parse_from(["snarkos", "--prover", "--private-key", "aleo1xx", "--cdn", "url"].iter()).unwrap();
873        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
874        let config =
875            Start::try_parse_from(["snarkos", "--prover", "--private-key", "aleo1xx", "--cdn", ""].iter()).unwrap();
876        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
877
878        // Prover (Dev)
879        let config =
880            Start::try_parse_from(["snarkos", "--dev", "0", "--prover", "--private-key", "aleo1xx"].iter()).unwrap();
881        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
882        let config = Start::try_parse_from(
883            ["snarkos", "--dev", "0", "--prover", "--private-key", "aleo1xx", "--cdn", "url"].iter(),
884        )
885        .unwrap();
886        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
887        let config = Start::try_parse_from(
888            ["snarkos", "--dev", "0", "--prover", "--private-key", "aleo1xx", "--cdn", ""].iter(),
889        )
890        .unwrap();
891        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
892
893        // Client (Prod)
894        let config = Start::try_parse_from(["snarkos", "--client", "--private-key", "aleo1xx"].iter()).unwrap();
895        assert!(config.parse_cdn::<CurrentNetwork>().is_some());
896        let config =
897            Start::try_parse_from(["snarkos", "--client", "--private-key", "aleo1xx", "--cdn", "url"].iter()).unwrap();
898        assert!(config.parse_cdn::<CurrentNetwork>().is_some());
899        let config =
900            Start::try_parse_from(["snarkos", "--client", "--private-key", "aleo1xx", "--cdn", ""].iter()).unwrap();
901        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
902
903        // Client (Dev)
904        let config =
905            Start::try_parse_from(["snarkos", "--dev", "0", "--client", "--private-key", "aleo1xx"].iter()).unwrap();
906        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
907        let config = Start::try_parse_from(
908            ["snarkos", "--dev", "0", "--client", "--private-key", "aleo1xx", "--cdn", "url"].iter(),
909        )
910        .unwrap();
911        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
912        let config = Start::try_parse_from(
913            ["snarkos", "--dev", "0", "--client", "--private-key", "aleo1xx", "--cdn", ""].iter(),
914        )
915        .unwrap();
916        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
917
918        // Default (Prod)
919        let config = Start::try_parse_from(["snarkos"].iter()).unwrap();
920        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
921        let config = Start::try_parse_from(["snarkos", "--cdn", "url"].iter()).unwrap();
922        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
923        let config = Start::try_parse_from(["snarkos", "--cdn", ""].iter()).unwrap();
924        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
925
926        // Default (Dev)
927        let config = Start::try_parse_from(["snarkos", "--dev", "0"].iter()).unwrap();
928        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
929        let config = Start::try_parse_from(["snarkos", "--dev", "0", "--cdn", "url"].iter()).unwrap();
930        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
931        let config = Start::try_parse_from(["snarkos", "--dev", "0", "--cdn", ""].iter()).unwrap();
932        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
933    }
934
935    #[test]
936    fn test_parse_development_and_genesis() {
937        let prod_genesis = Block::from_bytes_le(CurrentNetwork::genesis_bytes()).unwrap();
938
939        let mut trusted_peers = vec![];
940        let mut trusted_validators = vec![];
941        let mut config = Start::try_parse_from(["snarkos"].iter()).unwrap();
942        config.parse_development(&mut trusted_peers, &mut trusted_validators).unwrap();
943        let candidate_genesis = config.parse_genesis::<CurrentNetwork>().unwrap();
944        assert_eq!(trusted_peers.len(), 0);
945        assert_eq!(trusted_validators.len(), 0);
946        assert_eq!(candidate_genesis, prod_genesis);
947
948        let _config = Start::try_parse_from(["snarkos", "--dev", ""].iter()).unwrap_err();
949
950        let mut trusted_peers = vec![];
951        let mut trusted_validators = vec![];
952        let mut config = Start::try_parse_from(["snarkos", "--dev", "1"].iter()).unwrap();
953        config.parse_development(&mut trusted_peers, &mut trusted_validators).unwrap();
954        assert_eq!(config.rest, Some(SocketAddr::from_str("0.0.0.0:3031").unwrap()));
955
956        let mut trusted_peers = vec![];
957        let mut trusted_validators = vec![];
958        let mut config = Start::try_parse_from(["snarkos", "--dev", "1", "--rest", "127.0.0.1:8080"].iter()).unwrap();
959        config.parse_development(&mut trusted_peers, &mut trusted_validators).unwrap();
960        assert_eq!(config.rest, Some(SocketAddr::from_str("127.0.0.1:8080").unwrap()));
961
962        let mut trusted_peers = vec![];
963        let mut trusted_validators = vec![];
964        let mut config = Start::try_parse_from(["snarkos", "--dev", "1", "--norest"].iter()).unwrap();
965        config.parse_development(&mut trusted_peers, &mut trusted_validators).unwrap();
966        assert!(config.rest.is_none());
967
968        let mut trusted_peers = vec![];
969        let mut trusted_validators = vec![];
970        let mut config = Start::try_parse_from(["snarkos", "--dev", "0"].iter()).unwrap();
971        config.parse_development(&mut trusted_peers, &mut trusted_validators).unwrap();
972        let expected_genesis = config.parse_genesis::<CurrentNetwork>().unwrap();
973        assert_eq!(config.node, Some(SocketAddr::from_str("0.0.0.0:4130").unwrap()));
974        assert_eq!(config.rest, Some(SocketAddr::from_str("0.0.0.0:3030").unwrap()));
975        assert_eq!(trusted_peers.len(), 0);
976        assert_eq!(trusted_validators.len(), 1);
977        assert!(!config.validator);
978        assert!(!config.prover);
979        assert!(!config.client);
980        assert_ne!(expected_genesis, prod_genesis);
981
982        let mut trusted_peers = vec![];
983        let mut trusted_validators = vec![];
984        let mut config =
985            Start::try_parse_from(["snarkos", "--dev", "1", "--validator", "--private-key", ""].iter()).unwrap();
986        config.parse_development(&mut trusted_peers, &mut trusted_validators).unwrap();
987        let genesis = config.parse_genesis::<CurrentNetwork>().unwrap();
988        assert_eq!(config.node, Some(SocketAddr::from_str("0.0.0.0:4131").unwrap()));
989        assert_eq!(config.rest, Some(SocketAddr::from_str("0.0.0.0:3031").unwrap()));
990        assert_eq!(trusted_peers.len(), 1);
991        assert_eq!(trusted_validators.len(), 1);
992        assert!(config.validator);
993        assert!(!config.prover);
994        assert!(!config.client);
995        assert_eq!(genesis, expected_genesis);
996
997        let mut trusted_peers = vec![];
998        let mut trusted_validators = vec![];
999        let mut config =
1000            Start::try_parse_from(["snarkos", "--dev", "2", "--prover", "--private-key", ""].iter()).unwrap();
1001        config.parse_development(&mut trusted_peers, &mut trusted_validators).unwrap();
1002        let genesis = config.parse_genesis::<CurrentNetwork>().unwrap();
1003        assert_eq!(config.node, Some(SocketAddr::from_str("0.0.0.0:4132").unwrap()));
1004        assert_eq!(config.rest, Some(SocketAddr::from_str("0.0.0.0:3032").unwrap()));
1005        assert_eq!(trusted_peers.len(), 2);
1006        assert_eq!(trusted_validators.len(), 2);
1007        assert!(!config.validator);
1008        assert!(config.prover);
1009        assert!(!config.client);
1010        assert_eq!(genesis, expected_genesis);
1011
1012        let mut trusted_peers = vec![];
1013        let mut trusted_validators = vec![];
1014        let mut config =
1015            Start::try_parse_from(["snarkos", "--dev", "3", "--client", "--private-key", ""].iter()).unwrap();
1016        config.parse_development(&mut trusted_peers, &mut trusted_validators).unwrap();
1017        let genesis = config.parse_genesis::<CurrentNetwork>().unwrap();
1018        assert_eq!(config.node, Some(SocketAddr::from_str("0.0.0.0:4133").unwrap()));
1019        assert_eq!(config.rest, Some(SocketAddr::from_str("0.0.0.0:3033").unwrap()));
1020        assert_eq!(trusted_peers.len(), 3);
1021        assert_eq!(trusted_validators.len(), 2);
1022        assert!(!config.validator);
1023        assert!(!config.prover);
1024        assert!(config.client);
1025        assert_eq!(genesis, expected_genesis);
1026    }
1027
1028    #[test]
1029    fn clap_snarkos_start() {
1030        let arg_vec = vec![
1031            "snarkos",
1032            "start",
1033            "--nodisplay",
1034            "--dev",
1035            "2",
1036            "--validator",
1037            "--private-key",
1038            "PRIVATE_KEY",
1039            "--cdn",
1040            "CDN",
1041            "--peers",
1042            "IP1,IP2,IP3",
1043            "--validators",
1044            "IP1,IP2,IP3",
1045            "--rest",
1046            "127.0.0.1:3030",
1047        ];
1048        let cli = CLI::parse_from(arg_vec);
1049
1050        if let Command::Start(start) = cli.command {
1051            assert!(start.nodisplay);
1052            assert_eq!(start.dev, Some(2));
1053            assert!(start.validator);
1054            assert_eq!(start.private_key.as_deref(), Some("PRIVATE_KEY"));
1055            assert_eq!(start.cdn, Some("CDN".to_string()));
1056            assert_eq!(start.rest, Some("127.0.0.1:3030".parse().unwrap()));
1057            assert_eq!(start.network, 0);
1058            assert_eq!(start.peers, "IP1,IP2,IP3");
1059            assert_eq!(start.validators, "IP1,IP2,IP3");
1060        } else {
1061            panic!("Unexpected result of clap parsing!");
1062        }
1063    }
1064}