diff --git a/src/bin/simulation.rs b/src/bin/simulation.rs index 63b1bae..5a533a8 100644 --- a/src/bin/simulation.rs +++ b/src/bin/simulation.rs @@ -57,6 +57,7 @@ pub struct Config { // We start with this many level 4 users pub num_initial_trusted_users: u32, pub one_positive_report_per_cred: bool, + pub prob_censor_gets_invite: f64, pub prob_connection_fails: f64, pub prob_user_invites_friend: f64, pub prob_user_is_censor: f64, @@ -102,6 +103,7 @@ pub async fn main() { censor_partial_blocking_percent: config.censor_partial_blocking_percent, country: config.country, one_positive_report_per_cred: config.one_positive_report_per_cred, + prob_censor_gets_invite: config.prob_censor_gets_invite, prob_connection_fails: config.prob_connection_fails, prob_user_invites_friend: config.prob_user_invites_friend, prob_user_is_censor: config.prob_user_is_censor, @@ -256,6 +258,30 @@ pub async fn main() { let mut num_users_requesting_invites: u32 = rng.gen_range(config.min_new_users_per_day..=config.max_new_users_per_day); + // How many of the new users are censors? + let mut num_new_censor_users = 0; + for _ in 0..num_users_requesting_invites { + let num: f64 = rng.gen_range(0.0..1.0); + if num < config.prob_user_is_censor { + num_new_censor_users += 1; + num_users_requesting_invites -= 1; + } + } + + // Determine whether each new censor user can get an invite from + // an existing trusted user or needs to join via open-entry + // invite. Note: We still favor honest users by giving them + // invites *first*. This means if only a small number of invites + // are available, the censor may still not get invited. + let mut num_censor_invitations = 0; + for _ in 0..num_new_censor_users { + let num: f64 = rng.gen_range(0.0..1.0); + if num < config.prob_censor_gets_invite { + num_censor_invitations += 1; + num_new_censor_users -= 1; + } + } + let mut new_users = Vec::::new(); // Shuffle users so they act in a random order @@ -267,6 +293,7 @@ pub async fn main() { .daily_tasks( &sconfig, num_users_requesting_invites, + num_censor_invitations, &mut bridges, &mut censor, ) @@ -276,10 +303,17 @@ pub async fn main() { 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; + // Censors always invite as many censor friends + // as possible. Honest users may invite honest + // friends, or they may accidentally invite + // censor friends. + for inv_friend in &invited_friends { + if inv_friend.is_censor { + num_censor_invitations -= 1; + } else { + num_users_requesting_invites -= 1; + } + } } // If this user invited any friends, add them to the // list of users @@ -294,7 +328,7 @@ pub async fn main() { // 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; + let user = User::new(&sconfig, false).await; if user.is_ok() { users.push(user.unwrap()); } else { @@ -302,6 +336,17 @@ pub async fn main() { } } + // If any censor users couldn't get invites, they also join with + // open-entry invitations + for _ in 0..(num_new_censor_users + num_censor_invitations) { + let user = User::new(&sconfig, true).await; + if user.is_ok() { + users.push(user.unwrap()); + } else { + eprintln!("Failed to create new censor user."); + } + } + // CENSOR TASKS censor.end_of_day_tasks(&sconfig, &mut bridges).await; diff --git a/src/simulation/config.rs b/src/simulation/config.rs index d48c5fc..d7a3b23 100644 --- a/src/simulation/config.rs +++ b/src/simulation/config.rs @@ -17,6 +17,9 @@ pub struct Config { // share information with each other. pub country: String, pub one_positive_report_per_cred: bool, + // Probability that a censor-cooperating user can convince an honest + // user to give them an invite. + pub prob_censor_gets_invite: f64, // Probability that a connection randomly fails, even though censor // does not block the bridge pub prob_connection_fails: f64, diff --git a/src/simulation/user.rs b/src/simulation/user.rs index 0d19038..8de0940 100644 --- a/src/simulation/user.rs +++ b/src/simulation/user.rs @@ -45,7 +45,7 @@ pub struct User { } impl User { - pub async fn new(config: &Config) -> Result { + pub async fn new(config: &Config, is_censor: bool) -> Result { let cred = get_lox_credential( &config.la_net, &get_open_invitation(&config.la_net).await?, @@ -54,9 +54,6 @@ impl User { .await? .0; - // Probabilistically decide whether this user cooperates with a censor - let is_censor = event_happens(config.prob_user_is_censor); - // Probabilistically decide whether this user submits reports let submits_reports = if is_censor { false @@ -96,7 +93,12 @@ impl User { } // TODO: This should probably return an actual error type - pub async fn invite(&mut self, config: &Config, censor: &mut Censor) -> Result { + pub async fn invite( + &mut self, + config: &Config, + censor: &mut Censor, + invited_user_is_censor: bool, + ) -> Result { let etable = get_reachability_credential(&config.la_net).await?; let (new_cred, invite) = issue_invite( &config.la_net, @@ -127,13 +129,8 @@ impl User { .await? .0; - // If the inviting user is a censor, the invitee will also be a - // censor. If not, probabilistically decide. - let is_censor = if self.is_censor { - true - } else { - event_happens(config.prob_user_is_censor) - }; + // Calling function decides if the invited user is a censor + let is_censor = invited_user_is_censor; // Probabilistically decide whether this user submits reports let submits_reports = if is_censor { @@ -254,14 +251,21 @@ impl User { &mut self, config: &Config, num_users_requesting_invites: u32, + num_censor_invites: u32, bridges: &mut HashMap<[u8; 20], Bridge>, censor: &mut Censor, ) -> Result, Error> { 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) - .await + self.daily_tasks_non_censor( + config, + num_users_requesting_invites, + num_censor_invites, + bridges, + censor, + ) + .await } } @@ -273,6 +277,7 @@ impl User { &mut self, config: &Config, num_users_requesting_invites: u32, + num_censor_invites: u32, bridges: &mut HashMap<[u8; 20], Bridge>, censor: &mut Censor, ) -> Result, Error> { @@ -512,7 +517,8 @@ impl User { let mut new_friends = Vec::::new(); for _i in 0..min(invitations, num_users_requesting_invites) { if event_happens(config.prob_user_invites_friend) { - match self.invite(&config, censor).await { + // Invite non-censor friend + match self.invite(&config, censor, false).await { Ok(friend) => { // You really shouldn't push your friends, // especially new ones whose boundaries you @@ -526,6 +532,22 @@ impl User { } } + // Invite censor users if applicable + let invitations = invitations - new_friends.len() as u32; + for _i in 0..min(invitations, num_censor_invites) { + if event_happens(config.prob_user_invites_friend) { + // Invite non-censor friend + match self.invite(&config, censor, true).await { + Ok(friend) => { + new_friends.push(friend); + } + Err(e) => { + println!("{}", e); + } + } + } + } + Ok(new_friends) } else { Ok(Vec::::new()) @@ -643,7 +665,7 @@ impl User { let invitations = scalar_u32(&self.primary_cred.invites_remaining).unwrap(); let mut new_friends = Vec::::new(); for _ in 0..invitations { - match self.invite(&config, censor).await { + match self.invite(&config, censor, true).await { Ok(friend) => { new_friends.push(friend); }