mod faucet_server;
#[cfg(feature = "initial-data")]
pub(crate) mod gutenberger;
#[cfg(feature = "distribution")]
mod token_distribution;
use clap::{Parser, Subcommand};
use color_eyre::eyre::{bail, eyre, Result};
use faucet_server::{restart_faucet_server, run_faucet_server};
use indicatif::ProgressBar;
use sn_client::{
acc_packet::load_account_wallet_or_create_with_mnemonic, fund_faucet_from_genesis_wallet, send,
Client, ClientEvent, ClientEventsBroadcaster, ClientEventsReceiver,
};
use sn_logging::{Level, LogBuilder, LogOutputDest};
use sn_peers_acquisition::PeersArgs;
use sn_transfers::{get_faucet_data_dir, HotWallet, MainPubkey, NanoTokens, Transfer};
use std::{path::PathBuf, time::Duration};
use tokio::{sync::broadcast::error::RecvError, task::JoinHandle};
use tracing::{debug, error, info};
#[tokio::main]
async fn main() -> Result<()> {
let opt = Opt::parse();
let bootstrap_peers = opt.peers.get_peers().await?;
let bootstrap_peers = if bootstrap_peers.is_empty() {
None
} else {
Some(bootstrap_peers)
};
let logging_targets = vec![
("faucet".to_string(), Level::TRACE),
("sn_client".to_string(), Level::TRACE),
("sn_faucet".to_string(), Level::TRACE),
("sn_networking".to_string(), Level::DEBUG),
("sn_build_info".to_string(), Level::TRACE),
("sn_logging".to_string(), Level::TRACE),
("sn_peers_acquisition".to_string(), Level::TRACE),
("sn_protocol".to_string(), Level::TRACE),
("sn_registers".to_string(), Level::TRACE),
("sn_transfers".to_string(), Level::TRACE),
];
let mut log_builder = LogBuilder::new(logging_targets);
log_builder.output_dest(opt.log_output_dest);
let _log_handles = log_builder.initialize()?;
debug!(
"faucet built with git version: {}",
sn_build_info::git_info()
);
println!(
"faucet built with git version: {}",
sn_build_info::git_info()
);
info!("Instantiating a SAFE Test Faucet...");
let secret_key = bls::SecretKey::random();
let broadcaster = ClientEventsBroadcaster::default();
let (progress_bar, handle) = spawn_connection_progress_bar(broadcaster.subscribe());
let result = Client::new(secret_key, bootstrap_peers, None, Some(broadcaster)).await;
let client = match result {
Ok(client) => client,
Err(err) => {
progress_bar.finish_with_message("Could not connect to the network");
error!("Failed to get Client with err {err:?}");
return Err(err.into());
}
};
handle.await?;
let root_dir = get_faucet_data_dir();
let mut funded_faucet = match load_account_wallet_or_create_with_mnemonic(&root_dir, None) {
Ok(wallet) => wallet,
Err(err) => {
println!("failed to load wallet for faucet! with error {err:?}");
error!("failed to load wallet for faucet! with error {err:?}");
return Err(err.into());
}
};
fund_faucet_from_genesis_wallet(&client, &mut funded_faucet).await?;
if let Err(err) = faucet_cmds(opt.cmd.clone(), &client, funded_faucet).await {
error!("Failed to run faucet cmd {:?} with err {err:?}", opt.cmd);
eprintln!("Failed to run faucet cmd {:?} with err {err:?}", opt.cmd);
}
Ok(())
}
fn spawn_connection_progress_bar(mut rx: ClientEventsReceiver) -> (ProgressBar, JoinHandle<()>) {
let progress_bar = ProgressBar::new_spinner();
let progress_bar_clone = progress_bar.clone();
progress_bar.enable_steady_tick(Duration::from_millis(120));
progress_bar.set_message("Connecting to The SAFE Network...");
let new_style = progress_bar.style().tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈🔗");
progress_bar.set_style(new_style);
progress_bar.set_message("Connecting to The SAFE Network...");
let handle = tokio::spawn(async move {
let mut peers_connected = 0;
loop {
match rx.recv().await {
Ok(ClientEvent::ConnectedToNetwork) => {
progress_bar.finish_with_message("Connected to the Network");
break;
}
Ok(ClientEvent::PeerAdded {
max_peers_to_connect,
}) => {
peers_connected += 1;
progress_bar.set_message(format!(
"{peers_connected}/{max_peers_to_connect} initial peers found.",
));
}
Err(RecvError::Lagged(_)) => {
}
Err(RecvError::Closed) => {
progress_bar.finish_with_message("Could not connect to the network");
break;
}
_ => {}
}
}
});
(progress_bar_clone, handle)
}
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Opt {
#[clap(long, value_parser = parse_log_output, verbatim_doc_comment, default_value = "data-dir")]
pub log_output_dest: LogOutputDest,
#[command(flatten)]
peers: PeersArgs,
#[clap(subcommand)]
pub cmd: SubCmd,
}
#[derive(Subcommand, Debug, Clone)]
enum SubCmd {
ClaimGenesis,
Send {
#[clap(name = "amount")]
amount: String,
#[clap(name = "to")]
to: String,
},
Server,
RestartServer,
}
async fn faucet_cmds(cmds: SubCmd, client: &Client, funded_wallet: HotWallet) -> Result<()> {
match cmds {
SubCmd::ClaimGenesis => {
claim_genesis(client, funded_wallet).await?;
}
SubCmd::Send { amount, to } => {
send_tokens(client, funded_wallet, &amount, &to).await?;
}
SubCmd::Server => {
run_faucet_server(client).await?;
}
SubCmd::RestartServer => {
restart_faucet_server(client).await?;
}
}
Ok(())
}
async fn claim_genesis(client: &Client, mut wallet: HotWallet) -> Result<()> {
for i in 1..6 {
if let Err(e) = fund_faucet_from_genesis_wallet(client, &mut wallet).await {
println!("Failed to claim genesis: {e}");
} else {
println!("Genesis claimed!");
return Ok(());
}
println!("Trying to claiming genesis... attempt {i}");
}
bail!("Failed to claim genesis")
}
async fn send_tokens(client: &Client, from: HotWallet, amount: &str, to: &str) -> Result<String> {
let to = MainPubkey::from_hex(to)?;
use std::str::FromStr;
let amount = NanoTokens::from_str(amount)?;
if amount.as_nano() == 0 {
println!("Invalid format or zero amount passed in. Nothing sent.");
return Err(eyre!(
"Invalid format or zero amount passed in. Nothing sent."
));
}
let cash_note = send(from, amount, to, client, true).await?;
let transfer_hex = Transfer::transfer_from_cash_note(&cash_note)?.to_hex()?;
println!("{transfer_hex}");
Ok(transfer_hex)
}
fn parse_log_output(val: &str) -> Result<LogOutputDest> {
match val {
"stdout" => Ok(LogOutputDest::Stdout),
"data-dir" => {
let dir = get_faucet_data_dir().join("logs");
Ok(LogOutputDest::Path(dir))
}
value => Ok(LogOutputDest::Path(PathBuf::from(value))),
}
}