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