use crate::helpers::{
args::{network_id_parser, parse_node_data_dir},
dev::*,
};
use snarkos_account::Account;
use snarkos_display::Display;
use snarkos_node::{
Node,
bft::MEMORY_POOL_PORT,
network::{NodeType, bootstrap_peers},
rest::DEFAULT_REST_PORT,
router::DEFAULT_NODE_PORT,
};
use snarkos_utilities::{NodeDataDir, SignalHandler, jwt_secret_file, node_data};
use snarkvm::{
console::{
account::{Address, PrivateKey},
algorithms::Hash,
network::{CanaryV0, MainnetV0, Network, TestnetV0},
},
ledger::{
block::Block,
committee::{Committee, MIN_DELEGATOR_STAKE, MIN_VALIDATOR_STAKE},
store::{ConsensusStore, helpers::memory::ConsensusMemory},
},
prelude::{FromBytes, Itertools, ToBits, ToBytes},
synthesizer::VM,
utilities::to_bytes_le,
};
use aleo_std::{StorageMode, aleo_ledger_dir};
use anyhow::{Context, Result, anyhow, bail, ensure};
use base64::prelude::{BASE64_STANDARD, Engine};
use clap::Parser;
use colored::Colorize;
use core::str::FromStr;
use indexmap::IndexMap;
use rand::{Rng, SeedableRng};
use rand_chacha::ChaChaRng;
use serde::{Deserialize, Serialize};
use std::{
fs,
io::IsTerminal,
net::{Ipv4Addr, SocketAddr, SocketAddrV4, ToSocketAddrs},
path::{Path, PathBuf},
sync::{Arc, atomic::AtomicBool},
};
use tokio::{
runtime::{self, Runtime},
sync::mpsc,
task,
};
use tracing::{debug, info, warn};
use ureq::http;
#[cfg(target_family = "unix")]
const RECOMMENDED_MIN_NOFILES_LIMIT: u64 = 2048;
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct BondedBalances(IndexMap<String, (String, String, u64)>);
impl FromStr for BondedBalances {
type Err = serde_json::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_json::from_str(s)
}
}
#[derive(Clone, Debug, Parser)]
#[command(
// Use kebab-case for all arguments (e.g., use the `private-key` flag for the `private_key` field).
// This is already the default, but we specify it in case clap's default changes in the future.
rename_all = "kebab-case",
// Ensure at most one node type is specified.
group(clap::ArgGroup::new("node_type").required(false).multiple(false)
),
// Ensure all other dev flags can only be set if `--dev` is set.
group(clap::ArgGroup::new("dev_flags").required(false).multiple(true).requires("dev")
),
// Ensure any rest flag (including `--rest`) cannot be set
// if `--norest` is set.
group(clap::ArgGroup::new("rest_flags").required(false).multiple(true).conflicts_with("norest")),
// Ensure you cannot set --verbosity and --log-filter flags at the same time.
group(clap::ArgGroup::new("log_flags").required(false).multiple(false)),
// Ensure you need to set either --jwt-secret and --jwt-timestamp or --nojwt flags.
group(clap::ArgGroup::new("jwt_flags").required(false).multiple(true).conflicts_with("nojwt").conflicts_with("norest")),
)]
pub struct Start {
#[clap(long, default_value_t=MainnetV0::ID, long, value_parser = network_id_parser())]
pub network: u16,
#[clap(long, group = "node_type")]
pub prover: bool,
#[clap(long, group = "node_type", verbatim_doc_comment)]
pub client: bool,
#[clap(long = "bootstrap-client", group = "node_type", conflicts_with_all = ["peers", "validators"], verbatim_doc_comment)]
pub bootstrap_client: bool,
#[clap(long, group = "node_type", verbatim_doc_comment)]
pub validator: bool,
#[clap(long)]
pub private_key: Option<String>,
#[clap(long = "private-key-file")]
pub private_key_file: Option<PathBuf>,
#[clap(long)]
pub node: Option<SocketAddr>,
#[clap(long, requires = "validator")]
pub bft: Option<SocketAddr>,
#[clap(long, verbatim_doc_comment)]
pub peers: Option<String>,
#[clap(long)]
pub validators: Option<String>,
#[clap(long, verbatim_doc_comment)]
pub allow_external_peers: bool,
#[clap(long)]
pub rotate_external_peers: bool,
#[clap(long, group = "rest_flags")]
pub rest: Option<SocketAddr>,
#[clap(long, default_value_t = 10, group = "rest_flags")]
pub rest_rps: u32,
#[clap(long, group = "jwt_flags")]
pub jwt_secret: Option<String>,
#[clap(long, group = "jwt_flags")]
pub jwt_timestamp: Option<i64>,
#[clap(long)]
pub norest: bool,
#[clap(long, group = "rest_flags")]
pub nojwt: bool,
#[clap(long)]
pub trusted_peers_only: bool,
#[clap(long, verbatim_doc_comment)]
pub nodisplay: bool,
#[clap(long, hide = true)]
pub nobanner: bool,
#[clap(long, default_value_t = 1, group = "log_flags")]
pub verbosity: u8,
#[clap(long, group = "log_flags")]
pub log_filter: Option<String>,
#[clap(long, default_value_os_t = std::env::temp_dir().join("snarkos.log"))]
pub logfile: PathBuf,
#[cfg(feature = "metrics")]
#[clap(long)]
pub metrics: bool,
#[cfg(feature = "metrics")]
#[clap(long, requires = "metrics")]
pub metrics_ip: Option<SocketAddr>,
#[clap(long, verbatim_doc_comment, alias = "storage")]
pub ledger_storage: Option<PathBuf>,
#[clap(long, verbatim_doc_comment)]
pub node_data_storage: Option<PathBuf>,
#[clap(long)]
pub auto_db_checkpoints: Option<PathBuf>,
#[clap(long, conflicts_with = "nocdn")]
pub cdn: Option<http::Uri>,
#[clap(long)]
pub nocdn: bool,
#[clap(long, verbatim_doc_comment)]
pub dev: Option<u16>,
#[clap(long, group = "dev_flags", default_value_t=DEVELOPMENT_MODE_NUM_GENESIS_COMMITTEE_MEMBERS)]
pub dev_num_validators: u16,
#[clap(long, group = "dev_flags", conflicts_with = "peers")]
pub dev_num_clients: Option<u16>,
#[clap(long, group = "dev_flag")]
pub no_dev_txs: bool,
#[clap(long, group = "dev_flags")]
pub dev_bonded_balances: Option<BondedBalances>,
#[clap(long)]
pub auto_migrate_node_data: bool,
}
impl Start {
pub fn parse(self) -> Result<String> {
let shutdown: Arc<AtomicBool> = Default::default();
let log_receiver = crate::helpers::initialize_logger(
self.verbosity,
&self.log_filter,
self.nodisplay,
self.logfile.clone(),
shutdown.clone(),
)
.with_context(|| "Failed to set up logger")?;
if !std::io::stdout().is_terminal() && !self.nodisplay {
anyhow::bail!(
"snarkOS cannot use the terminal UI in a non-interactive session. Please restart with `--nodisplay`."
);
}
Self::runtime().block_on(async move {
let node_parse_error = || "Failed to start node";
let mut self_ = self.clone();
match self_.network {
MainnetV0::ID => self_.parse_node::<MainnetV0>(log_receiver).await.with_context(node_parse_error)?,
TestnetV0::ID => self_.parse_node::<TestnetV0>(log_receiver).await.with_context(node_parse_error)?,
CanaryV0::ID => self_.parse_node::<CanaryV0>(log_receiver).await.with_context(node_parse_error)?,
_ => panic!("Invalid network ID specified"),
};
Ok(String::new())
})
}
}
impl Start {
fn parse_trusted_addrs(&self, list: &Option<String>) -> Result<Vec<SocketAddr>> {
let Some(list) = list else { return Ok(vec![]) };
match list.is_empty() {
true => Ok(vec![]),
false => list.split(',').map(resolve_potential_hostnames).collect(),
}
}
fn parse_cdn<N: Network>(&self) -> Result<Option<http::Uri>> {
let no_cdn_reasons = [("--dev", self.dev.is_some()), ("--nocdn", self.nocdn), ("--prover", self.prover)]
.into_iter()
.filter_map(|(reason, flag_set)| flag_set.then_some(reason))
.join(" and ");
if !no_cdn_reasons.is_empty() {
info!("CDN disabled because the following flags are set: {no_cdn_reasons}.");
Ok(None)
}
else {
match &self.cdn {
Some(cdn) => match cdn.to_string().is_empty() {
true => Ok(None),
false => Ok(Some(cdn.clone())),
},
None => {
let uri = format!("{}/{}", snarkos_node_cdn::CDN_BASE_URL, N::SHORT_NAME);
Ok(Some(http::Uri::try_from(&uri).with_context(|| "Unexpected error")?))
}
}
}
}
fn parse_private_key<N: Network>(&self) -> Result<Account<N>> {
match self.dev {
None => match (&self.private_key, &self.private_key_file) {
(Some(private_key), None) => Account::from_str(private_key.trim()),
(None, Some(path)) => {
check_permissions(path)?;
Account::from_str(std::fs::read_to_string(path)?.trim())
}
(None, None) => match self.client {
true => Account::new(&mut rand::thread_rng()),
false => bail!("Missing the '--private-key' or '--private-key-file' argument"),
},
(Some(_), Some(_)) => {
bail!("Cannot use '--private-key' and '--private-key-file' simultaneously, please use only one")
}
},
Some(index) => {
let private_key = get_development_key(index)?;
if !self.nobanner {
println!(
"🔑 Your development private key for node {index} is {}.\n",
private_key.to_string().bold()
);
}
Account::try_from(private_key)
}
}
}
fn parse_development(
&mut self,
trusted_peers: &mut Vec<SocketAddr>,
trusted_validators: &mut Vec<SocketAddr>,
) -> Result<()> {
let Some(dev) = self.dev else {
return Ok(());
};
let num_validators = self.dev_num_validators;
ensure!(num_validators >= 4, "Value for `dev_num_validators` is too low. Needs to be at least 4.");
info!("Development mode enabled with index={dev} and num_validators={num_validators}.");
let is_validator = self.validator;
if is_validator {
ensure!(
dev < num_validators,
"Development validator index is too high (dev={dev}, dev_num_validators={num_validators})",
);
}
if trusted_validators.is_empty() && is_validator {
for idx in 0..num_validators {
if idx == dev {
continue;
}
trusted_validators.push(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, MEMORY_POOL_PORT + idx)));
}
debug!("Trusted validators set to: {trusted_validators:?}");
}
if trusted_peers.is_empty() {
if is_validator {
if let Some(num_clients) = self.dev_num_clients {
for client_idx in 0..num_clients {
if get_devnet_validators_for_client(client_idx, num_validators).contains(&dev) {
let node_idx = num_validators + client_idx;
trusted_peers.push(get_devnet_router_address_for_node(node_idx));
}
}
} else {
warn!(
"Development validator started without trusted peers or `--dev-num-clients`. No clients will be able to connect to it."
);
}
} else {
for validator_idx in get_devnet_validators_for_client(dev, num_validators) {
trusted_peers.push(get_devnet_router_address_for_node(validator_idx));
}
}
debug!("Trusted peers set to: {trusted_peers:?}");
} else {
debug!("Trusted peers/validators was set manually. Will not populate them with development addresses.")
}
if self.node.is_none() {
let port = get_devnet_router_address_for_node(dev).port();
let address = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port));
debug!("Setting node address to {address} due to dev={dev}");
self.node = Some(address);
}
if !self.norest && self.rest.is_none() {
let port = DEFAULT_REST_PORT + dev;
debug!("Setting REST port to {port} due to dev={dev}");
self.rest = Some(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port)));
}
Ok(())
}
fn jwt_secret_path<N: Network>(node_data_dir: &NodeDataDir, address: &Address<N>) -> PathBuf {
node_data_dir.path().join(jwt_secret_file(address))
}
fn parse_genesis<N: Network>(&self) -> Result<Block<N>> {
if self.dev.is_some() {
let num_committee_members = self.dev_num_validators;
ensure!(
num_committee_members >= DEVELOPMENT_MODE_NUM_GENESIS_COMMITTEE_MEMBERS,
"Number of genesis committee members is too low"
);
let mut rng = ChaChaRng::seed_from_u64(DEVELOPMENT_MODE_RNG_SEED);
let dev_keys =
(0..num_committee_members).map(|_| PrivateKey::<N>::new(&mut rng)).collect::<Result<Vec<_>>>()?;
let development_addresses = dev_keys.iter().map(Address::<N>::try_from).collect::<Result<Vec<_>>>()?;
let (committee, bonded_balances) = match &self.dev_bonded_balances {
Some(bonded_balances) => {
let bonded_balances = bonded_balances
.0
.iter()
.map(|(staker_address, (validator_address, withdrawal_address, amount))| {
let staker_addr = Address::<N>::from_str(staker_address)?;
let validator_addr = Address::<N>::from_str(validator_address)?;
let withdrawal_addr = Address::<N>::from_str(withdrawal_address)?;
Ok((staker_addr, (validator_addr, withdrawal_addr, *amount)))
})
.collect::<Result<IndexMap<_, _>>>()?;
let mut members = IndexMap::new();
for (staker_address, (validator_address, _, amount)) in bonded_balances.iter() {
match staker_address == validator_address {
true => ensure!(amount >= &MIN_VALIDATOR_STAKE, "Validator stake is too low"),
false => ensure!(amount >= &MIN_DELEGATOR_STAKE, "Delegator stake is too low"),
}
ensure!(
development_addresses.contains(validator_address),
"Validator address {validator_address} is not included in the list of development addresses"
);
members.entry(*validator_address).and_modify(|(stake, _, _)| *stake += amount).or_insert((
*amount,
true,
rng.gen_range(0..100),
));
}
let committee = Committee::<N>::new(0u64, members)?;
(committee, bonded_balances)
}
None => {
let stake_per_member =
N::STARTING_SUPPLY.saturating_div(2).saturating_div(num_committee_members as u64);
ensure!(stake_per_member >= MIN_VALIDATOR_STAKE, "Committee stake per member is too low");
let members = development_addresses
.iter()
.map(|address| (*address, (stake_per_member, true, rng.gen_range(0..100))))
.collect::<IndexMap<_, _>>();
let bonded_balances = members
.iter()
.map(|(address, (stake, _, _))| (*address, (*address, *address, *stake)))
.collect::<IndexMap<_, _>>();
let committee = Committee::<N>::new(0u64, members)?;
(committee, bonded_balances)
}
};
ensure!(
committee.members().len() == num_committee_members as usize,
"Number of committee members {} does not match the expected number of members {num_committee_members}",
committee.members().len()
);
let remaining_balance = N::STARTING_SUPPLY.saturating_sub(committee.total_stake());
let public_balance_per_validator = remaining_balance.saturating_div(num_committee_members as u64);
let mut public_balances = dev_keys
.iter()
.map(|private_key| Ok((Address::try_from(private_key)?, public_balance_per_validator)))
.collect::<Result<indexmap::IndexMap<_, _>>>()?;
let leftover =
remaining_balance.saturating_sub(public_balance_per_validator * num_committee_members as u64);
if leftover > 0 {
let (_, balance) = public_balances.get_index_mut(0).unwrap();
*balance += leftover;
}
let public_balances_sum: u64 = public_balances.values().copied().sum();
if committee.total_stake() + public_balances_sum != N::STARTING_SUPPLY {
bail!("Sum of committee stakes and public balances does not equal total starting supply.");
}
std::thread::spawn(move || {
load_or_compute_genesis(dev_keys[0], committee, public_balances, bonded_balances, &mut rng)
})
.join()
.unwrap()
} else {
Block::from_bytes_le(N::genesis_bytes())
}
}
const fn parse_node_type(&self) -> NodeType {
if self.validator {
NodeType::Validator
} else if self.prover {
NodeType::Prover
} else if self.bootstrap_client {
NodeType::BootstrapClient
} else {
NodeType::Client
}
}
#[rustfmt::skip]
async fn parse_node<N: Network>(&mut self, log_receiver: mpsc::Receiver<Vec<u8>>) -> Result<()> {
if !self.nobanner {
println!("{}", crate::helpers::welcome_message());
}
if cfg!(feature = "test_network") && self.dev.is_none() {
bail!("The 'test_network' feature is enabled, but the '--dev' flag is not set");
}
let mut trusted_peers = self.parse_trusted_addrs(&self.peers)?;
let mut trusted_validators = self.parse_trusted_addrs(&self.validators)?;
let bootstrap_peers = bootstrap_peers::<N>(self.dev.is_some());
for trusted in [&mut trusted_peers, &mut trusted_validators] {
let initial_peer_count = trusted.len();
trusted.retain(|addr| !bootstrap_peers.contains(addr));
let final_peer_count = trusted.len();
if final_peer_count != initial_peer_count {
warn!(
"Removed some ({}) trusted peers due to them also being bootstrap peers.",
initial_peer_count - final_peer_count
);
}
}
self.parse_development(&mut trusted_peers, &mut trusted_validators)?;
let cdn = self.parse_cdn::<N>().with_context(|| "Failed to parse given CDN URL")?;
let start = self.clone();
let genesis = task::spawn_blocking(move || start.parse_genesis::<N>()).await??;
let account = self.parse_private_key::<N>()?;
let node_type = self.parse_node_type();
let node_ip = self.node.unwrap_or(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, DEFAULT_NODE_PORT)));
let rest_ip = match self.norest {
true => None,
false => self.rest.or_else(|| Some("0.0.0.0:3030".parse().unwrap())),
};
let storage_mode = match &self.ledger_storage {
Some(path) => StorageMode::Custom(path.clone()),
None => match self.dev {
Some(id) => StorageMode::Development(id),
None => StorageMode::Production,
},
};
if self.node_data_storage.is_some() && !matches!(storage_mode, StorageMode::Custom(_)) {
if node_type == NodeType::Validator {
bail!("Custom path set for `--node-data-storage`, but not for `--ledger-storage`.")
} else {
warn!("Custom path set for `--node-data-storage`, but not for `--ledger-storage`. The latter will use the default path.");
}
} else if matches!(storage_mode, StorageMode::Custom(_)) && self.node_data_storage.is_none() {
if node_type == NodeType::Validator {
bail!("Custom path set for `--ledger-storage`, but not for `--node-data-storage`.");
} else {
warn!("Custom path set for `--ledger-storage`, but not for `--node-data-storage`. The latter will use the default path.");
}
}
let node_data_dir = parse_node_data_dir(&self.node_data_storage, N::ID, self.dev).with_context(|| "Failed to setup node configuration directory")?;
let data_path = node_data_dir.path();
if !data_path.exists() {
info!("Creating directore for node data storage at {data_path:?}");
std::fs::create_dir_all(data_path)
.with_context(|| format!("Failed to create directory for node data storage at {data_path:?}"))?
} else if !data_path.is_dir() {
bail!("Node data storage location at {data_path:?} is not a directory");
} else {
debug!("Using existing directory at {data_path:?} for node data storage");
}
Self::check_for_old_storage_format(&aleo_ledger_dir(N::ID, &storage_mode), &account.address(), &node_data_dir, self.dev, self.auto_migrate_node_data).with_context(|| "Node still uses the old storage format.")?;
let jwt_token = if self.nojwt {
None
} else if let Some(jwt_b64) = &self.jwt_secret {
let jwt_bytes = BASE64_STANDARD.decode(jwt_b64).map_err(|_| anyhow::anyhow!("Invalid JWT secret"))?;
if jwt_bytes.len() != 16 {
bail!("The JWT secret must be 16 bytes long");
}
let jwt_token = snarkos_node_rest::Claims::new(account.address(), Some(jwt_bytes), self.jwt_timestamp).to_jwt_string()?;
let path = Self::jwt_secret_path(&node_data_dir, &account.address());
std::fs::write(path, &jwt_token)?;
Some(jwt_token)
} else {
let jwt_token = snarkos_node_rest::Claims::new(account.address(), None, self.jwt_timestamp).to_jwt_string()?;
let path = Self::jwt_secret_path(&node_data_dir, &account.address());
std::fs::write(path, &jwt_token)? ;
Some(jwt_token)
};
if !self.nobanner {
println!("👛 Your Aleo address is {}.\n", account.address().to_string().bold());
println!(
"🧭 Starting {} on {} at {}.\n",
node_type.description().bold(),
N::NAME.bold(),
node_ip.to_string().bold()
);
if let Some(rest_ip) = rest_ip {
println!("🌐 Starting the REST server at {}.\n", rest_ip.to_string().bold());
if let Some(jwt_token) = jwt_token {
println!("🔑 Your one-time JWT token is {}\n", jwt_token.dimmed());
}
}
}
#[cfg(target_family = "unix")]
if node_type.is_validator() {
crate::helpers::check_open_files_limit(RECOMMENDED_MIN_NOFILES_LIMIT);
}
crate::helpers::check_validator_machine(node_type);
#[cfg(feature = "metrics")]
if self.metrics {
metrics::initialize_metrics(self.metrics_ip);
}
let dev_txs = match self.dev {
Some(_) => !self.no_dev_txs,
None => {
if self.no_dev_txs {
eprintln!("The '--no-dev-txs' flag is ignored because '--dev' is not set");
}
false
}
};
if !self.nodisplay && cdn.is_some() {
println!("🪧 The terminal UI will not start until the node has finished syncing from the CDN. If this step takes too long, consider restarting with `--nodisplay`.");
}
let signal_handler = SignalHandler::new();
let node = match node_type {
NodeType::Validator => Node::new_validator(node_ip, self.bft, rest_ip, self.rest_rps, account, &trusted_peers, &trusted_validators, genesis, cdn, storage_mode, node_data_dir, self.trusted_peers_only, self.auto_db_checkpoints.clone(), dev_txs, self.dev, signal_handler.clone()).await,
NodeType::Prover => Node::new_prover(node_ip, account, &trusted_peers, genesis, node_data_dir, self.trusted_peers_only, self.dev, signal_handler.clone()).await,
NodeType::Client => Node::new_client(node_ip, rest_ip, self.rest_rps, account, &trusted_peers, genesis, cdn, storage_mode, node_data_dir, self.trusted_peers_only, self.auto_db_checkpoints.clone(), self.dev, signal_handler.clone()).await,
NodeType::BootstrapClient => Node::new_bootstrap_client(node_ip, account, *genesis.header(), self.dev).await,
}?;
if !self.nodisplay {
Display::start(node.clone(), log_receiver, signal_handler.clone()).with_context(|| "Failed to start the display")?;
}
node.wait_for_signals(&signal_handler).await;
Ok(())
}
fn check_for_old_storage_format<N: Network>(
ledger_dir: &Path,
address: &Address<N>,
node_data_dir: &NodeDataDir,
dev: Option<u16>,
auto_migrate: bool,
) -> Result<()> {
let ledger_parent_dir = ledger_dir.parent().unwrap();
let old_router_cache_path = ledger_dir.join(node_data::LEGACY_ROUTER_PEER_CACHE_FILE);
let old_gateway_cache_path = ledger_dir.join(node_data::LEGACY_GATEWAY_PEER_CACHE_FILE);
let old_proposal_cache_path = ledger_dir.join(node_data::legacy_current_proposal_cache_file(N::ID, dev));
let old_jwt_secret_path = ledger_parent_dir.join(node_data::jwt_secret_file(address));
if auto_migrate {
if old_router_cache_path.exists() {
let new_router_cache_path = node_data_dir.router_peer_cache_path();
info!("Migrating node data file \"{old_router_cache_path:?}\" to \"{new_router_cache_path:?}\"");
fs::rename(old_router_cache_path, new_router_cache_path)
.with_context(|| "Failed to migrate node data file")?;
}
if old_gateway_cache_path.exists() {
let new_gateway_cache_path = node_data_dir.gateway_peer_cache_path();
info!("Migrating node data file \"{old_gateway_cache_path:?}\" to \"{new_gateway_cache_path:?}\"");
fs::rename(old_gateway_cache_path, new_gateway_cache_path)
.with_context(|| "Failed to migrate node data file")?;
}
if old_proposal_cache_path.exists() {
let new_proposal_cache_path = node_data_dir.current_proposal_cache_path();
info!("Migrating node data file \"{old_proposal_cache_path:?}\" to \"{new_proposal_cache_path:?}\"");
fs::rename(old_proposal_cache_path, new_proposal_cache_path)
.with_context(|| "Failed to migrate node data file")?;
}
if old_jwt_secret_path.exists() {
let new_jwt_secret_path = node_data_dir.jwt_secret_path(&address);
info!("Migrating node data file \"{old_jwt_secret_path:?}\" to \"{new_jwt_secret_path:?}\"");
fs::rename(old_jwt_secret_path, new_jwt_secret_path)
.with_context(|| "Failed to migrate node data file")?;
}
} else {
if old_router_cache_path.exists() {
let new_router_cache_path = node_data_dir.router_peer_cache_path();
bail!(
"Please migrate the node data file \"{old_router_cache_path:?}\" to \"{new_router_cache_path:?}\" before restarting, or restart with `--auto-migrate-node-data`."
);
}
if old_gateway_cache_path.exists() {
let new_gateway_cache_path = node_data_dir.gateway_peer_cache_path();
bail!(
"Please migrate the node data file \"{old_gateway_cache_path:?}\" to \"{new_gateway_cache_path:?}\" before restarting, or restart with `--auto-migrate-node-data`."
);
}
if old_proposal_cache_path.exists() {
let new_proposal_cache_path = node_data_dir.current_proposal_cache_path();
bail!(
"Please migrate the node data file \"{old_proposal_cache_path:?}\" to \"{new_proposal_cache_path:?}\" before restarting, or restart with `--auto-migrate-node-data`."
);
}
if old_jwt_secret_path.exists() {
let new_jwt_secret_path = node_data_dir.jwt_secret_path(&address);
bail!(
"Please migrate the node data file \"{old_jwt_secret_path:?}\" to \"{new_jwt_secret_path:?}\" before restarting, or restart with `--auto-migrate-node-data`."
);
}
}
Ok(())
}
fn runtime() -> Runtime {
let num_cores = num_cpus::get();
let (num_tokio_worker_threads, max_tokio_blocking_threads, num_rayon_cores_global) =
(2 * num_cores, 512, num_cores);
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")
}
}
fn check_permissions(path: &PathBuf) -> Result<(), snarkvm::prelude::Error> {
#[cfg(target_family = "unix")]
{
use std::os::unix::fs::PermissionsExt;
ensure!(path.exists(), "The file '{path:?}' does not exist");
crate::check_parent_permissions(path)?;
let permissions = path.metadata()?.permissions().mode();
ensure!(permissions & 0o777 == 0o600, "The file {path:?} must be readable only by the owner (0600)");
}
Ok(())
}
fn load_or_compute_genesis<N: Network>(
genesis_private_key: PrivateKey<N>,
committee: Committee<N>,
public_balances: indexmap::IndexMap<Address<N>, u64>,
bonded_balances: indexmap::IndexMap<Address<N>, (Address<N>, Address<N>, u64)>,
rng: &mut ChaChaRng,
) -> Result<Block<N>> {
let mut preimage = Vec::new();
preimage.extend(&N::ID.to_le_bytes());
preimage.extend(&to_bytes_le![N::GENESIS_COINBASE_TARGET]?);
preimage.extend(&to_bytes_le![N::GENESIS_PROOF_TARGET]?);
preimage.extend(genesis_private_key.to_bytes_le()?);
preimage.extend(committee.to_bytes_le()?);
preimage.extend(&to_bytes_le![public_balances.iter().collect::<Vec<(_, _)>>()]?);
preimage.extend(&to_bytes_le![
bonded_balances
.iter()
.flat_map(|(staker, (validator, withdrawal, amount))| to_bytes_le![staker, validator, withdrawal, amount])
.collect::<Vec<_>>()
]?);
match N::ID {
snarkvm::console::network::MainnetV0::ID => {
preimage.extend(snarkvm::parameters::mainnet::BondValidatorVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::mainnet::BondPublicVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::mainnet::UnbondPublicVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::mainnet::ClaimUnbondPublicVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::mainnet::SetValidatorStateVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::mainnet::TransferPrivateVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::mainnet::TransferPublicVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::mainnet::TransferPrivateToPublicVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::mainnet::TransferPublicToPrivateVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::mainnet::FeePrivateVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::mainnet::FeePublicVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::mainnet::InclusionVerifier::METADATA.as_bytes());
}
snarkvm::console::network::TestnetV0::ID => {
preimage.extend(snarkvm::parameters::testnet::BondValidatorVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::testnet::BondPublicVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::testnet::UnbondPublicVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::testnet::ClaimUnbondPublicVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::testnet::SetValidatorStateVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::testnet::TransferPrivateVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::testnet::TransferPublicVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::testnet::TransferPrivateToPublicVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::testnet::TransferPublicToPrivateVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::testnet::FeePrivateVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::testnet::FeePublicVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::testnet::InclusionVerifier::METADATA.as_bytes());
}
snarkvm::console::network::CanaryV0::ID => {
preimage.extend(snarkvm::parameters::canary::BondValidatorVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::canary::BondPublicVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::canary::UnbondPublicVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::canary::ClaimUnbondPublicVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::canary::SetValidatorStateVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::canary::TransferPrivateVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::canary::TransferPublicVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::canary::TransferPrivateToPublicVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::canary::TransferPublicToPrivateVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::canary::FeePrivateVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::canary::FeePublicVerifier::METADATA.as_bytes());
preimage.extend(snarkvm::parameters::canary::InclusionVerifier::METADATA.as_bytes());
}
_ => {
bail!("Unrecognized Network ID: {}", N::ID);
}
}
let hasher = snarkvm::console::algorithms::BHP256::<N>::setup("aleo.dev.block")?;
let hash = hasher.hash(&preimage.to_bits_le())?.to_string();
let load_block = |file_path| -> Result<Block<N>> {
let buffer = std::fs::read(file_path)?;
Block::from_bytes_le(&buffer)
};
let file_path = std::env::temp_dir().join(hash);
if file_path.exists() {
if let Ok(block) = load_block(&file_path) {
return Ok(block);
}
}
let vm = VM::from(ConsensusStore::<N, ConsensusMemory<N>>::open(StorageMode::new_test(None))?)?;
let block = vm.genesis_quorum(&genesis_private_key, committee, public_balances, bonded_balances, rng)?;
std::fs::write(&file_path, block.to_bytes_le()?)?;
Ok(block)
}
fn resolve_potential_hostnames(ip_or_hostname: &str) -> Result<SocketAddr> {
let trimmed = ip_or_hostname.trim();
match trimmed.to_socket_addrs() {
Ok(mut ip_iter) => {
let Some(ip) = ip_iter.next() else {
return Err(anyhow!("The supplied trusted hostname ('{trimmed}') does not reference any ip."));
};
Ok(ip)
}
Err(e) => Err(anyhow!("The supplied trusted hostname or IP ('{trimmed}') is malformed: {e}")),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::commands::{CLI, Command};
use snarkvm::prelude::MainnetV0;
use ureq::http;
type CurrentNetwork = MainnetV0;
#[test]
fn test_parse_trusted_addrs() {
let config = Start::try_parse_from(["snarkos", "--peers", ""].iter()).unwrap();
assert!(config.parse_trusted_addrs(&config.peers).is_ok());
assert!(config.parse_trusted_addrs(&config.peers).unwrap().is_empty());
let config = Start::try_parse_from(["snarkos", "--peers", "1.2.3.4:5"].iter()).unwrap();
assert!(config.parse_trusted_addrs(&config.peers).is_ok());
assert_eq!(config.parse_trusted_addrs(&config.peers).unwrap(), vec![
SocketAddr::from_str("1.2.3.4:5").unwrap()
]);
let config = Start::try_parse_from(["snarkos", "--peers", "1.2.3.4:5,6.7.8.9:0"].iter()).unwrap();
assert!(config.parse_trusted_addrs(&config.peers).is_ok());
assert_eq!(config.parse_trusted_addrs(&config.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_trusted_validators() {
let config = Start::try_parse_from(["snarkos", "--validators", ""].iter()).unwrap();
assert!(config.parse_trusted_addrs(&config.validators).is_ok());
assert!(config.parse_trusted_addrs(&config.validators).unwrap().is_empty());
let config = Start::try_parse_from(["snarkos", "--validators", "1.2.3.4:5"].iter()).unwrap();
assert!(config.parse_trusted_addrs(&config.validators).is_ok());
assert_eq!(config.parse_trusted_addrs(&config.validators).unwrap(), vec![
SocketAddr::from_str("1.2.3.4:5").unwrap()
]);
let config = Start::try_parse_from(["snarkos", "--validators", "1.2.3.4:5,6.7.8.9:0"].iter()).unwrap();
assert!(config.parse_trusted_addrs(&config.validators).is_ok());
assert_eq!(config.parse_trusted_addrs(&config.validators).unwrap(), vec![
SocketAddr::from_str("1.2.3.4:5").unwrap(),
SocketAddr::from_str("6.7.8.9:0").unwrap()
]);
}
#[test]
fn test_parse_log_filter() {
let result = Start::try_parse_from(["snarkos", "--verbosity=5", "--log-filter=warn"].iter());
assert!(result.is_err(), "Must not be able to set log-filter and verbosity at the same time");
let config = Start::try_parse_from(["snarkos", "--verbosity=5"].iter()).unwrap();
assert_eq!(config.verbosity, 5);
let config = Start::try_parse_from(["snarkos", "--log-filter=snarkos=warn"].iter()).unwrap();
assert_eq!(config.log_filter, Some("snarkos=warn".to_string()));
}
#[test]
fn test_parse_cdn() -> Result<()> {
let config = Start::try_parse_from(["snarkos", "--validator", "--private-key", "aleo1xx"].iter()).unwrap();
assert!(config.parse_cdn::<CurrentNetwork>()?.is_some());
let config =
Start::try_parse_from(["snarkos", "--validator", "--private-key", "aleo1xx", "--cdn", "url"].iter())
.unwrap();
assert!(config.parse_cdn::<CurrentNetwork>()?.is_some());
let config = Start::try_parse_from(["snarkos", "--validator", "--private-key", "aleo1xx", "--nocdn"].iter())?;
assert!(config.parse_cdn::<CurrentNetwork>()?.is_none());
let config =
Start::try_parse_from(["snarkos", "--dev", "0", "--validator", "--private-key", "aleo1xx"].iter()).unwrap();
assert!(config.parse_cdn::<CurrentNetwork>()?.is_none());
let config = Start::try_parse_from(
["snarkos", "--dev", "0", "--validator", "--private-key", "aleo1xx", "--cdn", "url"].iter(),
)
.unwrap();
assert!(config.parse_cdn::<CurrentNetwork>()?.is_none());
let config = Start::try_parse_from(
["snarkos", "--dev", "0", "--validator", "--private-key", "aleo1xx", "--nocdn"].iter(),
)?;
assert!(config.parse_cdn::<CurrentNetwork>()?.is_none());
let config = Start::try_parse_from(["snarkos", "--prover", "--private-key", "aleo1xx"].iter())?;
assert!(config.parse_cdn::<CurrentNetwork>()?.is_none());
let config = Start::try_parse_from(["snarkos", "--prover", "--private-key", "aleo1xx", "--cdn", "url"].iter())?;
assert!(config.parse_cdn::<CurrentNetwork>()?.is_none());
let config = Start::try_parse_from(["snarkos", "--prover", "--private-key", "aleo1xx", "--nocdn"].iter())?;
assert!(config.parse_cdn::<CurrentNetwork>()?.is_none());
let config =
Start::try_parse_from(["snarkos", "--dev", "0", "--prover", "--private-key", "aleo1xx"].iter()).unwrap();
assert!(config.parse_cdn::<CurrentNetwork>()?.is_none());
let config = Start::try_parse_from(
["snarkos", "--dev", "0", "--prover", "--private-key", "aleo1xx", "--cdn", "url"].iter(),
)
.unwrap();
assert!(config.parse_cdn::<CurrentNetwork>()?.is_none());
let config =
Start::try_parse_from(["snarkos", "--dev", "0", "--prover", "--private-key", "aleo1xx", "--nocdn"].iter())
.unwrap();
assert!(config.parse_cdn::<CurrentNetwork>()?.is_none());
let config = Start::try_parse_from(["snarkos", "--client", "--private-key", "aleo1xx"].iter()).unwrap();
assert!(config.parse_cdn::<CurrentNetwork>()?.is_some());
let config =
Start::try_parse_from(["snarkos", "--client", "--private-key", "aleo1xx", "--cdn", "url"].iter()).unwrap();
assert!(config.parse_cdn::<CurrentNetwork>()?.is_some());
let config =
Start::try_parse_from(["snarkos", "--client", "--private-key", "aleo1xx", "--nocdn"].iter()).unwrap();
assert!(config.parse_cdn::<CurrentNetwork>()?.is_none());
let config =
Start::try_parse_from(["snarkos", "--dev", "0", "--client", "--private-key", "aleo1xx"].iter()).unwrap();
assert!(config.parse_cdn::<CurrentNetwork>()?.is_none());
let config = Start::try_parse_from(
["snarkos", "--dev", "0", "--client", "--private-key", "aleo1xx", "--cdn", "url"].iter(),
)
.unwrap();
assert!(config.parse_cdn::<CurrentNetwork>()?.is_none());
let config =
Start::try_parse_from(["snarkos", "--dev", "0", "--client", "--private-key", "aleo1xx", "--nocdn"].iter())
.unwrap();
assert!(config.parse_cdn::<CurrentNetwork>()?.is_none());
let config = Start::try_parse_from(["snarkos"].iter()).unwrap();
assert!(config.parse_cdn::<CurrentNetwork>()?.is_some());
let config = Start::try_parse_from(["snarkos", "--cdn", "url"].iter()).unwrap();
assert!(config.parse_cdn::<CurrentNetwork>()?.is_some());
let config = Start::try_parse_from(["snarkos", "--nocdn"].iter()).unwrap();
assert!(config.parse_cdn::<CurrentNetwork>()?.is_none());
let config = Start::try_parse_from(["snarkos", "--dev", "0"].iter()).unwrap();
assert!(config.parse_cdn::<CurrentNetwork>()?.is_none());
let config = Start::try_parse_from(["snarkos", "--dev", "0", "--cdn", "url"].iter()).unwrap();
assert!(config.parse_cdn::<CurrentNetwork>()?.is_none());
let config = Start::try_parse_from(["snarkos", "--dev", "0", "--nocdn"].iter()).unwrap();
assert!(config.parse_cdn::<CurrentNetwork>()?.is_none());
Ok(())
}
#[test]
fn test_parse_development_and_genesis() {
let prod_genesis = Block::from_bytes_le(CurrentNetwork::genesis_bytes()).unwrap();
let mut trusted_peers = vec![];
let mut trusted_validators = vec![];
let mut config = Start::try_parse_from(["snarkos"].iter()).unwrap();
config.parse_development(&mut trusted_peers, &mut trusted_validators).unwrap();
let candidate_genesis = config.parse_genesis::<CurrentNetwork>().unwrap();
assert_eq!(trusted_peers.len(), 0);
assert_eq!(trusted_validators.len(), 0);
assert_eq!(candidate_genesis, prod_genesis);
let _config = Start::try_parse_from(["snarkos", "--dev", ""].iter()).unwrap_err();
let mut trusted_peers = vec![];
let mut trusted_validators = vec![];
let mut config = Start::try_parse_from(["snarkos", "--dev", "1", "--validator"].iter()).unwrap();
config.parse_development(&mut trusted_peers, &mut trusted_validators).unwrap();
assert_eq!(config.rest, Some(SocketAddr::from_str("0.0.0.0:3031").unwrap()));
assert_eq!(trusted_validators.len(), 3);
let mut trusted_peers = vec![];
let mut trusted_validators = vec![];
let mut config =
Start::try_parse_from(["snarkos", "--dev", "1", "--rest", "127.0.0.1:8080", "--validator"].iter()).unwrap();
config.parse_development(&mut trusted_peers, &mut trusted_validators).unwrap();
assert_eq!(config.rest, Some(SocketAddr::from_str("127.0.0.1:8080").unwrap()));
assert_eq!(trusted_validators.len(), 3);
let mut trusted_peers = vec![];
let mut trusted_validators = vec![];
let mut config = Start::try_parse_from(["snarkos", "--dev", "1", "--norest", "--validator"].iter()).unwrap();
config.parse_development(&mut trusted_peers, &mut trusted_validators).unwrap();
assert!(config.rest.is_none());
assert_eq!(trusted_validators.len(), 3);
let mut trusted_peers = vec![];
let mut trusted_validators = vec![];
let mut config = Start::try_parse_from(["snarkos", "--dev", "5"].iter()).unwrap();
config.parse_development(&mut trusted_peers, &mut trusted_validators).unwrap();
let expected_genesis = config.parse_genesis::<CurrentNetwork>().unwrap();
assert_eq!(config.node, Some(SocketAddr::from_str("0.0.0.0:4135").unwrap()));
assert_eq!(config.rest, Some(SocketAddr::from_str("0.0.0.0:3035").unwrap()));
assert_eq!(trusted_peers.len(), 2);
assert_eq!(trusted_validators.len(), 0);
assert!(!config.validator);
assert!(!config.prover);
assert!(!config.client);
assert_ne!(expected_genesis, prod_genesis);
let mut trusted_peers = vec![];
let mut trusted_validators = vec![];
let mut config =
Start::try_parse_from(["snarkos", "--dev", "1", "--validator", "--private-key", ""].iter()).unwrap();
config.parse_development(&mut trusted_peers, &mut trusted_validators).unwrap();
let genesis = config.parse_genesis::<CurrentNetwork>().unwrap();
assert_eq!(config.node, Some(SocketAddr::from_str("0.0.0.0:4131").unwrap()));
assert_eq!(config.rest, Some(SocketAddr::from_str("0.0.0.0:3031").unwrap()));
assert_eq!(trusted_peers.len(), 0);
assert_eq!(trusted_validators.len(), 3);
assert!(config.validator);
assert!(!config.prover);
assert!(!config.client);
assert_eq!(genesis, expected_genesis);
let mut trusted_peers = vec![];
let mut trusted_validators = vec![];
let mut config =
Start::try_parse_from(["snarkos", "--dev", "6", "--prover", "--private-key", ""].iter()).unwrap();
config.parse_development(&mut trusted_peers, &mut trusted_validators).unwrap();
let genesis = config.parse_genesis::<CurrentNetwork>().unwrap();
assert_eq!(config.node, Some(SocketAddr::from_str("0.0.0.0:4136").unwrap()));
assert_eq!(config.rest, Some(SocketAddr::from_str("0.0.0.0:3036").unwrap()));
assert_eq!(trusted_peers.len(), 2);
assert_eq!(trusted_validators.len(), 0);
assert!(!config.validator);
assert!(config.prover);
assert!(!config.client);
assert_eq!(genesis, expected_genesis);
let mut trusted_peers = vec![];
let mut trusted_validators = vec![];
let mut config =
Start::try_parse_from(["snarkos", "--dev", "10", "--client", "--private-key", ""].iter()).unwrap();
config.parse_development(&mut trusted_peers, &mut trusted_validators).unwrap();
let genesis = config.parse_genesis::<CurrentNetwork>().unwrap();
assert_eq!(config.node, Some(SocketAddr::from_str("0.0.0.0:4140").unwrap()));
assert_eq!(config.rest, Some(SocketAddr::from_str("0.0.0.0:3040").unwrap()));
assert_eq!(trusted_peers.len(), 2);
assert_eq!(trusted_validators.len(), 0);
assert!(!config.validator);
assert!(!config.prover);
assert!(config.client);
assert_eq!(genesis, expected_genesis);
}
#[test]
fn test_parse_development_num_clients_and_peers() {
let result = Start::try_parse_from(
["snarkos", "--validator", "--dev", "1", "--peers", "127.0.0.1:3030", "--dev-num-clients", "1"].iter(),
);
assert!(result.is_err());
}
#[test]
fn clap_snarkos_start() {
let arg_vec = vec![
"snarkos",
"start",
"--nodisplay",
"--dev",
"2",
"--validator",
"--private-key",
"PRIVATE_KEY",
"--cdn",
"CDN",
"--peers",
"IP1,IP2,IP3",
"--validators",
"IP1,IP2,IP3",
"--rest",
"127.0.0.1:3030",
];
let cli = CLI::parse_from(arg_vec);
let Command::Start(start) = cli.command else {
panic!("Unexpected result of clap parsing!");
};
assert!(start.nodisplay);
assert_eq!(start.dev, Some(2));
assert!(start.validator);
assert_eq!(start.private_key.as_deref(), Some("PRIVATE_KEY"));
assert_eq!(start.cdn, Some(http::Uri::try_from("CDN").unwrap()));
assert_eq!(start.rest, Some("127.0.0.1:3030".parse().unwrap()));
assert_eq!(start.network, 0);
assert_eq!(start.peers, Some("IP1,IP2,IP3".to_string()));
assert_eq!(start.validators, Some("IP1,IP2,IP3".to_string()));
}
#[test]
fn test_parse_development_client_validators() {
let mut client1_config =
Start::try_parse_from(["snarkos", "--dev", "10", "--client", "--private-key", ""].iter()).unwrap();
let mut trusted_peers1 = vec![];
let mut trusted_validators1 = vec![];
client1_config.parse_development(&mut trusted_peers1, &mut trusted_validators1).unwrap();
let mut client2_config =
Start::try_parse_from(["snarkos", "--dev", "11", "--client", "--private-key", ""].iter()).unwrap();
let mut trusted_peers2 = vec![];
let mut trusted_validators2 = vec![];
client2_config.parse_development(&mut trusted_peers2, &mut trusted_validators2).unwrap();
assert_ne!(trusted_peers1, trusted_peers2);
}
#[test]
fn parse_peers_when_ips() {
let arg_vec = vec!["snarkos", "start", "--peers", "127.0.0.1:3030,127.0.0.2:3030"];
let cli = CLI::parse_from(arg_vec);
if let Command::Start(start) = cli.command {
let peers = start.parse_trusted_addrs(&start.peers);
assert!(peers.is_ok());
assert_eq!(peers.unwrap().len(), 2, "Expected two peers");
} else {
panic!("Unexpected result of clap parsing!");
}
}
#[test]
fn parse_peers_when_hostnames() {
let arg_vec = vec!["snarkos", "start", "--peers", "www.example.com:4130,www.google.com:4130"];
let cli = CLI::parse_from(arg_vec);
if let Command::Start(start) = cli.command {
let peers = start.parse_trusted_addrs(&start.peers);
assert!(peers.is_ok());
assert_eq!(peers.unwrap().len(), 2, "Expected two peers");
} else {
panic!("Unexpected result of clap parsing!");
}
}
#[test]
fn parse_peers_when_mixed_and_with_whitespaces() {
let arg_vec = vec!["snarkos", "start", "--peers", " 127.0.0.1:3030, www.google.com:4130 "];
let cli = CLI::parse_from(arg_vec);
if let Command::Start(start) = cli.command {
let peers = start.parse_trusted_addrs(&start.peers);
assert!(peers.is_ok());
assert_eq!(peers.unwrap().len(), 2, "Expected two peers");
} else {
panic!("Unexpected result of clap parsing!");
}
}
#[test]
fn parse_peers_when_unknown_hostname_gracefully() {
let arg_vec = vec!["snarkos", "start", "--peers", "banana.cake.eafafdaeefasdfasd.com"];
let cli = CLI::parse_from(arg_vec);
if let Command::Start(start) = cli.command {
assert!(start.parse_trusted_addrs(&start.peers).is_err());
} else {
panic!("Unexpected result of clap parsing!");
}
}
}