diff --git a/node-launchpad/src/components/discord_username.rs b/node-launchpad/src/components/discord_username.rs index 2f460bfbb1..8ec42ac126 100644 --- a/node-launchpad/src/components/discord_username.rs +++ b/node-launchpad/src/components/discord_username.rs @@ -69,7 +69,7 @@ impl Component for DiscordUsernameInputBox { } _ => { // max 32 limit as per discord docs - if self.discord_input_filed.value().len() >= 32 { + if self.discord_input_filed.value().chars().count() >= 32 { return Ok(vec![]); } self.discord_input_filed.handle_event(&Event::Key(key)); diff --git a/sn_auditor/src/dag_db.rs b/sn_auditor/src/dag_db.rs index a16c3922c5..f1033ce92f 100644 --- a/sn_auditor/src/dag_db.rs +++ b/sn_auditor/src/dag_db.rs @@ -7,6 +7,8 @@ // permissions and limitations relating to use of the SAFE Network Software. use bls::SecretKey; +#[cfg(feature = "svg-dag")] +use color_eyre::eyre::Context; use color_eyre::eyre::{bail, eyre, Result}; #[cfg(feature = "svg-dag")] use graphviz_rust::{cmd::Format, exec, parse, printer::PrinterContext}; @@ -35,7 +37,7 @@ pub struct SpendDagDb { path: PathBuf, dag: Arc>, forwarded_payments: Arc>, - beta_participants: BTreeMap, + pub(crate) beta_participants: Arc>>, encryption_sk: Option, } @@ -54,8 +56,13 @@ impl SpendDagDb { /// Create a new SpendDagDb /// If a local spend DAG file is found, it will be loaded /// Else a new DAG will be created containing only Genesis - pub async fn new(path: PathBuf, client: Client) -> Result { + pub async fn new( + path: PathBuf, + client: Client, + encryption_sk: Option, + ) -> Result { let dag_path = path.join(SPEND_DAG_FILENAME); + info!("Loading DAG from {dag_path:?}..."); let dag = match SpendDag::load_from_file(&dag_path) { Ok(d) => { println!("Found a local spend DAG file"); @@ -72,13 +79,18 @@ impl SpendDagDb { path, dag: Arc::new(RwLock::new(dag)), forwarded_payments: Arc::new(RwLock::new(BTreeMap::new())), - beta_participants: BTreeMap::new(), - encryption_sk: None, + beta_participants: Arc::new(RwLock::new(BTreeMap::new())), + encryption_sk, }) } + // Check if the DAG has an encryption secret key set + pub fn has_encryption_sk(&self) -> bool { + self.encryption_sk.is_some() + } + /// Create a new SpendDagDb from a local file and no network connection - pub fn offline(dag_path: PathBuf) -> Result { + pub fn offline(dag_path: PathBuf, encryption_sk: Option) -> Result { let path = dag_path .parent() .ok_or_else(|| eyre!("Failed to get parent path"))? @@ -89,16 +101,11 @@ impl SpendDagDb { path, dag: Arc::new(RwLock::new(dag)), forwarded_payments: Arc::new(RwLock::new(BTreeMap::new())), - beta_participants: BTreeMap::new(), - encryption_sk: None, + beta_participants: Arc::new(RwLock::new(BTreeMap::new())), + encryption_sk, }) } - /// Set encryption secret key for beta program - pub fn set_encryption_sk(&mut self, sk: Option) { - self.encryption_sk = sk; - } - /// Get info about a single spend in JSON format pub fn spend_json(&self, address: SpendAddress) -> Result { let dag_ref = self.dag.clone(); @@ -150,7 +157,8 @@ impl SpendDagDb { #[cfg(feature = "svg-dag")] pub fn load_svg(&self) -> Result> { let svg_path = self.path.join(SPEND_DAG_SVG_FILENAME); - let svg = std::fs::read(svg_path)?; + let svg = std::fs::read(&svg_path) + .context(format!("Could not load svg from path: {svg_path:?}"))?; Ok(svg) } @@ -190,12 +198,12 @@ impl SpendDagDb { .await?; // write update to DAG - let dag_ref = self.dag.clone(); - let mut w_handle = dag_ref + let mut dag_w_handle = self + .dag .write() .map_err(|e| eyre!("Failed to get write lock: {e}"))?; - *w_handle = dag; - std::mem::drop(w_handle); + *dag_w_handle = dag; + std::mem::drop(dag_w_handle); #[cfg(feature = "svg-dag")] { @@ -210,7 +218,7 @@ impl SpendDagDb { } // gather forwarded payments in a background thread so we don't block - let mut self_clone = self.clone(); + let self_clone = self.clone(); tokio::spawn(async move { if let Err(e) = self_clone.gather_forwarded_payments().await { error!("Failed to gather forwarded payments: {e}"); @@ -224,8 +232,8 @@ impl SpendDagDb { /// This can be used to enrich our DAG with a DAG from another node to avoid costly computations /// Make sure to verify the other DAG is trustworthy before calling this function to merge it in pub fn merge(&mut self, other: SpendDag) -> Result<()> { - let dag_ref = self.dag.clone(); - let mut w_handle = dag_ref + let mut w_handle = self + .dag .write() .map_err(|e| eyre!("Failed to get write lock: {e}"))?; w_handle.merge(other, true)?; @@ -252,19 +260,22 @@ impl SpendDagDb { Ok(json) } - /// Initialize reward forward tracking, gathers current rewards from the DAG - pub(crate) async fn init_reward_forward_tracking( - &mut self, - participants: Vec, - sk: SecretKey, - ) -> Result<()> { - self.beta_participants = participants - .iter() - .map(|h| (Hash::hash(h.as_bytes()), h.clone())) - .collect(); + /// Track new beta participants. This just add the participants to the list of tracked participants. + pub(crate) fn track_new_beta_participants(&self, participants: Vec) -> Result<()> { + // track new participants { - let w_handle = self.forwarded_payments.clone(); - let mut fwd_payments = w_handle + let mut beta_participants = self + .beta_participants + .write() + .map_err(|e| eyre!("Failed to get beta participants write lock: {e}"))?; + for p in participants.iter() { + beta_participants.insert(Hash::hash(p.as_bytes()), p.clone()); + } + } + // initialize forwarded payments + { + let mut fwd_payments = self + .forwarded_payments .write() .map_err(|e| eyre!("Failed to get forwarded payments write lock: {e}"))?; *fwd_payments = participants @@ -272,14 +283,21 @@ impl SpendDagDb { .map(|n| (n, BTreeSet::new())) .collect(); } - self.encryption_sk = Some(sk); + Ok(()) + } + /// Initialize reward forward tracking, gathers current rewards from the DAG + pub(crate) async fn init_reward_forward_tracking( + &self, + participants: Vec, + ) -> Result<()> { + self.track_new_beta_participants(participants)?; self.gather_forwarded_payments().await?; Ok(()) } // Gather forwarded payments from the DAG - pub(crate) async fn gather_forwarded_payments(&mut self) -> Result<()> { + pub(crate) async fn gather_forwarded_payments(&self) -> Result<()> { info!("Gathering forwarded payments..."); // make sure we have the foundation secret key @@ -289,8 +307,7 @@ impl SpendDagDb { .ok_or_else(|| eyre!("Foundation secret key not set"))?; // get spends from current DAG - let r_handle = self.dag.clone(); - let dag = r_handle.read().map_err(|e| { + let dag = self.dag.read().map_err(|e| { eyre!("Failed to get dag read lock for gathering forwarded payments: {e}") })?; let all_spends = dag.all_spends(); @@ -304,7 +321,11 @@ impl SpendDagDb { }; let addr = spend.address(); let amount = spend.spend.amount; - if let Some(user_name) = self.beta_participants.get(&user_name_hash) { + let beta_participants_read = self + .beta_participants + .read() + .map_err(|e| eyre!("Failed to get payments write lock: {e}"))?; + if let Some(user_name) = beta_participants_read.get(&user_name_hash) { debug!("Got forwarded reward from {user_name} of {amount} at {addr:?}"); payments .entry(user_name.to_owned()) @@ -323,8 +344,8 @@ impl SpendDagDb { } // save new payments - let w_handle = self.forwarded_payments.clone(); - let mut self_payments = w_handle + let mut self_payments = self + .forwarded_payments .write() .map_err(|e| eyre!("Failed to get payments write lock: {e}"))?; self_payments.extend(payments); diff --git a/sn_auditor/src/main.rs b/sn_auditor/src/main.rs index b22349d906..946492dcbb 100644 --- a/sn_auditor/src/main.rs +++ b/sn_auditor/src/main.rs @@ -112,11 +112,10 @@ async fn main() -> Result<()> { let beta_rewards_on = maybe_sk.is_some(); if let Some(dag_to_view) = opt.offline_viewer { - let mut dag = SpendDagDb::offline(dag_to_view)?; + let dag = SpendDagDb::offline(dag_to_view, maybe_sk)?; #[cfg(feature = "svg-dag")] dag.dump_dag_svg()?; - dag.set_encryption_sk(maybe_sk); start_server(dag).await?; return Ok(()); } @@ -222,7 +221,7 @@ async fn initialize_background_spend_dag_collection( } // initialize the DAG - let dag = dag_db::SpendDagDb::new(path.clone(), client.clone()) + let dag = dag_db::SpendDagDb::new(path.clone(), client.clone(), foundation_sk) .await .map_err(|e| eyre!("Could not create SpendDag Db: {e}"))?; @@ -250,17 +249,15 @@ async fn initialize_background_spend_dag_collection( // initialize beta rewards program tracking if !beta_participants.is_empty() { - let sk = match foundation_sk { - Some(sk) => sk, - None => panic!("Foundation SK required to initialize beta rewards program"), + if !dag.has_encryption_sk() { + panic!("Foundation SK required to initialize beta rewards program"); }; println!("Initializing beta rewards program tracking..."); - let mut d = dag.clone(); - let _ = d - .init_reward_forward_tracking(beta_participants, sk) - .await - .map_err(|e| eprintln!("Could not initialize beta rewards: {e}")); + if let Err(e) = dag.init_reward_forward_tracking(beta_participants).await { + eprintln!("Could not initialize beta rewards: {e}"); + return Err(e); + } } // background thread to update DAG @@ -298,6 +295,7 @@ async fn start_server(dag: SpendDagDb) -> Result<()> { let response = match request.url() { "/" => routes::spend_dag_svg(&dag), s if s.starts_with("/spend/") => routes::spend(&dag, &request), + s if s.starts_with("/add-participant/") => routes::add_participant(&dag, &request), "/beta-rewards" => routes::beta_rewards(&dag), _ => routes::not_found(), }; diff --git a/sn_auditor/src/routes.rs b/sn_auditor/src/routes.rs index 83b25b1b32..5f8802ea66 100644 --- a/sn_auditor/src/routes.rs +++ b/sn_auditor/src/routes.rs @@ -11,8 +11,6 @@ use sn_client::transfers::SpendAddress; use std::{io::Cursor, str::FromStr}; use tiny_http::{Request, Response}; -use crate::dag_db::SpendDagDb; - pub(crate) fn spend_dag_svg(_dag: &SpendDagDb) -> Result>>> { #[cfg(not(feature = "svg-dag"))] return Ok(Response::from_string( @@ -68,3 +66,31 @@ pub(crate) fn beta_rewards(dag: &SpendDagDb) -> Result>> let response = Response::from_data(json); Ok(response) } + +pub(crate) fn add_participant( + dag: &SpendDagDb, + request: &Request, +) -> Result>>> { + let discord_id = match request.url().split('/').last() { + Some(discord_id) => discord_id, + None => { + return Ok(Response::from_string( + "No discord_id provided. Should be /add-participant/[your_discord_id_here]", + ) + .with_status_code(400)) + } + }; + + if discord_id.chars().count() >= 32 { + return Ok( + Response::from_string("discord_id cannot be more than 32 chars").with_status_code(400), + ); + } + + match dag.track_new_beta_participants(vec![discord_id.to_owned()]) { + Ok(()) => Ok(Response::from_string("Added participant")), + Err(e) => Ok( + Response::from_string(format!("Failed to add participant: {e}")).with_status_code(500), + ), + } +}