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