Parcourir la source

Disable open invites when censor shows up

The censor just wins the open-entry game when it shows up. Doing it this way is much more efficient.
Vecna il y a 7 mois
Parent
commit
5e7961df52
2 fichiers modifiés avec 212 ajouts et 88 suppressions
  1. 65 21
      src/main.rs
  2. 147 67
      src/user.rs

+ 65 - 21
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::<User>::new();
 
+    // We have some pool of invitations available
+    let mut invites = Vec::<Invitation>::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::<f64>::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");
 }

+ 147 - 67
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<Self> {
+        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<Self> {
+    ) -> Result<Invitation> {
         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<Self> {
+        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::<NegativeReport>::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<Invitation>,
     ) -> Result<Vec<User>> {
         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<Invitation>,
     ) -> Result<Vec<User>> {
         // 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::<User>::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::<User>::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::<User>::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::<User>::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);