snarkos_cli/commands/
start.rs

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