use crate::{
environment::{Client, ClientTrial, Environment, Miner, MinerTrial, Operator, OperatorTrial, Prover, ProverTrial, SyncNode},
helpers::{NodeType, Updater},
network::Server,
Display,
};
use snarkos_storage::storage::rocksdb::RocksDB;
use snarkvm::dpc::{prelude::*, testnet2::Testnet2};
use anyhow::{anyhow, Result};
use colored::*;
use crossterm::tty::IsTty;
use std::{io, net::SocketAddr, path::PathBuf, str::FromStr};
use structopt::StructOpt;
use tokio::{signal, sync::mpsc, task};
use tracing_subscriber::EnvFilter;
#[derive(StructOpt, Debug)]
#[structopt(name = "snarkos", author = "The Aleo Team <hello@aleo.org>", setting = structopt::clap::AppSettings::ColoredHelp)]
pub struct Node {
#[structopt(long = "connect")]
pub connect: Option<String>,
#[structopt(long = "miner")]
pub miner: Option<String>,
#[structopt(long = "operator")]
pub operator: Option<String>,
#[structopt(long = "prover")]
pub prover: Option<String>,
#[structopt(long = "pool")]
pub pool: Option<SocketAddr>,
#[structopt(default_value = "2", long = "network")]
pub network: u16,
#[structopt(parse(try_from_str), default_value = "0.0.0.0:4132", long = "node")]
pub node: SocketAddr,
#[structopt(parse(try_from_str), default_value = "0.0.0.0:3032", long = "rpc")]
pub rpc: SocketAddr,
#[structopt(default_value = "root", long = "username")]
pub rpc_username: String,
#[structopt(default_value = "pass", long = "password")]
pub rpc_password: String,
#[structopt(default_value = "2", long = "verbosity")]
pub verbosity: u8,
#[structopt(long)]
pub dev: Option<u16>,
#[structopt(long)]
pub display: bool,
#[structopt(long)]
pub norpc: bool,
#[structopt(hidden = true, long)]
pub trial: bool,
#[structopt(hidden = true, long)]
pub sync: bool,
#[structopt(subcommand)]
commands: Option<Command>,
}
impl Node {
pub async fn start(self) -> Result<()> {
match self.commands {
Some(command) => {
println!("{}", command.parse()?);
Ok(())
}
None => match &self.get_node_type() {
(NodeType::Client, false) => self.start_server::<Testnet2, Client<Testnet2>>(&None).await,
(NodeType::Miner, false) => self.start_server::<Testnet2, Miner<Testnet2>>(&self.miner).await,
(NodeType::Operator, false) => self.start_server::<Testnet2, Operator<Testnet2>>(&self.operator).await,
(NodeType::Prover, false) => self.start_server::<Testnet2, Prover<Testnet2>>(&self.prover).await,
(NodeType::Client, true) => self.start_server::<Testnet2, ClientTrial<Testnet2>>(&None).await,
(NodeType::Miner, true) => self.start_server::<Testnet2, MinerTrial<Testnet2>>(&self.miner).await,
(NodeType::Operator, true) => self.start_server::<Testnet2, OperatorTrial<Testnet2>>(&self.operator).await,
(NodeType::Prover, true) => self.start_server::<Testnet2, ProverTrial<Testnet2>>(&self.prover).await,
(NodeType::Sync, _) => self.start_server::<Testnet2, SyncNode<Testnet2>>(&None).await,
_ => panic!("Unsupported node configuration"),
},
}
}
fn get_node_type(&self) -> (NodeType, bool) {
(
match (self.network, &self.miner, &self.operator, &self.prover, self.sync) {
(2, None, None, None, false) => NodeType::Client,
(2, Some(_), None, None, false) => NodeType::Miner,
(2, None, Some(_), None, false) => NodeType::Operator,
(2, None, None, Some(_), false) => NodeType::Prover,
(2, None, None, None, true) => NodeType::Sync,
_ => panic!("Unsupported node configuration"),
},
self.trial,
)
}
pub(crate) fn ledger_storage_path(&self, _local_ip: SocketAddr) -> PathBuf {
cfg_if::cfg_if! {
if #[cfg(feature = "test")] {
PathBuf::from(format!("/tmp/snarkos-test-ledger-{}", _local_ip.port()))
} else {
aleo_std::aleo_ledger_dir(self.network, self.dev)
}
}
}
pub(crate) fn operator_storage_path(&self, _local_ip: SocketAddr) -> PathBuf {
cfg_if::cfg_if! {
if #[cfg(feature = "test")] {
PathBuf::from(format!("/tmp/snarkos-test-operator-{}", _local_ip.port()))
} else {
aleo_std::aleo_operator_dir(self.network, self.dev)
}
}
}
pub(crate) fn prover_storage_path(&self, _local_ip: SocketAddr) -> PathBuf {
cfg_if::cfg_if! {
if #[cfg(feature = "test")] {
PathBuf::from(format!("/tmp/snarkos-test-prover-{}", _local_ip.port()))
} else {
aleo_std::aleo_prover_dir(self.network, self.dev)
}
}
}
async fn start_server<N: Network, E: Environment>(&self, address: &Option<String>) -> Result<()> {
println!("{}", crate::display::welcome_message());
let address = match (E::NODE_TYPE, address) {
(NodeType::Miner, Some(address)) | (NodeType::Operator, Some(address)) | (NodeType::Prover, Some(address)) => {
let address = Address::<N>::from_str(address)?;
println!("Your Aleo address is {}.\n", address);
Some(address)
}
_ => None,
};
println!("Starting {} on {}.", E::NODE_TYPE.description(), N::NETWORK_NAME);
println!("{}", crate::display::notification_message::<N>(address));
let server = Server::<N, E>::initialize(self, address, self.pool).await?;
let server_clone = server.clone();
handle_signals(server_clone);
if self.display {
println!("\nThe snarkOS console is initializing...\n");
let _display = Display::<N, E>::start(server.clone(), self.verbosity)?;
};
if let Some(peer_ip) = &self.connect {
let _ = server.connect_to(peer_ip.parse().unwrap()).await;
}
std::future::pending::<()>().await;
Ok(())
}
}
pub fn initialize_logger(verbosity: u8, log_sender: Option<mpsc::Sender<Vec<u8>>>) {
match verbosity {
0 => std::env::set_var("RUST_LOG", "info"),
1 => std::env::set_var("RUST_LOG", "debug"),
2 | 3 => std::env::set_var("RUST_LOG", "trace"),
_ => std::env::set_var("RUST_LOG", "info"),
};
let filter = EnvFilter::from_default_env()
.add_directive("mio=off".parse().unwrap())
.add_directive("tokio_util=off".parse().unwrap())
.add_directive("hyper::proto::h1::conn=off".parse().unwrap())
.add_directive("hyper::proto::h1::decode=off".parse().unwrap())
.add_directive("hyper::proto::h1::io=off".parse().unwrap())
.add_directive("hyper::proto::h1::role=off".parse().unwrap())
.add_directive("jsonrpsee=off".parse().unwrap());
let _ = tracing_subscriber::fmt()
.with_env_filter(filter)
.with_ansi(log_sender.is_none() && io::stdout().is_tty())
.with_writer(move || LogWriter::new(&log_sender))
.with_target(verbosity == 3)
.try_init();
}
#[derive(StructOpt, Debug)]
pub enum Command {
#[structopt(name = "clean", about = "Removes the ledger files from storage")]
Clean(Clean),
#[structopt(name = "update", about = "Updates snarkOS to the latest version")]
Update(Update),
#[structopt(name = "experimental", about = "Experimental features")]
Experimental(Experimental),
#[structopt(name = "miner", about = "Miner commands and settings")]
Miner(MinerSubcommand),
}
impl Command {
pub fn parse(self) -> Result<String> {
match self {
Self::Clean(command) => command.parse(),
Self::Update(command) => command.parse(),
Self::Experimental(command) => command.parse(),
Self::Miner(command) => command.parse(),
}
}
}
enum LogWriter {
Stdout(io::Stdout),
Sender(mpsc::Sender<Vec<u8>>),
}
impl LogWriter {
fn new(log_sender: &Option<mpsc::Sender<Vec<u8>>>) -> Self {
if let Some(sender) = log_sender {
Self::Sender(sender.clone())
} else {
Self::Stdout(io::stdout())
}
}
}
impl io::Write for LogWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self {
Self::Stdout(stdout) => stdout.write(buf),
Self::Sender(sender) => {
let log = buf.to_vec();
let _ = sender.try_send(log);
Ok(buf.len())
}
}
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[derive(StructOpt, Debug)]
pub struct Clean {
#[structopt(default_value = "2", long = "network")]
pub network: u16,
#[structopt(long)]
pub dev: Option<u16>,
}
impl Clean {
pub fn parse(self) -> Result<String> {
Self::remove_ledger(self.network, self.dev)
}
fn remove_ledger(network: u16, dev: Option<u16>) -> Result<String> {
let path = aleo_std::aleo_ledger_dir(network, dev);
if path.exists() {
match std::fs::remove_dir_all(&path) {
Ok(_) => Ok(format!("Successfully removed the ledger files from storage. ({})", path.display())),
Err(error) => Err(anyhow!(
"Failed to remove the ledger files from storage. ({})\n{}",
path.display(),
error
)),
}
} else {
Ok(format!("No ledger files were found in storage. ({})", path.display()))
}
}
}
#[derive(StructOpt, Debug)]
pub struct Update {
#[structopt(short = "l", long)]
list: bool,
#[structopt(short = "q", long)]
quiet: bool,
#[structopt(short = "v", long)]
version: Option<String>,
}
impl Update {
pub fn parse(self) -> Result<String> {
match self.list {
true => match Updater::show_available_releases() {
Ok(output) => Ok(output),
Err(error) => Ok(format!("Failed to list the available versions of snarkOS\n{}\n", error)),
},
false => {
let result = Updater::update_to_release(!self.quiet, self.version);
if !self.quiet {
match result {
Ok(status) => {
if status.uptodate() {
Ok("\nsnarkOS is already on the latest version".to_string())
} else if status.updated() {
Ok(format!("\nsnarkOS has updated to version {}", status.version()))
} else {
Ok(String::new())
}
}
Err(e) => Ok(format!("\nFailed to update snarkOS to the latest version\n{}\n", e)),
}
} else {
Ok(String::new())
}
}
}
}
}
#[derive(StructOpt, Debug)]
pub struct Experimental {
#[structopt(subcommand)]
commands: ExperimentalCommands,
}
impl Experimental {
pub fn parse(self) -> Result<String> {
match self.commands {
ExperimentalCommands::NewAccount(command) => command.parse(),
}
}
}
#[derive(StructOpt, Debug)]
pub enum ExperimentalCommands {
#[structopt(name = "new_account", about = "Generate a new Aleo account.")]
NewAccount(NewAccount),
}
#[derive(StructOpt, Debug)]
pub struct NewAccount {}
impl NewAccount {
pub fn parse(self) -> Result<String> {
let account = Account::<Testnet2>::new(&mut rand::thread_rng());
let mut output = "".to_string();
output += &format!(
"\n {:>12}\n",
"Attention - Remember to store this account private key and view key.".red().bold()
);
output += &format!("\n {:>12} {}\n", "Private Key".cyan().bold(), account.private_key());
output += &format!(" {:>12} {}\n", "View Key".cyan().bold(), account.view_key());
output += &format!(" {:>12} {}\n", "Address".cyan().bold(), account.address());
Ok(output)
}
}
#[derive(StructOpt, Debug)]
pub struct MinerSubcommand {
#[structopt(subcommand)]
commands: MinerCommands,
}
impl MinerSubcommand {
pub fn parse(self) -> Result<String> {
match self.commands {
MinerCommands::Stats(command) => command.parse(),
}
}
}
#[derive(StructOpt, Debug)]
pub enum MinerCommands {
#[structopt(name = "stats", about = "Prints statistics for the miner.")]
Stats(MinerStats),
}
#[derive(StructOpt, Debug)]
pub struct MinerStats {
#[structopt()]
address: String,
}
impl MinerStats {
pub fn parse(self) -> Result<String> {
let miner = Address::<Testnet2>::from_str(&self.address)?;
let node = Node::from_iter(&["snarkos", "--norpc", "--verbosity", "0"]);
let ip = "0.0.0.0:1000".parse().unwrap();
let ledger_storage_path = node.ledger_storage_path(ip);
let ledger = snarkos_storage::LedgerState::<Testnet2>::open_reader::<RocksDB, _>(ledger_storage_path).unwrap();
let prover_storage_path = node.prover_storage_path(ip);
let prover = snarkos_storage::ProverState::<Testnet2>::open_writer::<RocksDB, _>(prover_storage_path).unwrap();
let latest_block_height = ledger.latest_block_height();
let mut confirmed = vec![];
let mut pending = vec![];
for (block_height, record) in prover.to_coinbase_records() {
if let Ok(true) = ledger.contains_commitment(&record.commitment()) {
if record.owner() == miner {
match block_height + 2048 < latest_block_height {
true => confirmed.push((block_height, record)),
false => pending.push((block_height, record)),
}
}
}
}
return Ok(format!(
"Mining Report (confirmed_blocks = {}, pending_blocks = {}, miner_address = {})",
confirmed.len(),
pending.len(),
miner
));
}
}
fn handle_signals<N: Network, E: Environment>(server: Server<N, E>) {
task::spawn(async move {
match signal::ctrl_c().await {
Ok(()) => {
server.shut_down().await;
std::process::exit(0);
}
Err(error) => error!("tokio::signal::ctrl_c encountered an error: {}", error),
}
});
}