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