snarkos_cli/commands/
start.rs

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