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