From f245ee21f966bc0fadb8619d62cf8e6490a08b23 Mon Sep 17 00:00:00 2001 From: Vecna Date: Wed, 29 May 2024 01:05:37 -0400 Subject: [PATCH] Improve error handling and stats collection --- src/bin/simulation.rs | 166 ++++++++++++++++++++++++-------- src/simulation/bridge.rs | 25 +++++ src/simulation/censor.rs | 14 ++- src/simulation/user.rs | 200 +++++++++++++++++++++------------------ 4 files changed, 270 insertions(+), 135 deletions(-) diff --git a/src/bin/simulation.rs b/src/bin/simulation.rs index 4e4e746..5c6e783 100644 --- a/src/bin/simulation.rs +++ b/src/bin/simulation.rs @@ -18,7 +18,7 @@ use troll_patrol::{ use clap::Parser; use lox_cli::{networking::*, *}; use lox_library::proto::{level_up::LEVEL_INTERVAL, trust_promotion::UNTRUSTED_INTERVAL}; -use rand::Rng; +use rand::{prelude::SliceRandom, Rng}; use serde::Deserialize; use std::{ collections::{HashMap, HashSet}, @@ -87,7 +87,7 @@ pub async fn main() { hostname: "http://localhost:8004".to_string(), }; - let la_pubkeys = get_lox_auth_keys(&la_net).await; + let la_pubkeys = get_lox_auth_keys(&la_net).await.unwrap(); let sconfig = SConfig { la_pubkeys, @@ -120,7 +120,10 @@ pub async fn main() { if config.num_initial_trusted_users > 0 { // Add some number of trusted users initially for _ in 0..config.num_initial_trusted_users { - users.push(User::trusted_user(&sconfig).await); + let new_user = User::trusted_user(&sconfig).await; + if new_user.is_ok() { + users.push(new_user.unwrap()); + } } // Level trusted users up to level 4 @@ -138,19 +141,25 @@ pub async fn main() { set_simulated_date(get_date() + UNTRUSTED_INTERVAL); for user in &mut users { - user.primary_cred = trust_migration( + let migcred = trust_promotion( &sconfig.la_net, &user.primary_cred, - &trust_promotion( - &sconfig.la_net, - &user.primary_cred, - get_lox_pub(&sconfig.la_pubkeys), - ) - .await, get_lox_pub(&sconfig.la_pubkeys), - get_migration_pub(&sconfig.la_pubkeys), ) .await; + if migcred.is_ok() { + let new_cred = trust_migration( + &sconfig.la_net, + &user.primary_cred, + &migcred.unwrap(), + get_lox_pub(&sconfig.la_pubkeys), + get_migration_pub(&sconfig.la_pubkeys), + ) + .await; + if new_cred.is_ok() { + user.primary_cred = new_cred.unwrap(); + } + } } for i in 1..LEVEL_INTERVAL.len() - 2 { @@ -167,15 +176,23 @@ pub async fn main() { set_simulated_date(get_date() + LEVEL_INTERVAL[i]); for user in &mut users { - let reachcred = get_bucket(&sconfig.la_net, &user.primary_cred).await.1; - user.primary_cred = level_up( - &sconfig.la_net, - &user.primary_cred, - &reachcred.unwrap(), - get_lox_pub(&sconfig.la_pubkeys), - get_reachability_pub(&sconfig.la_pubkeys), - ) - .await; + let reachcred_res = get_bucket(&sconfig.la_net, &user.primary_cred).await; + if reachcred_res.is_ok() { + let reachcred = reachcred_res.unwrap().1; + let new_cred = level_up( + &sconfig.la_net, + &user.primary_cred, + &reachcred.unwrap(), + get_lox_pub(&sconfig.la_pubkeys), + get_reachability_pub(&sconfig.la_pubkeys), + ) + .await; + if new_cred.is_ok() { + user.primary_cred = new_cred.unwrap(); + } else { + eprintln!("Failed to level up trusted user's credential at start"); + } + } } } @@ -201,8 +218,10 @@ pub async fn main() { }); sleep(Duration::from_millis(1)).await; - let mut fp = 0; - let mut tp = 0; + let mut false_neg = 0; + let mut false_pos = 0; + let mut true_neg = 0; + let mut true_pos = 0; // Main loop for day in 1..=config.num_days { @@ -210,26 +229,56 @@ pub async fn main() { // USER TASKS - // Add some new users - let num_new_users: u32 = + // Number of users who want to join today + let mut num_users_requesting_invites: u32 = rng.gen_range(config.min_new_users_per_day..=config.max_new_users_per_day); - for _ in 0..num_new_users { - users.push(User::new(&sconfig).await); - } let mut new_users = Vec::::new(); + // Shuffle users so they act in a random order + users.shuffle(&mut rng); + // Users do daily actions for user in &mut users { - let mut invited_friends = user.daily_tasks(&sconfig, &mut bridges, &mut censor).await; + let invited_friends = user + .daily_tasks( + &sconfig, + num_users_requesting_invites, + &mut bridges, + &mut censor, + ) + .await; - // If this user invited any friends, add them to the list of users - new_users.append(&mut invited_friends); + if invited_friends.is_ok() { + let mut invited_friends = invited_friends.unwrap(); + if invited_friends.len() > 0 { + if !user.is_censor { + // Users should never invite more friends than + // need invitations, so this should never become + // negative + num_users_requesting_invites -= invited_friends.len() as u32; + } + // If this user invited any friends, add them to the + // list of users + new_users.append(&mut invited_friends); + } + } } // Add new users users.append(&mut new_users); + // If any users couldn't get invites, they join with open-entry + // invitations + for _ in 0..num_users_requesting_invites { + let user = User::new(&sconfig).await; + if user.is_ok() { + users.push(user.unwrap()); + } else { + eprintln!("Failed to create new user."); + } + } + // CENSOR TASKS censor.end_of_day_tasks(&sconfig, &mut bridges).await; @@ -257,16 +306,37 @@ pub async fn main() { serde_json::from_slice(&new_blockages_resp).unwrap(); // TODO: Track more stats about new blockages + // Since we have only one censor, just convert to a set of bridges + let mut blocked_bridges = HashSet::<[u8; 20]>::new(); for (bridge, ccs) in new_blockages { let fingerprint = array_bytes::hex2array(bridge).unwrap(); - for cc in ccs { - if cc == sconfig.country { - if censor.knows_bridge(&fingerprint) { - tp += 1; - } else { - fp += 1; - } - } + if ccs.contains(&sconfig.country) { + blocked_bridges.insert(fingerprint); + } + } + + for (fingerprint, bridge) in &mut bridges { + let detected_blocked = blocked_bridges.contains(fingerprint); + + // If this is the first day Troll Patrol has determined this + // bridge is blocked, note that for stats + if detected_blocked && bridge.first_detected_blocked == 0 { + bridge.first_detected_blocked = get_date(); + } + + // Check if censor actually blocks this bridge + let really_blocked = censor.blocks_bridge(&sconfig, fingerprint); + if really_blocked && bridge.first_blocked == 0 { + bridge.first_blocked = get_date(); + } + if detected_blocked && really_blocked { + true_pos += 1; + } else if detected_blocked { + false_pos += 1; + } else if really_blocked { + false_neg += 1; + } else { + true_neg += 1; } } @@ -286,6 +356,22 @@ pub async fn main() { increment_simulated_date(); } - println!("True Positives: {}", tp); - println!("False Positives: {}", fp); + println!("True Positives: {}", true_pos); + println!("True Negatives: {}", true_neg); + println!("False Positives: {}", false_pos); + println!("False Negatives: {}", false_neg); + + println!( + "Fingerprint,first_distributed,first_blocked,first_detected_blocked,first_positive_report" + ); + for (fingerprint, bridge) in bridges { + println!( + "{},{},{},{},{}", + array_bytes::bytes2hex("", fingerprint), + bridge.first_distributed, + bridge.first_blocked, + bridge.first_detected_blocked, + bridge.first_positive_report + ); + } } diff --git a/src/simulation/bridge.rs b/src/simulation/bridge.rs index 6125adc..e7237ce 100644 --- a/src/simulation/bridge.rs +++ b/src/simulation/bridge.rs @@ -5,6 +5,27 @@ use std::collections::BTreeMap; // The Bridge struct only tracks data for today pub struct Bridge { pub fingerprint: [u8; 20], + + // The following four values are Julian dates used to track + // accuracy of the Troll Patrol system. A value of 0 is used to + // indicate this event *has not happened yet*. Note that 0 is not a + // date we will ever encounter. + + // Date the bridge was first distributed to a user, i.e., the date + // we created this Bridge object + pub first_distributed: u32, + + // First date a censor blocked this bridge + pub first_blocked: u32, + + // First date Troll Patrol detected that this bridge was blocked + // (whether or not it was correct) + pub first_detected_blocked: u32, + + // First date Troll Patrol received a positive report for this + // bridge (for identifying stage three) + pub first_positive_report: u32, + real_connections: u32, total_connections: u32, } @@ -13,6 +34,10 @@ impl Bridge { pub fn new(fingerprint: &[u8; 20]) -> Self { Self { fingerprint: *fingerprint, + first_distributed: get_date(), + first_blocked: 0, + first_detected_blocked: 0, + first_positive_report: 0, real_connections: 0, total_connections: 0, } diff --git a/src/simulation/censor.rs b/src/simulation/censor.rs index de5ab8c..1154ead 100644 --- a/src/simulation/censor.rs +++ b/src/simulation/censor.rs @@ -55,12 +55,20 @@ impl Censor { self.known_bridges.contains(fingerprint) } + pub fn blocks_bridge(&self, config: &Config, fingerprint: &[u8; 20]) -> bool { + self.knows_bridge(fingerprint) + && (config.censor_speed == Speed::Fast + || config.censor_speed == Speed::Lox && self.has_lox_cred(fingerprint) + || config.censor_speed == Speed::Random && self.delay_date <= get_date()) + } + pub fn learn_bridge(&mut self, fingerprint: &[u8; 20]) { self.known_bridges.insert(*fingerprint); } pub fn has_lox_cred(&self, fingerprint: &[u8; 20]) -> bool { self.lox_credentials.contains_key(fingerprint) + && self.lox_credentials.get(fingerprint).unwrap().1 > 0 } pub fn give_lox_cred(&mut self, fingerprint: &[u8; 20], cred: &Lox) { @@ -164,21 +172,21 @@ impl Censor { } } -#[derive(Debug, Deserialize, PartialEq)] +#[derive(Clone, Copy, Debug, Deserialize, PartialEq)] pub enum Speed { Fast, Lox, Random, } -#[derive(Debug, Deserialize, PartialEq)] +#[derive(Clone, Copy, Debug, Deserialize, PartialEq)] pub enum Hides { Overt, Hiding, Flooding, } -#[derive(Debug, Deserialize, PartialEq)] +#[derive(Clone, Copy, Debug, Deserialize, PartialEq)] pub enum Totality { Full, Partial, diff --git a/src/simulation/user.rs b/src/simulation/user.rs index 1061d2c..7cc4345 100644 --- a/src/simulation/user.rs +++ b/src/simulation/user.rs @@ -6,7 +6,7 @@ use crate::{ positive_report::PositiveReport, simulation::{ bridge::Bridge, - censor::{Censor, Hides::*, Speed::*, Totality::*}, + censor::{Censor, Hides::*, Totality::*}, config::Config, }, BridgeDistributor, @@ -16,7 +16,8 @@ use lox_library::{ bridge_table::BridgeLine, cred::Lox, proto::check_blockage::MIN_TRUST_LEVEL, scalar_u32, }; use rand::Rng; -use std::collections::HashMap; +use serde_json::error::Error; +use std::{cmp::min, collections::HashMap}; use x25519_dalek::PublicKey; // Helper function to probabilistically return true or false @@ -28,7 +29,7 @@ pub fn event_happens(probability: f64) -> bool { pub struct User { // Does this user cooperate with a censor? - is_censor: bool, + pub is_censor: bool, // The user always has a primary credential. If this credential's bucket is // blocked, the user may replace it or temporarily hold two credentials @@ -44,13 +45,13 @@ pub struct User { } impl User { - pub async fn new(config: &Config) -> Self { + pub async fn new(config: &Config) -> Result { let cred = get_lox_credential( &config.la_net, - &get_open_invitation(&config.la_net).await, + &get_open_invitation(&config.la_net).await?, get_lox_pub(&config.la_pubkeys), ) - .await + .await? .0; // Probabilistically decide whether this user cooperates with a censor @@ -68,35 +69,35 @@ impl User { let mut rng = rand::thread_rng(); let prob_use_bridges = rng.gen_range(0.0..=1.0); - Self { + Ok(Self { is_censor, primary_cred: cred, secondary_cred: None, submits_reports: submits_reports, prob_use_bridges: prob_use_bridges, - } + }) } - pub async fn trusted_user(config: &Config) -> Self { + pub async fn trusted_user(config: &Config) -> Result { let cred = get_lox_credential( &config.la_net, - &get_open_invitation(&config.la_net).await, + &get_open_invitation(&config.la_net).await?, get_lox_pub(&config.la_pubkeys), ) - .await + .await? .0; - Self { + Ok(Self { is_censor: false, primary_cred: cred, secondary_cred: None, submits_reports: true, prob_use_bridges: 1.0, - } + }) } // TODO: This should probably return an actual error type - pub async fn invite(&mut self, config: &Config, censor: &mut Censor) -> Result { - let etable = get_reachability_credential(&config.la_net).await; + pub async fn invite(&mut self, config: &Config, censor: &mut Censor) -> Result { + let etable = get_reachability_credential(&config.la_net).await?; let (new_cred, invite) = issue_invite( &config.la_net, &self.primary_cred, @@ -105,12 +106,12 @@ impl User { get_reachability_pub(&config.la_pubkeys), get_invitation_pub(&config.la_pubkeys), ) - .await; + .await?; self.primary_cred = new_cred; if self.is_censor { // Make sure censor has access to each bridge and each // credential - let (bucket, _reachcred) = get_bucket(&config.la_net, &self.primary_cred).await; + let (bucket, _reachcred) = get_bucket(&config.la_net, &self.primary_cred).await?; for bl in bucket { let fingerprint = bl.get_hashed_fingerprint(); censor.learn_bridge(&fingerprint); @@ -123,7 +124,7 @@ impl User { get_lox_pub(&config.la_pubkeys), get_invitation_pub(&config.la_pubkeys), ) - .await + .await? .0; // If the inviting user is a censor, the invitee will also be a @@ -157,26 +158,21 @@ impl User { // Attempt to "connect" to the bridge, returns true if successful pub fn connect(&self, config: &Config, bridge: &mut Bridge, censor: &Censor) -> bool { - if censor.knows_bridge(&bridge.fingerprint) { - if config.censor_speed == Fast - || config.censor_speed == Random && censor.delay_date <= get_date() - || config.censor_speed == Lox && censor.has_lox_cred(&bridge.fingerprint) + if censor.blocks_bridge(config, &bridge.fingerprint) { + if config.censor_totality == Full + || config.censor_totality == Partial + && event_happens(censor.partial_blocking_percent) + || config.censor_totality == Throttling { - if config.censor_totality == Full - || config.censor_totality == Partial - && event_happens(censor.partial_blocking_percent) - || config.censor_totality == Throttling - { - // If censor tries to hide its censorship or - // throttles rather than actually blocking, record a - // false connection - if config.censor_hides == Hiding || config.censor_totality == Throttling { - bridge.connect_total(); - } - - // Return false because the connection failed - return false; + // If censor tries to hide its censorship or + // throttles rather than actually blocking, record a + // false connection + if config.censor_hides == Hiding || config.censor_totality == Throttling { + bridge.connect_total(); } + + // Return false because the connection failed + return false; } } @@ -190,6 +186,15 @@ impl User { true } + pub async fn get_new_credential(config: &Config) -> Result<(Lox, BridgeLine), Error> { + get_lox_credential( + &config.la_net, + &get_open_invitation(&config.la_net).await?, + get_lox_pub(&config.la_pubkeys), + ) + .await + } + pub async fn send_negative_reports(config: &Config, reports: Vec) { let date = get_date(); let pubkey = serde_json::from_slice::>( @@ -226,13 +231,15 @@ impl User { pub async fn daily_tasks( &mut self, config: &Config, + num_users_requesting_invites: u32, bridges: &mut HashMap<[u8; 20], Bridge>, censor: &mut Censor, - ) -> Vec { + ) -> Result, Error> { if self.is_censor { self.daily_tasks_censor(config, bridges, censor).await } else { - self.daily_tasks_non_censor(config, bridges, censor).await + self.daily_tasks_non_censor(config, num_users_requesting_invites, bridges, censor) + .await } } @@ -243,15 +250,16 @@ impl User { pub async fn daily_tasks_non_censor( &mut self, config: &Config, + num_users_requesting_invites: u32, bridges: &mut HashMap<[u8; 20], Bridge>, censor: &mut Censor, - ) -> Vec { + ) -> Result, Error> { // Probabilistically decide if the user should use bridges today if event_happens(self.prob_use_bridges) { // Download bucket to see if bridge is still reachable. (We // assume that this step can be done even if the user can't // actually talk to the LA.) - let (bucket, reachcred) = get_bucket(&config.la_net, &self.primary_cred).await; + let (bucket, reachcred) = get_bucket(&config.la_net, &self.primary_cred).await?; let level = scalar_u32(&self.primary_cred.trust_level).unwrap(); // Make sure each bridge in bucket is in the global bridges set @@ -304,14 +312,13 @@ impl User { std::mem::replace(&mut self.secondary_cred, None) } else { // Get new credential - let cred = get_lox_credential( - &config.la_net, - &get_open_invitation(&config.la_net).await, - get_lox_pub(&config.la_pubkeys), - ) - .await - .0; - Some(cred) + match Self::get_new_credential(&config).await { + Ok((cred, _bl)) => Some(cred), + Err(e) => { + eprintln!("Failed to get new Lox credential. Error: {}", e); + None + } + } } } else { // If we're able to connect with the primary credential, don't @@ -321,7 +328,7 @@ impl User { if second_cred.is_some() { let second_cred = second_cred.as_ref().unwrap(); let (second_bucket, second_reachcred) = - get_bucket(&config.la_net, &second_cred).await; + get_bucket(&config.la_net, &second_cred).await?; for bridgeline in second_bucket { if bridgeline != BridgeLine::default() { if !bridges.contains_key(&bridgeline.get_hashed_fingerprint()) { @@ -364,6 +371,15 @@ impl User { } if level >= 3 { for bridgeline in &succeeded { + // If we haven't received a positive report yet, + // add a record about it with today's date + let bridge = bridges + .get_mut(&bridgeline.get_hashed_fingerprint()) + .unwrap(); + if bridge.first_positive_report == 0 { + bridge.first_positive_report = get_date(); + } + positive_reports.push( PositiveReport::from_lox_credential( bridgeline.get_hashed_fingerprint(), @@ -391,11 +407,11 @@ impl User { &self.primary_cred, get_lox_pub(&config.la_pubkeys), ) - .await, + .await?, get_lox_pub(&config.la_pubkeys), get_migration_pub(&config.la_pubkeys), ) - .await + .await? } else { level_up( &config.la_net, @@ -404,7 +420,7 @@ impl User { get_lox_pub(&config.la_pubkeys), get_reachability_pub(&config.la_pubkeys), ) - .await + .await? }; self.primary_cred = cred; self.secondary_cred = None; @@ -425,11 +441,11 @@ impl User { &second_cred, get_lox_pub(&config.la_pubkeys), ) - .await, + .await?, get_lox_pub(&config.la_pubkeys), get_migration_pub(&config.la_pubkeys), ) - .await; + .await?; self.primary_cred = cred; self.secondary_cred = None; } else if can_migrate { @@ -441,11 +457,11 @@ impl User { &self.primary_cred, get_lox_pub(&config.la_pubkeys), ) - .await, + .await?, get_lox_pub(&config.la_pubkeys), get_migration_pub(&config.la_pubkeys), ) - .await; + .await?; self.primary_cred = cred; self.secondary_cred = None; } else if second_cred.is_some() { @@ -466,7 +482,7 @@ impl User { // Invite friends if applicable let invitations = scalar_u32(&self.primary_cred.invites_remaining).unwrap(); let mut new_friends = Vec::::new(); - for _i in 0..invitations { + for _i in 0..min(invitations, num_users_requesting_invites) { if event_happens(config.prob_user_invites_friend) { match self.invite(&config, censor).await { Ok(friend) => { @@ -482,9 +498,9 @@ impl User { } } - new_friends + Ok(new_friends) } else { - Vec::::new() + Ok(Vec::::new()) } } @@ -495,10 +511,10 @@ impl User { config: &Config, bridges: &mut HashMap<[u8; 20], Bridge>, censor: &mut Censor, - ) -> Vec { + ) -> Result, Error> { // Download bucket to see if bridge is still reachable and if we // have any new bridges - let (bucket, reachcred) = get_bucket(&config.la_net, &self.primary_cred).await; + let (bucket, reachcred) = get_bucket(&config.la_net, &self.primary_cred).await?; let level = scalar_u32(&self.primary_cred.trust_level).unwrap(); // Make sure each bridge is in global bridges set and known by @@ -527,11 +543,11 @@ impl User { &self.primary_cred, get_lox_pub(&config.la_pubkeys), ) - .await, + .await?, get_lox_pub(&config.la_pubkeys), get_migration_pub(&config.la_pubkeys), ) - .await + .await? } else { level_up( &config.la_net, @@ -540,10 +556,10 @@ impl User { get_lox_pub(&config.la_pubkeys), get_reachability_pub(&config.la_pubkeys), ) - .await + .await? }; self.primary_cred = new_cred; - let (bucket, _reachcred) = get_bucket(&config.la_net, &self.primary_cred).await; + let (bucket, _reachcred) = get_bucket(&config.la_net, &self.primary_cred).await?; // Make sure each bridge is in global bridges set and // known by censor for bl in bucket { @@ -561,40 +577,40 @@ impl User { // will not be reverted, so replace the primary credential // with a new level 0 credential and work on gaining trust // for that one. - let (new_cred, bl) = get_lox_credential( - &config.la_net, - &get_open_invitation(&config.la_net).await, - get_lox_pub(&config.la_pubkeys), - ) - .await; + let res = Self::get_new_credential(&config).await; + if res.is_ok() { + let (new_cred, bl) = res.unwrap(); + let fingerprint = bl.get_hashed_fingerprint(); + if !bridges.contains_key(&fingerprint) { + let bridge = Bridge::from_bridge_line(&bl); + bridges.insert(fingerprint, bridge); + } + censor.learn_bridge(&fingerprint); + // Censor doesn't want new_cred yet + self.primary_cred = new_cred; + } else { + eprintln!("Censor failed to get new credential"); + } + } + + // Separately from primary credential, censor user requests a + // new secondary credential each day just to block the + // open-entry bridges. This is stored but not reused. + let res = Self::get_new_credential(&config).await; + if res.is_ok() { + let (_new_cred, bl) = res.unwrap(); let fingerprint = bl.get_hashed_fingerprint(); if !bridges.contains_key(&fingerprint) { let bridge = Bridge::from_bridge_line(&bl); bridges.insert(fingerprint, bridge); } censor.learn_bridge(&fingerprint); - // Censor doesn't want new_cred yet - self.primary_cred = new_cred; + // Censor doesn't want new_cred. User doesn't actually use + // secondary_cred, so don't store it. + } else { + eprintln!("Censor failed to get new credential"); } - // Separately from primary credential, censor user requests a - // new secondary credential each day just to block the - // open-entry bridges. This is stored but not reused. - let (_new_cred, bl) = get_lox_credential( - &config.la_net, - &get_open_invitation(&config.la_net).await, - get_lox_pub(&config.la_pubkeys), - ) - .await; - let fingerprint = bl.get_hashed_fingerprint(); - if !bridges.contains_key(&fingerprint) { - let bridge = Bridge::from_bridge_line(&bl); - bridges.insert(fingerprint, bridge); - } - censor.learn_bridge(&fingerprint); - // Censor doesn't want new_cred. User doesn't actually use - // secondary_cred, so don't store it. - // Censor user invites as many censor friends as possible let invitations = scalar_u32(&self.primary_cred.invites_remaining).unwrap(); let mut new_friends = Vec::::new(); @@ -608,6 +624,6 @@ impl User { } } } - new_friends + Ok(new_friends) } }