diff --git a/src/censor.rs b/src/censor.rs index 8b0a463..77d943f 100644 --- a/src/censor.rs +++ b/src/censor.rs @@ -57,6 +57,10 @@ impl Censor { } } + pub fn is_active(&self) -> bool { + get_date() >= self.start_date + } + pub fn knows_bridge(&self, fingerprint: &[u8; 20]) -> bool { self.known_bridges.contains(fingerprint) } @@ -218,7 +222,9 @@ impl Censor { config: &Config, bridges: &mut HashMap<[u8; 20], Bridge>, ) { - if get_date() >= self.start_date { + // This check is redundant because the simulation driver only + // calls this function if the censor is active. + if self.is_active() { if config.censor_secrecy == Secrecy::Flooding && !(config.censor_speed == Speed::Random && self.delay_date <= get_date()) { diff --git a/src/config.rs b/src/config.rs index 17a102e..7c86ed7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -19,7 +19,9 @@ pub struct Config { 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. + // user to give them an invite. This number is the baseline, for + // soliciting an invitation from a level 1 user. We assume it is + // harder for the censor to get invitations from higher-level users. pub prob_censor_gets_invite: f64, // Probability that a connection randomly fails, even though censor // does not block the bridge diff --git a/src/main.rs b/src/main.rs index 93fea60..a49b5f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -137,6 +137,9 @@ pub async fn main() { // Main loop for day in 1..=config.num_days { + // Save some function calls by storing this + let date = get_date(); + println!("Starting day {} of the simulation", day); println!( " We have {} users and {} bridges", @@ -167,33 +170,9 @@ pub async fn main() { // USER TASKS // Number of users who want to join today - let mut num_users_requesting_invites: u32 = + let 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 @@ -205,7 +184,6 @@ pub async fn main() { .daily_tasks( &sconfig, num_users_requesting_invites, - num_censor_invitations, &mut bridges, &mut censor, ) @@ -214,21 +192,6 @@ pub async fn main() { if invited_friends.is_ok() { let mut invited_friends = invited_friends.unwrap(); if invited_friends.len() > 0 { - if !user.is_censor { - // 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 new_users.append(&mut invited_friends); } } @@ -240,7 +203,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, false).await; + let user = User::new(&sconfig, false, &mut bridges, &mut censor).await; if user.is_ok() { users.push(user.unwrap()); } else { @@ -248,19 +211,14 @@ 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; + if censor.is_active() { + // Censor gets as many open-entry invites as possible + while let Ok(new_user) = User::new(&sconfig, true, &mut bridges, &mut censor).await { + users.push(new_user); + } + censor.end_of_day_tasks(&sconfig, &mut bridges).await; + } // BRIDGE TASKS let mut new_extra_infos = HashSet::::new(); @@ -319,13 +277,13 @@ pub async fn main() { // 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(); + bridge.first_detected_blocked = 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(); + bridge.first_blocked = date; } if detected_blocked && really_blocked { true_pos += 1; diff --git a/src/user.rs b/src/user.rs index 79e4f5d..d1e9ff0 100644 --- a/src/user.rs +++ b/src/user.rs @@ -43,14 +43,13 @@ pub struct User { } impl User { - 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?, - get_lox_pub(&config.la_pubkeys), - ) - .await? - .0; + pub async fn new( + config: &Config, + is_censor: bool, + bridges: &mut HashMap<[u8; 20], Bridge>, + censor: &mut Censor, + ) -> Result { + let cred = Self::get_new_credential(&config).await?.0; // Probabilistically decide whether this user submits reports let submits_reports = if is_censor { @@ -64,6 +63,21 @@ impl User { let mut rng = rand::thread_rng(); let prob_use_bridges = rng.gen_range(0.0..=1.0); + // If the user cooperates with the censor, immediately tell the + // censor about all the bridges + if is_censor { + let (bucket, _reachcred) = get_bucket(&config.la_net, &cred).await?; + for bridgeline in bucket { + if bridgeline != BridgeLine::default() { + if !bridges.contains_key(&bridgeline.get_hashed_fingerprint()) { + let bridge = Bridge::from_bridge_line(&bridgeline); + bridges.insert(bridgeline.get_hashed_fingerprint(), bridge); + } + censor.learn_bridge(&bridgeline.get_hashed_fingerprint()); + } + } + } + Ok(Self { is_censor, primary_cred: cred, @@ -73,27 +87,11 @@ impl User { }) } - pub async fn trusted_user(config: &Config) -> Result { - let cred = get_lox_credential( - &config.la_net, - &get_open_invitation(&config.la_net).await?, - get_lox_pub(&config.la_pubkeys), - ) - .await? - .0; - 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, + bridges: &mut HashMap<[u8; 20], Bridge>, censor: &mut Censor, invited_user_is_censor: bool, ) -> Result { @@ -130,17 +128,31 @@ impl User { // 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 { - false + // 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 { + (0.0, false) } else { - event_happens(config.prob_user_submits_reports) + let mut rng = rand::thread_rng(); + let prob_use_bridges = rng.gen_range(0.0..=1.0); + let submits_reports = event_happens(config.prob_user_submits_reports); + (prob_use_bridges, submits_reports) }; - // Randomly determine how likely this user is to use bridges on - // a given day - let mut rng = rand::thread_rng(); - let prob_use_bridges = rng.gen_range(0.0..=1.0); + // If the user cooperates with the censor, immediately tell the + // censor about all the bridges + if is_censor { + let (bucket, _reachcred) = get_bucket(&config.la_net, &self.primary_cred).await?; + for bridgeline in bucket { + if bridgeline != BridgeLine::default() { + if !bridges.contains_key(&bridgeline.get_hashed_fingerprint()) { + let bridge = Bridge::from_bridge_line(&bridgeline); + bridges.insert(bridgeline.get_hashed_fingerprint(), bridge); + } + censor.learn_bridge(&bridgeline.get_hashed_fingerprint()); + } + } + } Ok(Self { is_censor, @@ -153,8 +165,7 @@ impl User { // Attempt to "connect" to the bridge, returns true if successful. // Note that this does not involve making a real connection to a - // real bridge. The function is async because the *censor* might - // submit a positive report during this function. + // real bridge. pub fn connect(&self, config: &Config, bridge: &mut Bridge, censor: &Censor) -> bool { if censor.blocks_bridge(config, &bridge.fingerprint) { if config.censor_totality == Full @@ -254,21 +265,14 @@ 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> { if self.is_censor { self.daily_tasks_censor(config, bridges, censor).await } else { - self.daily_tasks_non_censor( - config, - num_users_requesting_invites, - num_censor_invites, - bridges, - censor, - ) - .await + self.daily_tasks_non_censor(config, num_users_requesting_invites, bridges, censor) + .await } } @@ -280,7 +284,6 @@ 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> { @@ -518,10 +521,31 @@ impl User { None => 0, // This is probably an error case that should not happen }; let mut new_friends = Vec::::new(); + + // Scale the probability of inviting a censor, based on the + // user's own trust level. We assume that users with + // more-trusted credentials are less likely to invite + // censors because they have more to lose. + let level_scale = match scalar_u32(&self.primary_cred.trust_level) { + // These numbers are fairly arbitrary. + Some(4) => 0.01, + Some(3) => 0.1, + Some(2) => 0.5, + _ => 1.0, + }; for _i in 0..min(invitations, num_users_requesting_invites) { if event_happens(config.prob_user_invites_friend) { - // Invite non-censor friend - match self.invite(&config, censor, false).await { + // 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 @@ -535,22 +559,6 @@ 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()) @@ -641,8 +649,6 @@ impl User { censor.learn_bridge(&fingerprint); // Censor doesn't want new_cred yet self.primary_cred = new_cred; - } else { - eprintln!("Censor failed to get new credential"); } } @@ -660,15 +666,13 @@ impl User { censor.learn_bridge(&fingerprint); // 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"); } // 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, censor, true).await { + match self.invite(&config, bridges, censor, true).await { Ok(friend) => { new_friends.push(friend); }