diff --git a/src/main.rs b/src/main.rs index 8d7dacf..25a5f47 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,7 @@ use lox_simulation::{ use clap::Parser; use lox_cli::{networking::*, *}; +use lox_library::cred::Invitation; use memory_stats::memory_stats; use rand::{prelude::SliceRandom, Rng}; use serde::Deserialize; @@ -118,6 +119,9 @@ pub async fn main() { // Set up users let mut users = Vec::::new(); + // We have some pool of invitations available + let mut invites = Vec::::new(); + // Set up extra-infos server spawn(async move { extra_infos_server::server().await; @@ -136,6 +140,9 @@ pub async fn main() { let mut total_tn = 0; let mut total_tp = 0; + // Get starting date + let start_date = get_date(); + // Track daily percentage of users who have at least one working bridge let mut percent_users_can_connect = Vec::::new(); @@ -193,12 +200,7 @@ pub async fn main() { // Users do daily actions for user in &mut users { let invited_friends = user - .daily_tasks( - &sconfig, - num_users_requesting_invites, - &mut bridges, - &mut censor, - ) + .daily_tasks(&sconfig, &mut bridges, &mut censor, &mut invites) .await; if invited_friends.is_ok() { @@ -208,9 +210,9 @@ pub async fn main() { } } - // Count the number of non-censor users who are able to - // connect to at least one bridge - if !user.is_censor { + // Count the number of non-censor users who were able to + // connect to at least one bridge today + if !user.is_censor && user.attempted_connection { if user.able_to_connect { count_users_can_connect += 1; } else { @@ -233,31 +235,63 @@ pub async fn main() { } // Add percent of users who can connect to vector - percent_users_can_connect.push( - count_users_can_connect as f64 - / (count_users_can_connect + count_users_cannot_connect) as f64, + let percent_connect_today = count_users_can_connect as f64 + / (count_users_can_connect + count_users_cannot_connect) as f64; + println!( + " Percent of users who can connect today: {}", + percent_connect_today ); + percent_users_can_connect.push(percent_connect_today); // Add new users users.append(&mut new_users); - // If any users couldn't get invites, they join with open-entry - // invitations + // For each user requesting an invite, see if one is available. + // If not, they can try to join via open-entry invitation if the + // censor is not active. for _ in 0..num_users_requesting_invites { - let user = User::new(&sconfig, false, &mut bridges, &mut censor).await; - if user.is_ok() { - users.push(user.unwrap()); - } else { - eprintln!("Failed to create new user."); + let mut user_created = false; + + // Try invites until one works or we run out + while let Some(invite) = invites.pop() { + if let Ok(user) = + User::from_invite(invite, &sconfig, false, &mut bridges, &mut censor).await + { + users.push(user); + user_created = true; + + // We got a user. Stop now. + break; + } + } + + // If we couldn't get a working invite, try open-entry invite + if !user_created && !censor.is_active() { + if let Ok(user) = User::new(&sconfig, false, &mut bridges, &mut censor).await { + users.push(user); + } } } // CENSOR TASKS - if censor.is_active() { - // Censor gets as many open-entry invites as possible + + // On the day the censor activates, learn only the next 3 bridges (for efficiency) + if date == censor.start_date { + let num_bridges_before = censor.known_bridges.len(); + + // Censor gets as many invites as possible for 3 bridges while let Ok(new_user) = User::new(&sconfig, true, &mut bridges, &mut censor).await { + // Add new censor user users.push(new_user); + + // If we now know 3 more bridges, break + if censor.known_bridges.len() >= num_bridges_before + 3 { + break; + } } + } + + if censor.is_active() { censor.end_of_day_tasks(&sconfig, &mut bridges).await; } @@ -429,4 +463,14 @@ pub async fn main() { } } println!("End which users can connect"); + + println!("\nSimulation began on day {}", start_date); + println!("Censor began on day {}\n", censor.start_date); + + println!("\nPercent of users who can connect per day:"); + println!("date,percent"); + for i in 0..percent_users_can_connect.len() { + println!("{},{}", start_date + i as u32, percent_users_can_connect[i]); + } + println!("End percent of users who can connect per day"); } diff --git a/src/user.rs b/src/user.rs index b44ea3b..15553fa 100644 --- a/src/user.rs +++ b/src/user.rs @@ -9,10 +9,13 @@ use crate::{ use anyhow::{anyhow, Result}; use lox_cli::{networking::*, *}; use lox_library::{ - bridge_table::BridgeLine, cred::Lox, proto::check_blockage::MIN_TRUST_LEVEL, scalar_u32, + bridge_table::BridgeLine, + cred::{Invitation, Lox}, + proto::check_blockage::MIN_TRUST_LEVEL, + scalar_u32, }; use rand::Rng; -use std::{cmp::min, collections::HashMap}; +use std::collections::HashMap; use troll_patrol::{ get_date, negative_report::NegativeReport, positive_report::PositiveReport, BridgeDistributor, }; @@ -47,6 +50,8 @@ pub struct User { // Track date the user joined and whether they're able to connect pub join_date: u32, pub able_to_connect: bool, + // Attempted to connect today + pub attempted_connection: bool, } impl User { @@ -56,6 +61,10 @@ impl User { bridges: &mut HashMap<[u8; 20], Bridge>, censor: &mut Censor, ) -> Result { + if censor.is_active() && !is_censor { + return Err(anyhow!("Censor is active; open invites disabled")); + } + let cred = Self::get_new_credential(&config).await?.0; // Decide how likely this user is to use bridges on a given day @@ -123,17 +132,17 @@ impl User { in_censorship_range, join_date: get_date(), able_to_connect, + attempted_connection: true, }) } - // TODO: This should probably return an actual error type - pub async fn invite( + // Get an invite if able + pub async fn get_invite( &mut self, config: &Config, bridges: &mut HashMap<[u8; 20], Bridge>, censor: &mut Censor, - invited_user_is_censor: bool, - ) -> Result { + ) -> Result { let etable = get_reachability_credential(&config.la_net).await?; let (new_cred, invite) = issue_invite( &config.la_net, @@ -166,8 +175,17 @@ impl User { } } } + Ok(invite) + } - let friend_cred = redeem_invite( + pub async fn from_invite( + invite: Invitation, + config: &Config, + is_censor: bool, + bridges: &mut HashMap<[u8; 20], Bridge>, + censor: &mut Censor, + ) -> Result { + let cred = redeem_invite( &config.la_net, &invite, get_lox_pub(&config.la_pubkeys), @@ -176,9 +194,6 @@ impl User { .await? .0; - // Calling function decides if the invited user is a censor - let is_censor = invited_user_is_censor; - // Decide how likely this user is to use bridges on a given day // and whether they submit reports let (prob_use_bridges, submits_reports) = if is_censor { @@ -201,7 +216,7 @@ impl User { // Immediately download bucket and test bridges or give them to // the censor let mut negative_reports = Vec::::new(); - let (bucket, _reachcred) = get_bucket(&config.la_net, &friend_cred).await?; + let (bucket, _reachcred) = get_bucket(&config.la_net, &cred).await?; for bridgeline in bucket { let fingerprint = bridgeline.get_hashed_fingerprint(); if bridgeline != BridgeLine::default() { @@ -244,13 +259,14 @@ impl User { Ok(Self { is_censor, - primary_cred: friend_cred, + primary_cred: cred, secondary_cred: None, submits_reports: submits_reports, prob_use_bridges: prob_use_bridges, in_censorship_range, join_date: get_date(), able_to_connect, + attempted_connection: true, }) } @@ -359,14 +375,14 @@ 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, + invites: &mut Vec, ) -> Result> { if self.is_censor { self.daily_tasks_censor(config, bridges, censor).await } else { - self.daily_tasks_non_censor(config, num_users_requesting_invites, bridges, censor) + self.daily_tasks_non_censor(config, bridges, censor, invites) .await } } @@ -378,12 +394,38 @@ 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, + invites: &mut Vec, ) -> Result> { // Probabilistically decide if the user should use bridges today if event_happens(self.prob_use_bridges) { + self.attempted_connection = true; + + // If we couldn't connect yesterday, try to get a new credential via invite. + if !self.able_to_connect { + while let Some(invite) = invites.pop() { + match redeem_invite( + &config.la_net, + &invite, + get_lox_pub(&config.la_pubkeys), + get_invitation_pub(&config.la_pubkeys), + ) + .await + { + Ok((cred, _bucket)) => { + self.primary_cred = cred; + + // We got a credential. Stop now. + break; + } + Err(e) => { + println!("Failed to redeem invite. Error: {}", e); + } + } + } + } + // Start with the assumption that we can't connect, change // only if we can self.able_to_connect = false; @@ -456,12 +498,17 @@ impl User { if self.secondary_cred.is_some() { std::mem::replace(&mut self.secondary_cred, None) } else { - // Get new credential - match Self::get_new_credential(&config).await { - Ok((cred, _bl)) => Some(cred), - Err(e) => { - eprintln!("Failed to get new Lox credential. Error: {}", e); - None + // If the censor is in play, we cannot get open-entry invites + if censor.is_active() { + None + } else { + // Get new credential + match Self::get_new_credential(&config).await { + Ok((cred, _bl)) => Some(cred), + Err(e) => { + eprintln!("Failed to get new Lox credential. Error: {}", e); + None + } } } } @@ -629,7 +676,11 @@ impl User { Some(v) => v, None => 0, // This is probably an error case that should not happen }; - let mut new_friends = Vec::::new(); + + // It's just more convenient in the code to do this this way. + // Invite censors directly as new users. If the invited user is + // not a censor, instead add the invitation to a global list. + let mut new_censors = Vec::::new(); // Scale the probability of inviting a censor, based on the // user's own trust level. We assume that users with @@ -642,24 +693,33 @@ impl User { Some(2) => 0.5, _ => 1.0, }; - for _i in 0..min(invitations, num_users_requesting_invites) { + for _ in 0..invitations { if event_happens(config.prob_user_invites_friend) { - // With some probability, the user is convinced to - // invite a censor. We assume users with higher - // trust levels will be more cautious with - // invitations because they have more to lose. - let invited_friend_is_censor = censor.is_active() - && event_happens(config.prob_censor_gets_invite * level_scale); - // Invite friend (who might be a censor) - match self - .invite(&config, bridges, censor, invited_friend_is_censor) - .await - { - Ok(friend) => { - // You really shouldn't push your friends, - // especially new ones whose boundaries you - // might not know well. - new_friends.push(friend); + match self.get_invite(config, bridges, censor).await { + Ok(invite) => { + // With some probability, the user is convinced to + // invite a censor. We assume users with higher + // trust levels will be more cautious with + // invitations because they have more to lose. + if censor.is_active() + && event_happens(config.prob_censor_gets_invite * level_scale) + { + // Invite friend (who might be a censor) + match Self::from_invite(invite, config, true, bridges, censor).await + { + Ok(friend) => { + // You really shouldn't push your friends, + // especially new ones whose boundaries you + // might not know well. + new_censors.push(friend); + } + Err(e) => { + println!("{}", e); + } + } + } else { + invites.push(invite); + } } Err(e) => { println!("{}", e); @@ -668,8 +728,12 @@ impl User { } } - Ok(new_friends) + Ok(new_censors) } else { + // If we didn't try to connect, indicate so. This is to + // prevent users from being counted as able/unable to + // connect when they just haven't tried. + self.attempted_connection = false; Ok(Vec::::new()) } } @@ -791,47 +855,63 @@ impl User { } } } - } else { - // If unable to migrate, try to get a new open-entry - // credential and start over + + // Removing this case for efficiency. If the censor is in + // play, we just assume it wins the open-entry game and stop + // distributing open-entry invites altogether. + /* + } else { + // If unable to migrate, try to get a new open-entry + // credential and start over + 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; + } + */ + } + } + + // Also removing this case for efficiency. + /* + // 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 (_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. } - } - } - - // 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. 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(); for _ in 0..invitations { - match self.invite(&config, bridges, censor, true).await { - Ok(friend) => { - new_friends.push(friend); + match self.get_invite(config, bridges, censor).await { + Ok(invite) => { + match Self::from_invite(invite, &config, true, bridges, censor).await { + Ok(friend) => { + new_friends.push(friend); + } + Err(e) => { + println!("{}", e); + } + } } Err(e) => { println!("{}", e);