use snarkos_account::Account;
use snarkos_display::Display;
use snarkos_node::{messages::NodeType, Node};
use snarkvm::prelude::{
block::Block,
store::{helpers::memory::ConsensusMemory, ConsensusStore},
FromBytes,
Network,
PrivateKey,
Testnet3,
VM,
};
use anyhow::{bail, Result};
use clap::Parser;
use colored::Colorize;
use core::str::FromStr;
use rand::SeedableRng;
use rand_chacha::ChaChaRng;
use std::{net::SocketAddr, path::PathBuf};
use tokio::runtime::{self, Runtime};
#[cfg(target_family = "unix")]
const RECOMMENDED_MIN_NOFILES_LIMIT_BEACON: u64 = 2048;
#[cfg(target_family = "unix")]
const RECOMMENDED_MIN_NOFILES_LIMIT_VALIDATOR: u64 = 1024;
#[derive(Clone, Debug, Parser)]
pub struct Start {
#[clap(default_value = "3", long = "network")]
pub network: u16,
#[clap(long = "beacon")]
pub beacon: Option<String>,
#[clap(long = "validator")]
pub validator: Option<String>,
#[clap(long = "prover")]
pub prover: Option<String>,
#[clap(long = "client")]
pub client: Option<String>,
#[clap(default_value = "0.0.0.0:4133", long = "node")]
pub node: SocketAddr,
#[clap(default_value = "", long = "connect")]
pub connect: String,
#[clap(default_value = "0.0.0.0:3033", long = "rest")]
pub rest: SocketAddr,
#[clap(long)]
pub norest: bool,
#[clap(long)]
pub nodisplay: bool,
#[clap(default_value = "2", long = "verbosity")]
pub verbosity: u8,
#[clap(default_value_os_t = std::env::temp_dir().join("snarkos.log"), long = "logfile")]
pub logfile: PathBuf,
#[clap(default_value = "https://testnet3.blocks.aleo.org/phase3", long = "cdn")]
pub cdn: String,
#[clap(long)]
pub dev: Option<u16>,
}
impl Start {
pub fn parse(self) -> Result<String> {
let log_receiver = crate::helpers::initialize_logger(self.verbosity, self.nodisplay, self.logfile.clone());
Self::runtime().block_on(async move {
let mut cli = self.clone();
match cli.network {
3 => {
let node = cli.parse_node::<Testnet3>().await.expect("Failed to parse the node");
if !cli.nodisplay {
Display::start(node, log_receiver).expect("Failed to initialize the display");
}
}
_ => panic!("Invalid network ID specified"),
};
std::future::pending::<()>().await;
});
Ok(String::new())
}
}
impl Start {
fn parse_trusted_peers(&self) -> Result<Vec<SocketAddr>> {
match self.connect.is_empty() {
true => Ok(vec![]),
false => Ok(self
.connect
.split(',')
.flat_map(|ip| match ip.parse::<SocketAddr>() {
Ok(ip) => Some(ip),
Err(e) => {
eprintln!("The IP supplied to --connect ('{ip}') is malformed: {e}");
None
}
})
.collect()),
}
}
fn parse_cdn(&self) -> Option<String> {
if self.dev.is_some() || self.cdn.is_empty() || self.client.is_some() || self.prover.is_some() {
None
}
else if let (None, None, None, None) = (&self.beacon, &self.validator, &self.prover, &self.client) {
None
}
else {
Some(self.cdn.clone())
}
}
fn parse_development<N: Network>(&mut self, trusted_peers: &mut Vec<SocketAddr>) -> Result<Block<N>> {
if let Some(dev) = self.dev {
if dev > 0 && self.beacon.is_some() {
bail!("At most one beacon at '--dev 0' is supported in development mode");
}
for i in 0..dev {
trusted_peers.push(SocketAddr::from_str(&format!("127.0.0.1:{}", 4130 + i))?);
}
self.node = SocketAddr::from_str(&format!("0.0.0.0:{}", 4130 + dev))?;
if !self.norest {
self.rest = SocketAddr::from_str(&format!("0.0.0.0:{}", 3030 + dev))?;
}
let mut rng = ChaChaRng::seed_from_u64(1234567890u64);
let beacon_private_key = PrivateKey::<N>::new(&mut rng)?;
let vm = VM::from(ConsensusStore::<N, ConsensusMemory<N>>::open(None)?)?;
let genesis = vm.genesis(&beacon_private_key, &mut rng)?;
let sample_account = |node: &mut Option<String>, is_beacon: bool| -> Result<()> {
let account = match is_beacon {
true => Account::<N>::try_from(beacon_private_key)?,
false => Account::<N>::new(&mut rand::thread_rng())?,
};
*node = Some(account.private_key().to_string());
println!(
"⚠️ Attention - Sampling a *one-time* account for this instance, please save this securely:\n\n{account}\n"
);
Ok(())
};
if self.beacon.is_some() {
sample_account(&mut self.beacon, true)?;
}
else if let Some("") = self.validator.as_deref() {
sample_account(&mut self.validator, false)?;
} else if let Some("") = self.prover.as_deref() {
sample_account(&mut self.prover, false)?;
} else if let Some("") = self.client.as_deref() {
sample_account(&mut self.client, false)?;
}
Ok(genesis)
} else {
Block::from_bytes_le(N::genesis_bytes())
}
}
fn parse_account<N: Network>(&self) -> Result<(Account<N>, NodeType)> {
match (&self.beacon, &self.validator, &self.prover, &self.client) {
(Some(private_key), None, None, None) => Ok((Account::<N>::from_str(private_key)?, NodeType::Beacon)),
(None, Some(private_key), None, None) => Ok((Account::<N>::from_str(private_key)?, NodeType::Validator)),
(None, None, Some(private_key), None) => Ok((Account::<N>::from_str(private_key)?, NodeType::Prover)),
(None, None, None, Some(private_key)) => Ok((Account::<N>::from_str(private_key)?, NodeType::Client)),
(None, None, None, None) => Ok((Account::<N>::new(&mut rand::thread_rng())?, NodeType::Client)),
_ => bail!("Unsupported node configuration"),
}
}
#[rustfmt::skip]
async fn parse_node<N: Network>(&mut self) -> Result<Node<N>> {
println!("{}", crate::helpers::welcome_message());
let mut trusted_peers = self.parse_trusted_peers()?;
let cdn = self.parse_cdn();
let genesis = self.parse_development::<N>(&mut trusted_peers)?;
let rest_ip = match self.norest {
true => None,
false => Some(self.rest),
};
let (account, node_type) = self.parse_account::<N>()?;
if self.nodisplay {
println!("🪪 Your Aleo address is {}.\n", account.address().to_string().bold());
println!(
"🧭 Starting {} on {} {} at {}.\n",
node_type.description().bold(),
N::NAME.bold(),
"Phase 3".bold(),
self.node.to_string().bold()
);
if node_type.is_beacon() || node_type.is_validator() {
if let Some(rest_ip) = rest_ip {
println!("🌐 Starting the REST server at {}.\n", rest_ip.to_string().bold());
if let Ok(jwt_token) = snarkos_node_rest::Claims::new(account.address()).to_jwt_string() {
println!("🔑 Your one-time JWT token is {}\n", jwt_token.dimmed());
}
}
}
}
if node_type.is_beacon() {
#[cfg(target_family = "unix")]
crate::helpers::check_open_files_limit(RECOMMENDED_MIN_NOFILES_LIMIT_BEACON);
}
if node_type.is_validator() {
#[cfg(target_family = "unix")]
crate::helpers::check_open_files_limit(RECOMMENDED_MIN_NOFILES_LIMIT_VALIDATOR);
}
match node_type {
NodeType::Beacon => Node::new_beacon(self.node, rest_ip, account, &trusted_peers, genesis, cdn, self.dev).await,
NodeType::Validator => Node::new_validator(self.node, rest_ip, account, &trusted_peers, genesis, cdn, self.dev).await,
NodeType::Prover => Node::new_prover(self.node, account, &trusted_peers, genesis, self.dev).await,
NodeType::Client => Node::new_client(self.node, account, &trusted_peers, genesis, self.dev).await,
}
}
fn runtime() -> Runtime {
let (num_tokio_worker_threads, max_tokio_blocking_threads, num_rayon_cores_global) =
{ (num_cpus::get().min(8), 512, num_cpus::get().saturating_sub(8).max(1)) };
rayon::ThreadPoolBuilder::new()
.stack_size(8 * 1024 * 1024)
.num_threads(num_rayon_cores_global)
.build_global()
.unwrap();
runtime::Builder::new_multi_thread()
.enable_all()
.thread_stack_size(8 * 1024 * 1024)
.worker_threads(num_tokio_worker_threads)
.max_blocking_threads(max_tokio_blocking_threads)
.build()
.expect("Failed to initialize a runtime for the router")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::commands::{Command, CLI};
use snarkvm::prelude::Testnet3;
type CurrentNetwork = Testnet3;
#[test]
fn test_parse_trusted_peers() {
let config = Start::try_parse_from(["snarkos", "--connect", ""].iter()).unwrap();
assert!(config.parse_trusted_peers().is_ok());
assert!(config.parse_trusted_peers().unwrap().is_empty());
let config = Start::try_parse_from(["snarkos", "--connect", "1.2.3.4:5"].iter()).unwrap();
assert!(config.parse_trusted_peers().is_ok());
assert_eq!(config.parse_trusted_peers().unwrap(), vec![SocketAddr::from_str("1.2.3.4:5").unwrap()]);
let config = Start::try_parse_from(["snarkos", "--connect", "1.2.3.4:5,6.7.8.9:0"].iter()).unwrap();
assert!(config.parse_trusted_peers().is_ok());
assert_eq!(config.parse_trusted_peers().unwrap(), vec![
SocketAddr::from_str("1.2.3.4:5").unwrap(),
SocketAddr::from_str("6.7.8.9:0").unwrap()
]);
}
#[test]
fn test_parse_cdn() {
let config = Start::try_parse_from(["snarkos", "--beacon", "aleo1xx"].iter()).unwrap();
assert!(config.parse_cdn().is_some());
let config = Start::try_parse_from(["snarkos", "--beacon", "aleo1xx", "--cdn", "url"].iter()).unwrap();
assert!(config.parse_cdn().is_some());
let config = Start::try_parse_from(["snarkos", "--beacon", "aleo1xx", "--cdn", ""].iter()).unwrap();
assert!(config.parse_cdn().is_none());
let config = Start::try_parse_from(["snarkos", "--dev", "0", "--beacon", "aleo1xx"].iter()).unwrap();
assert!(config.parse_cdn().is_none());
let config =
Start::try_parse_from(["snarkos", "--dev", "0", "--beacon", "aleo1xx", "--cdn", "url"].iter()).unwrap();
assert!(config.parse_cdn().is_none());
let config =
Start::try_parse_from(["snarkos", "--dev", "0", "--beacon", "aleo1xx", "--cdn", ""].iter()).unwrap();
assert!(config.parse_cdn().is_none());
let config = Start::try_parse_from(["snarkos", "--validator", "aleo1xx"].iter()).unwrap();
assert!(config.parse_cdn().is_some());
let config = Start::try_parse_from(["snarkos", "--validator", "aleo1xx", "--cdn", "url"].iter()).unwrap();
assert!(config.parse_cdn().is_some());
let config = Start::try_parse_from(["snarkos", "--validator", "aleo1xx", "--cdn", ""].iter()).unwrap();
assert!(config.parse_cdn().is_none());
let config = Start::try_parse_from(["snarkos", "--dev", "0", "--validator", "aleo1xx"].iter()).unwrap();
assert!(config.parse_cdn().is_none());
let config =
Start::try_parse_from(["snarkos", "--dev", "0", "--validator", "aleo1xx", "--cdn", "url"].iter()).unwrap();
assert!(config.parse_cdn().is_none());
let config =
Start::try_parse_from(["snarkos", "--dev", "0", "--validator", "aleo1xx", "--cdn", ""].iter()).unwrap();
assert!(config.parse_cdn().is_none());
let config = Start::try_parse_from(["snarkos", "--prover", "aleo1xx"].iter()).unwrap();
assert!(config.parse_cdn().is_none());
let config = Start::try_parse_from(["snarkos", "--prover", "aleo1xx", "--cdn", "url"].iter()).unwrap();
assert!(config.parse_cdn().is_none());
let config = Start::try_parse_from(["snarkos", "--prover", "aleo1xx", "--cdn", ""].iter()).unwrap();
assert!(config.parse_cdn().is_none());
let config = Start::try_parse_from(["snarkos", "--dev", "0", "--prover", "aleo1xx"].iter()).unwrap();
assert!(config.parse_cdn().is_none());
let config =
Start::try_parse_from(["snarkos", "--dev", "0", "--prover", "aleo1xx", "--cdn", "url"].iter()).unwrap();
assert!(config.parse_cdn().is_none());
let config =
Start::try_parse_from(["snarkos", "--dev", "0", "--prover", "aleo1xx", "--cdn", ""].iter()).unwrap();
assert!(config.parse_cdn().is_none());
let config = Start::try_parse_from(["snarkos", "--client", "aleo1xx"].iter()).unwrap();
assert!(config.parse_cdn().is_none());
let config = Start::try_parse_from(["snarkos", "--client", "aleo1xx", "--cdn", "url"].iter()).unwrap();
assert!(config.parse_cdn().is_none());
let config = Start::try_parse_from(["snarkos", "--client", "aleo1xx", "--cdn", ""].iter()).unwrap();
assert!(config.parse_cdn().is_none());
let config = Start::try_parse_from(["snarkos", "--dev", "0", "--client", "aleo1xx"].iter()).unwrap();
assert!(config.parse_cdn().is_none());
let config =
Start::try_parse_from(["snarkos", "--dev", "0", "--client", "aleo1xx", "--cdn", "url"].iter()).unwrap();
assert!(config.parse_cdn().is_none());
let config =
Start::try_parse_from(["snarkos", "--dev", "0", "--client", "aleo1xx", "--cdn", ""].iter()).unwrap();
assert!(config.parse_cdn().is_none());
let config = Start::try_parse_from(["snarkos"].iter()).unwrap();
assert!(config.parse_cdn().is_none());
let config = Start::try_parse_from(["snarkos", "--cdn", "url"].iter()).unwrap();
assert!(config.parse_cdn().is_none());
let config = Start::try_parse_from(["snarkos", "--cdn", ""].iter()).unwrap();
assert!(config.parse_cdn().is_none());
let config = Start::try_parse_from(["snarkos", "--dev", "0"].iter()).unwrap();
assert!(config.parse_cdn().is_none());
let config = Start::try_parse_from(["snarkos", "--dev", "0", "--cdn", "url"].iter()).unwrap();
assert!(config.parse_cdn().is_none());
let config = Start::try_parse_from(["snarkos", "--dev", "0", "--cdn", ""].iter()).unwrap();
assert!(config.parse_cdn().is_none());
}
#[test]
fn test_parse_development() {
let prod_genesis = Block::from_bytes_le(CurrentNetwork::genesis_bytes()).unwrap();
let mut trusted_peers = vec![];
let mut config = Start::try_parse_from(["snarkos"].iter()).unwrap();
let candidate_genesis = config.parse_development::<CurrentNetwork>(&mut trusted_peers).unwrap();
assert_eq!(trusted_peers.len(), 0);
assert_eq!(candidate_genesis, prod_genesis);
let _config = Start::try_parse_from(["snarkos", "--dev", ""].iter()).unwrap_err();
let mut config = Start::try_parse_from(["snarkos", "--dev", "1", "--beacon", ""].iter()).unwrap();
config.parse_development::<CurrentNetwork>(&mut vec![]).unwrap_err();
let mut trusted_peers = vec![];
let mut config = Start::try_parse_from(["snarkos", "--dev", "0"].iter()).unwrap();
let expected_genesis = config.parse_development::<CurrentNetwork>(&mut trusted_peers).unwrap();
assert_eq!(config.node, SocketAddr::from_str("0.0.0.0:4130").unwrap());
assert_eq!(config.rest, SocketAddr::from_str("0.0.0.0:3030").unwrap());
assert_eq!(trusted_peers.len(), 0);
assert!(config.beacon.is_none());
assert!(config.validator.is_none());
assert!(config.prover.is_none());
assert!(config.client.is_none());
assert_ne!(expected_genesis, prod_genesis);
let mut trusted_peers = vec![];
let mut config = Start::try_parse_from(["snarkos", "--dev", "0", "--beacon", ""].iter()).unwrap();
let genesis = config.parse_development::<CurrentNetwork>(&mut trusted_peers).unwrap();
assert_eq!(config.node, SocketAddr::from_str("0.0.0.0:4130").unwrap());
assert_eq!(config.rest, SocketAddr::from_str("0.0.0.0:3030").unwrap());
assert_eq!(trusted_peers.len(), 0);
assert!(config.beacon.is_some());
assert!(config.validator.is_none());
assert!(config.prover.is_none());
assert!(config.client.is_none());
assert_eq!(genesis, expected_genesis);
let mut trusted_peers = vec![];
let mut config = Start::try_parse_from(["snarkos", "--dev", "1", "--validator", ""].iter()).unwrap();
let genesis = config.parse_development::<CurrentNetwork>(&mut trusted_peers).unwrap();
assert_eq!(config.node, SocketAddr::from_str("0.0.0.0:4131").unwrap());
assert_eq!(config.rest, SocketAddr::from_str("0.0.0.0:3031").unwrap());
assert_eq!(trusted_peers.len(), 1);
assert!(config.beacon.is_none());
assert!(config.validator.is_some());
assert!(config.prover.is_none());
assert!(config.client.is_none());
assert_eq!(genesis, expected_genesis);
let mut trusted_peers = vec![];
let mut config = Start::try_parse_from(["snarkos", "--dev", "2", "--prover", ""].iter()).unwrap();
let genesis = config.parse_development::<CurrentNetwork>(&mut trusted_peers).unwrap();
assert_eq!(config.node, SocketAddr::from_str("0.0.0.0:4132").unwrap());
assert_eq!(config.rest, SocketAddr::from_str("0.0.0.0:3032").unwrap());
assert_eq!(trusted_peers.len(), 2);
assert!(config.beacon.is_none());
assert!(config.validator.is_none());
assert!(config.prover.is_some());
assert!(config.client.is_none());
assert_eq!(genesis, expected_genesis);
let mut trusted_peers = vec![];
let mut config = Start::try_parse_from(["snarkos", "--dev", "3", "--client", ""].iter()).unwrap();
let genesis = config.parse_development::<CurrentNetwork>(&mut trusted_peers).unwrap();
assert_eq!(config.node, SocketAddr::from_str("0.0.0.0:4133").unwrap());
assert_eq!(config.rest, SocketAddr::from_str("0.0.0.0:3033").unwrap());
assert_eq!(trusted_peers.len(), 3);
assert!(config.beacon.is_none());
assert!(config.validator.is_none());
assert!(config.prover.is_none());
assert!(config.client.is_some());
assert_eq!(genesis, expected_genesis);
}
#[test]
fn clap_snarkos_start() {
let arg_vec = vec![
"snarkos",
"start",
"--nodisplay",
"--dev",
"2",
"--validator",
"PRIVATE_KEY",
"--cdn",
"CDN",
"--connect",
"IP1,IP2,IP3",
"--rest",
"127.0.0.1:3033",
];
let cli = CLI::parse_from(arg_vec);
if let Command::Start(start) = cli.command {
assert!(start.nodisplay);
assert_eq!(start.dev, Some(2));
assert_eq!(start.validator.as_deref(), Some("PRIVATE_KEY"));
assert_eq!(start.cdn, "CDN");
assert_eq!(start.rest, "127.0.0.1:3033".parse().unwrap());
assert_eq!(start.network, 3);
assert_eq!(start.connect, "IP1,IP2,IP3");
} else {
panic!("Unexpected result of clap parsing!");
}
}
}