snarkos_cli/commands/
start.rs

1// Copyright 2024 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://blocks.aleo.org";
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}/mainnet/v0")),
288                    TestnetV0::ID => Some(format!("{CDN_BASE_URL}/testnet/v0")),
289                    CanaryV0::ID => Some(format!("{CDN_BASE_URL}/canary/v0")),
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, self.dev, &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 => StorageMode::from(self.dev),
600        };
601
602        // Determine whether to generate background transactions in dev mode.
603        let dev_txs = match self.dev {
604            Some(_) => !self.no_dev_txs,
605            None => {
606                // If the `no_dev_txs` flag is set, inform the user that it is ignored.
607                if self.no_dev_txs {
608                    eprintln!("The '--no-dev-txs' flag is ignored because '--dev' is not set");
609                }
610                false
611            }
612        };
613
614        // Initialize the node.
615        match node_type {
616            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,
617            NodeType::Prover => Node::new_prover(node_ip, account, &trusted_peers, genesis, storage_mode, shutdown.clone()).await,
618            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,
619        }
620    }
621
622    /// Returns a runtime for the node.
623    fn runtime() -> Runtime {
624        // Retrieve the number of cores.
625        let num_cores = num_cpus::get();
626
627        // Initialize the number of tokio worker threads, max tokio blocking threads, and rayon cores.
628        // Note: We intentionally set the number of tokio worker threads and number of rayon cores to be
629        // more than the number of physical cores, because the node is expected to be I/O-bound.
630        let (num_tokio_worker_threads, max_tokio_blocking_threads, num_rayon_cores_global) =
631            (2 * num_cores, 512, num_cores);
632
633        // Initialize the parallelization parameters.
634        rayon::ThreadPoolBuilder::new()
635            .stack_size(8 * 1024 * 1024)
636            .num_threads(num_rayon_cores_global)
637            .build_global()
638            .unwrap();
639
640        // Initialize the runtime configuration.
641        runtime::Builder::new_multi_thread()
642            .enable_all()
643            .thread_stack_size(8 * 1024 * 1024)
644            .worker_threads(num_tokio_worker_threads)
645            .max_blocking_threads(max_tokio_blocking_threads)
646            .build()
647            .expect("Failed to initialize a runtime for the router")
648    }
649}
650
651fn check_permissions(path: &PathBuf) -> Result<(), snarkvm::prelude::Error> {
652    #[cfg(target_family = "unix")]
653    {
654        use std::os::unix::fs::PermissionsExt;
655        ensure!(path.exists(), "The file '{:?}' does not exist", path);
656        crate::check_parent_permissions(path)?;
657        let permissions = path.metadata()?.permissions().mode();
658        ensure!(permissions & 0o777 == 0o600, "The file {:?} must be readable only by the owner (0600)", path);
659    }
660    Ok(())
661}
662
663/// Loads or computes the genesis block.
664fn load_or_compute_genesis<N: Network>(
665    genesis_private_key: PrivateKey<N>,
666    committee: Committee<N>,
667    public_balances: indexmap::IndexMap<Address<N>, u64>,
668    bonded_balances: indexmap::IndexMap<Address<N>, (Address<N>, Address<N>, u64)>,
669    dev_id: Option<u16>,
670    rng: &mut ChaChaRng,
671) -> Result<Block<N>> {
672    // Construct the preimage.
673    let mut preimage = Vec::new();
674
675    // Input the network ID.
676    preimage.extend(&N::ID.to_le_bytes());
677    // Input the genesis coinbase target.
678    preimage.extend(&to_bytes_le![N::GENESIS_COINBASE_TARGET]?);
679    // Input the genesis proof target.
680    preimage.extend(&to_bytes_le![N::GENESIS_PROOF_TARGET]?);
681
682    // Input the genesis private key, committee, and public balances.
683    preimage.extend(genesis_private_key.to_bytes_le()?);
684    preimage.extend(committee.to_bytes_le()?);
685    preimage.extend(&to_bytes_le![public_balances.iter().collect::<Vec<(_, _)>>()]?);
686    preimage.extend(&to_bytes_le![
687        bonded_balances
688            .iter()
689            .flat_map(|(staker, (validator, withdrawal, amount))| to_bytes_le![staker, validator, withdrawal, amount])
690            .collect::<Vec<_>>()
691    ]?);
692
693    // Input the parameters' metadata based on network
694    match N::ID {
695        snarkvm::console::network::MainnetV0::ID => {
696            preimage.extend(snarkvm::parameters::mainnet::BondValidatorVerifier::METADATA.as_bytes());
697            preimage.extend(snarkvm::parameters::mainnet::BondPublicVerifier::METADATA.as_bytes());
698            preimage.extend(snarkvm::parameters::mainnet::UnbondPublicVerifier::METADATA.as_bytes());
699            preimage.extend(snarkvm::parameters::mainnet::ClaimUnbondPublicVerifier::METADATA.as_bytes());
700            preimage.extend(snarkvm::parameters::mainnet::SetValidatorStateVerifier::METADATA.as_bytes());
701            preimage.extend(snarkvm::parameters::mainnet::TransferPrivateVerifier::METADATA.as_bytes());
702            preimage.extend(snarkvm::parameters::mainnet::TransferPublicVerifier::METADATA.as_bytes());
703            preimage.extend(snarkvm::parameters::mainnet::TransferPrivateToPublicVerifier::METADATA.as_bytes());
704            preimage.extend(snarkvm::parameters::mainnet::TransferPublicToPrivateVerifier::METADATA.as_bytes());
705            preimage.extend(snarkvm::parameters::mainnet::FeePrivateVerifier::METADATA.as_bytes());
706            preimage.extend(snarkvm::parameters::mainnet::FeePublicVerifier::METADATA.as_bytes());
707            preimage.extend(snarkvm::parameters::mainnet::InclusionVerifier::METADATA.as_bytes());
708        }
709        snarkvm::console::network::TestnetV0::ID => {
710            preimage.extend(snarkvm::parameters::testnet::BondValidatorVerifier::METADATA.as_bytes());
711            preimage.extend(snarkvm::parameters::testnet::BondPublicVerifier::METADATA.as_bytes());
712            preimage.extend(snarkvm::parameters::testnet::UnbondPublicVerifier::METADATA.as_bytes());
713            preimage.extend(snarkvm::parameters::testnet::ClaimUnbondPublicVerifier::METADATA.as_bytes());
714            preimage.extend(snarkvm::parameters::testnet::SetValidatorStateVerifier::METADATA.as_bytes());
715            preimage.extend(snarkvm::parameters::testnet::TransferPrivateVerifier::METADATA.as_bytes());
716            preimage.extend(snarkvm::parameters::testnet::TransferPublicVerifier::METADATA.as_bytes());
717            preimage.extend(snarkvm::parameters::testnet::TransferPrivateToPublicVerifier::METADATA.as_bytes());
718            preimage.extend(snarkvm::parameters::testnet::TransferPublicToPrivateVerifier::METADATA.as_bytes());
719            preimage.extend(snarkvm::parameters::testnet::FeePrivateVerifier::METADATA.as_bytes());
720            preimage.extend(snarkvm::parameters::testnet::FeePublicVerifier::METADATA.as_bytes());
721            preimage.extend(snarkvm::parameters::testnet::InclusionVerifier::METADATA.as_bytes());
722        }
723        snarkvm::console::network::CanaryV0::ID => {
724            preimage.extend(snarkvm::parameters::canary::BondValidatorVerifier::METADATA.as_bytes());
725            preimage.extend(snarkvm::parameters::canary::BondPublicVerifier::METADATA.as_bytes());
726            preimage.extend(snarkvm::parameters::canary::UnbondPublicVerifier::METADATA.as_bytes());
727            preimage.extend(snarkvm::parameters::canary::ClaimUnbondPublicVerifier::METADATA.as_bytes());
728            preimage.extend(snarkvm::parameters::canary::SetValidatorStateVerifier::METADATA.as_bytes());
729            preimage.extend(snarkvm::parameters::canary::TransferPrivateVerifier::METADATA.as_bytes());
730            preimage.extend(snarkvm::parameters::canary::TransferPublicVerifier::METADATA.as_bytes());
731            preimage.extend(snarkvm::parameters::canary::TransferPrivateToPublicVerifier::METADATA.as_bytes());
732            preimage.extend(snarkvm::parameters::canary::TransferPublicToPrivateVerifier::METADATA.as_bytes());
733            preimage.extend(snarkvm::parameters::canary::FeePrivateVerifier::METADATA.as_bytes());
734            preimage.extend(snarkvm::parameters::canary::FeePublicVerifier::METADATA.as_bytes());
735            preimage.extend(snarkvm::parameters::canary::InclusionVerifier::METADATA.as_bytes());
736        }
737        _ => {
738            // Unrecognized Network ID
739            bail!("Unrecognized Network ID: {}", N::ID);
740        }
741    }
742
743    // Initialize the hasher.
744    let hasher = snarkvm::console::algorithms::BHP256::<N>::setup("aleo.dev.block")?;
745    // Compute the hash.
746    // NOTE: this is a fast-to-compute but *IMPERFECT* identifier for the genesis block;
747    //       to know the actual genesis block hash, you need to compute the block itself.
748    let hash = hasher.hash(&preimage.to_bits_le())?.to_string();
749
750    // A closure to load the block.
751    let load_block = |file_path| -> Result<Block<N>> {
752        // Attempts to load the genesis block file locally.
753        let buffer = std::fs::read(file_path)?;
754        // Return the genesis block.
755        Block::from_bytes_le(&buffer)
756    };
757
758    // Construct the file path.
759    let file_path = std::env::temp_dir().join(hash);
760    // Check if the genesis block exists.
761    if file_path.exists() {
762        // If the block loads successfully, return it.
763        if let Ok(block) = load_block(&file_path) {
764            return Ok(block);
765        }
766    }
767
768    /* Otherwise, compute the genesis block and store it. */
769
770    // Initialize a new VM.
771    let vm = VM::from(ConsensusStore::<N, ConsensusMemory<N>>::open(dev_id)?)?;
772    // Initialize the genesis block.
773    let block = vm.genesis_quorum(&genesis_private_key, committee, public_balances, bonded_balances, rng)?;
774    // Write the genesis block to the file.
775    std::fs::write(&file_path, block.to_bytes_le()?)?;
776    // Return the genesis block.
777    Ok(block)
778}
779
780#[cfg(test)]
781mod tests {
782    use super::*;
783    use crate::commands::{CLI, Command};
784    use snarkvm::prelude::MainnetV0;
785
786    type CurrentNetwork = MainnetV0;
787
788    #[test]
789    fn test_parse_trusted_peers() {
790        let config = Start::try_parse_from(["snarkos", "--peers", ""].iter()).unwrap();
791        assert!(config.parse_trusted_peers().is_ok());
792        assert!(config.parse_trusted_peers().unwrap().is_empty());
793
794        let config = Start::try_parse_from(["snarkos", "--peers", "1.2.3.4:5"].iter()).unwrap();
795        assert!(config.parse_trusted_peers().is_ok());
796        assert_eq!(config.parse_trusted_peers().unwrap(), vec![SocketAddr::from_str("1.2.3.4:5").unwrap()]);
797
798        let config = Start::try_parse_from(["snarkos", "--peers", "1.2.3.4:5,6.7.8.9:0"].iter()).unwrap();
799        assert!(config.parse_trusted_peers().is_ok());
800        assert_eq!(config.parse_trusted_peers().unwrap(), vec![
801            SocketAddr::from_str("1.2.3.4:5").unwrap(),
802            SocketAddr::from_str("6.7.8.9:0").unwrap()
803        ]);
804    }
805
806    #[test]
807    fn test_parse_trusted_validators() {
808        let config = Start::try_parse_from(["snarkos", "--validators", ""].iter()).unwrap();
809        assert!(config.parse_trusted_validators().is_ok());
810        assert!(config.parse_trusted_validators().unwrap().is_empty());
811
812        let config = Start::try_parse_from(["snarkos", "--validators", "1.2.3.4:5"].iter()).unwrap();
813        assert!(config.parse_trusted_validators().is_ok());
814        assert_eq!(config.parse_trusted_validators().unwrap(), vec![SocketAddr::from_str("1.2.3.4:5").unwrap()]);
815
816        let config = Start::try_parse_from(["snarkos", "--validators", "1.2.3.4:5,6.7.8.9:0"].iter()).unwrap();
817        assert!(config.parse_trusted_validators().is_ok());
818        assert_eq!(config.parse_trusted_validators().unwrap(), vec![
819            SocketAddr::from_str("1.2.3.4:5").unwrap(),
820            SocketAddr::from_str("6.7.8.9:0").unwrap()
821        ]);
822    }
823
824    #[test]
825    fn test_parse_cdn() {
826        // Validator (Prod)
827        let config = Start::try_parse_from(["snarkos", "--validator", "--private-key", "aleo1xx"].iter()).unwrap();
828        assert!(config.parse_cdn::<CurrentNetwork>().is_some());
829        let config =
830            Start::try_parse_from(["snarkos", "--validator", "--private-key", "aleo1xx", "--cdn", "url"].iter())
831                .unwrap();
832        assert!(config.parse_cdn::<CurrentNetwork>().is_some());
833        let config =
834            Start::try_parse_from(["snarkos", "--validator", "--private-key", "aleo1xx", "--cdn", ""].iter()).unwrap();
835        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
836
837        // Validator (Dev)
838        let config =
839            Start::try_parse_from(["snarkos", "--dev", "0", "--validator", "--private-key", "aleo1xx"].iter()).unwrap();
840        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
841        let config = Start::try_parse_from(
842            ["snarkos", "--dev", "0", "--validator", "--private-key", "aleo1xx", "--cdn", "url"].iter(),
843        )
844        .unwrap();
845        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
846        let config = Start::try_parse_from(
847            ["snarkos", "--dev", "0", "--validator", "--private-key", "aleo1xx", "--cdn", ""].iter(),
848        )
849        .unwrap();
850        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
851
852        // Prover (Prod)
853        let config = Start::try_parse_from(["snarkos", "--prover", "--private-key", "aleo1xx"].iter()).unwrap();
854        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
855        let config =
856            Start::try_parse_from(["snarkos", "--prover", "--private-key", "aleo1xx", "--cdn", "url"].iter()).unwrap();
857        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
858        let config =
859            Start::try_parse_from(["snarkos", "--prover", "--private-key", "aleo1xx", "--cdn", ""].iter()).unwrap();
860        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
861
862        // Prover (Dev)
863        let config =
864            Start::try_parse_from(["snarkos", "--dev", "0", "--prover", "--private-key", "aleo1xx"].iter()).unwrap();
865        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
866        let config = Start::try_parse_from(
867            ["snarkos", "--dev", "0", "--prover", "--private-key", "aleo1xx", "--cdn", "url"].iter(),
868        )
869        .unwrap();
870        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
871        let config = Start::try_parse_from(
872            ["snarkos", "--dev", "0", "--prover", "--private-key", "aleo1xx", "--cdn", ""].iter(),
873        )
874        .unwrap();
875        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
876
877        // Client (Prod)
878        let config = Start::try_parse_from(["snarkos", "--client", "--private-key", "aleo1xx"].iter()).unwrap();
879        assert!(config.parse_cdn::<CurrentNetwork>().is_some());
880        let config =
881            Start::try_parse_from(["snarkos", "--client", "--private-key", "aleo1xx", "--cdn", "url"].iter()).unwrap();
882        assert!(config.parse_cdn::<CurrentNetwork>().is_some());
883        let config =
884            Start::try_parse_from(["snarkos", "--client", "--private-key", "aleo1xx", "--cdn", ""].iter()).unwrap();
885        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
886
887        // Client (Dev)
888        let config =
889            Start::try_parse_from(["snarkos", "--dev", "0", "--client", "--private-key", "aleo1xx"].iter()).unwrap();
890        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
891        let config = Start::try_parse_from(
892            ["snarkos", "--dev", "0", "--client", "--private-key", "aleo1xx", "--cdn", "url"].iter(),
893        )
894        .unwrap();
895        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
896        let config = Start::try_parse_from(
897            ["snarkos", "--dev", "0", "--client", "--private-key", "aleo1xx", "--cdn", ""].iter(),
898        )
899        .unwrap();
900        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
901
902        // Default (Prod)
903        let config = Start::try_parse_from(["snarkos"].iter()).unwrap();
904        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
905        let config = Start::try_parse_from(["snarkos", "--cdn", "url"].iter()).unwrap();
906        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
907        let config = Start::try_parse_from(["snarkos", "--cdn", ""].iter()).unwrap();
908        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
909
910        // Default (Dev)
911        let config = Start::try_parse_from(["snarkos", "--dev", "0"].iter()).unwrap();
912        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
913        let config = Start::try_parse_from(["snarkos", "--dev", "0", "--cdn", "url"].iter()).unwrap();
914        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
915        let config = Start::try_parse_from(["snarkos", "--dev", "0", "--cdn", ""].iter()).unwrap();
916        assert!(config.parse_cdn::<CurrentNetwork>().is_none());
917    }
918
919    #[test]
920    fn test_parse_development_and_genesis() {
921        let prod_genesis = Block::from_bytes_le(CurrentNetwork::genesis_bytes()).unwrap();
922
923        let mut trusted_peers = vec![];
924        let mut trusted_validators = vec![];
925        let mut config = Start::try_parse_from(["snarkos"].iter()).unwrap();
926        config.parse_development(&mut trusted_peers, &mut trusted_validators).unwrap();
927        let candidate_genesis = config.parse_genesis::<CurrentNetwork>().unwrap();
928        assert_eq!(trusted_peers.len(), 0);
929        assert_eq!(trusted_validators.len(), 0);
930        assert_eq!(candidate_genesis, prod_genesis);
931
932        let _config = Start::try_parse_from(["snarkos", "--dev", ""].iter()).unwrap_err();
933
934        let mut trusted_peers = vec![];
935        let mut trusted_validators = vec![];
936        let mut config = Start::try_parse_from(["snarkos", "--dev", "1"].iter()).unwrap();
937        config.parse_development(&mut trusted_peers, &mut trusted_validators).unwrap();
938        assert_eq!(config.rest, Some(SocketAddr::from_str("0.0.0.0:3031").unwrap()));
939
940        let mut trusted_peers = vec![];
941        let mut trusted_validators = vec![];
942        let mut config = Start::try_parse_from(["snarkos", "--dev", "1", "--rest", "127.0.0.1:8080"].iter()).unwrap();
943        config.parse_development(&mut trusted_peers, &mut trusted_validators).unwrap();
944        assert_eq!(config.rest, Some(SocketAddr::from_str("127.0.0.1:8080").unwrap()));
945
946        let mut trusted_peers = vec![];
947        let mut trusted_validators = vec![];
948        let mut config = Start::try_parse_from(["snarkos", "--dev", "1", "--norest"].iter()).unwrap();
949        config.parse_development(&mut trusted_peers, &mut trusted_validators).unwrap();
950        assert!(config.rest.is_none());
951
952        let mut trusted_peers = vec![];
953        let mut trusted_validators = vec![];
954        let mut config = Start::try_parse_from(["snarkos", "--dev", "0"].iter()).unwrap();
955        config.parse_development(&mut trusted_peers, &mut trusted_validators).unwrap();
956        let expected_genesis = config.parse_genesis::<CurrentNetwork>().unwrap();
957        assert_eq!(config.node, Some(SocketAddr::from_str("0.0.0.0:4130").unwrap()));
958        assert_eq!(config.rest, Some(SocketAddr::from_str("0.0.0.0:3030").unwrap()));
959        assert_eq!(trusted_peers.len(), 0);
960        assert_eq!(trusted_validators.len(), 1);
961        assert!(!config.validator);
962        assert!(!config.prover);
963        assert!(!config.client);
964        assert_ne!(expected_genesis, prod_genesis);
965
966        let mut trusted_peers = vec![];
967        let mut trusted_validators = vec![];
968        let mut config =
969            Start::try_parse_from(["snarkos", "--dev", "1", "--validator", "--private-key", ""].iter()).unwrap();
970        config.parse_development(&mut trusted_peers, &mut trusted_validators).unwrap();
971        let genesis = config.parse_genesis::<CurrentNetwork>().unwrap();
972        assert_eq!(config.node, Some(SocketAddr::from_str("0.0.0.0:4131").unwrap()));
973        assert_eq!(config.rest, Some(SocketAddr::from_str("0.0.0.0:3031").unwrap()));
974        assert_eq!(trusted_peers.len(), 1);
975        assert_eq!(trusted_validators.len(), 1);
976        assert!(config.validator);
977        assert!(!config.prover);
978        assert!(!config.client);
979        assert_eq!(genesis, expected_genesis);
980
981        let mut trusted_peers = vec![];
982        let mut trusted_validators = vec![];
983        let mut config =
984            Start::try_parse_from(["snarkos", "--dev", "2", "--prover", "--private-key", ""].iter()).unwrap();
985        config.parse_development(&mut trusted_peers, &mut trusted_validators).unwrap();
986        let genesis = config.parse_genesis::<CurrentNetwork>().unwrap();
987        assert_eq!(config.node, Some(SocketAddr::from_str("0.0.0.0:4132").unwrap()));
988        assert_eq!(config.rest, Some(SocketAddr::from_str("0.0.0.0:3032").unwrap()));
989        assert_eq!(trusted_peers.len(), 2);
990        assert_eq!(trusted_validators.len(), 2);
991        assert!(!config.validator);
992        assert!(config.prover);
993        assert!(!config.client);
994        assert_eq!(genesis, expected_genesis);
995
996        let mut trusted_peers = vec![];
997        let mut trusted_validators = vec![];
998        let mut config =
999            Start::try_parse_from(["snarkos", "--dev", "3", "--client", "--private-key", ""].iter()).unwrap();
1000        config.parse_development(&mut trusted_peers, &mut trusted_validators).unwrap();
1001        let genesis = config.parse_genesis::<CurrentNetwork>().unwrap();
1002        assert_eq!(config.node, Some(SocketAddr::from_str("0.0.0.0:4133").unwrap()));
1003        assert_eq!(config.rest, Some(SocketAddr::from_str("0.0.0.0:3033").unwrap()));
1004        assert_eq!(trusted_peers.len(), 3);
1005        assert_eq!(trusted_validators.len(), 2);
1006        assert!(!config.validator);
1007        assert!(!config.prover);
1008        assert!(config.client);
1009        assert_eq!(genesis, expected_genesis);
1010    }
1011
1012    #[test]
1013    fn clap_snarkos_start() {
1014        let arg_vec = vec![
1015            "snarkos",
1016            "start",
1017            "--nodisplay",
1018            "--dev",
1019            "2",
1020            "--validator",
1021            "--private-key",
1022            "PRIVATE_KEY",
1023            "--cdn",
1024            "CDN",
1025            "--peers",
1026            "IP1,IP2,IP3",
1027            "--validators",
1028            "IP1,IP2,IP3",
1029            "--rest",
1030            "127.0.0.1:3030",
1031        ];
1032        let cli = CLI::parse_from(arg_vec);
1033
1034        if let Command::Start(start) = cli.command {
1035            assert!(start.nodisplay);
1036            assert_eq!(start.dev, Some(2));
1037            assert!(start.validator);
1038            assert_eq!(start.private_key.as_deref(), Some("PRIVATE_KEY"));
1039            assert_eq!(start.cdn, Some("CDN".to_string()));
1040            assert_eq!(start.rest, Some("127.0.0.1:3030".parse().unwrap()));
1041            assert_eq!(start.network, 0);
1042            assert_eq!(start.peers, "IP1,IP2,IP3");
1043            assert_eq!(start.validators, "IP1,IP2,IP3");
1044        } else {
1045            panic!("Unexpected result of clap parsing!");
1046        }
1047    }
1048}