Skip to main content

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