Browse Source

Simulate only one censor at a time

Vecna 9 months ago
parent
commit
9cb967ca61
7 changed files with 399 additions and 286 deletions
  1. 28 28
      src/bin/simulation.rs
  2. 1 1
      src/lib.rs
  3. 22 33
      src/simulation/bridge.rs
  4. 49 46
      src/simulation/censor.rs
  5. 25 0
      src/simulation/config.rs
  6. 0 21
      src/simulation/state.rs
  7. 274 157
      src/simulation/user.rs

+ 28 - 28
src/bin/simulation.rs

@@ -8,9 +8,9 @@ use troll_patrol::{
     increment_simulated_date,
     simulation::{
         bridge::Bridge,
-        censor::{Censor, Hides::*, Speed::*, Totality::*},
+        censor::{self, Censor},
+        config::Config as SConfig,
         extra_infos_server,
-        state::State,
         user::User,
     },
 };
@@ -42,17 +42,20 @@ pub struct Config {
     pub la_test_port: u16,
     pub tp_port: u16,
     pub tp_test_port: u16,
+    pub censor_hides: censor::Hides,
+    pub censor_speed: censor::Speed,
+    pub censor_event_duration: u32,
+    pub censor_totality: censor::Totality,
+    pub censor_partial_blocking_percent: f64,
+    pub country: String,
     pub min_new_users_per_day: u32,
     pub max_new_users_per_day: u32,
     // How many days to simulate
     pub num_days: u32,
     pub prob_connection_fails: f64,
-    pub prob_friend_in_same_country: f64,
     pub prob_user_invites_friend: f64,
     pub prob_user_is_censor: f64,
     pub prob_user_submits_reports: f64,
-    pub probs_user_in_country: Vec<(String, f64)>,
-    pub sharing: bool,
 }
 
 #[tokio::main]
@@ -82,27 +85,26 @@ pub async fn main() {
 
     let la_pubkeys = get_lox_auth_keys(&la_net).await;
 
-    let state = State {
+    let sconfig = SConfig {
+        la_pubkeys,
         la_net,
         tp_net,
-        la_pubkeys,
+        censor_hides: config.censor_hides,
+        censor_speed: config.censor_speed,
+        censor_event_duration: config.censor_event_duration,
+        censor_totality: config.censor_totality,
+        censor_partial_blocking_percent: config.censor_partial_blocking_percent,
+        country: config.country,
         prob_connection_fails: config.prob_connection_fails,
-        prob_friend_in_same_country: config.prob_friend_in_same_country,
         prob_user_invites_friend: config.prob_user_invites_friend,
         prob_user_is_censor: config.prob_user_is_censor,
         prob_user_submits_reports: config.prob_user_submits_reports,
-        probs_user_in_country: config.probs_user_in_country.clone(),
-        sharing: config.sharing,
     };
 
     let mut rng = rand::thread_rng();
 
-    // Set up censors
-    let mut censors = HashMap::<String, Censor>::new();
-    for i in 0..config.probs_user_in_country.len() {
-        let cc = config.probs_user_in_country[i].0.clone();
-        censors.insert(cc.clone(), Censor::new(cc, Fast, Overt, Full));
-    }
+    // Set up censor
+    let mut censor = Censor::new(&sconfig);
 
     // Set up bridges (no bridges yet)
     let mut bridges = HashMap::<[u8; 20], Bridge>::new();
@@ -129,15 +131,14 @@ pub async fn main() {
         let num_new_users: 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(&state).await);
+            users.push(User::new(&sconfig).await);
         }
 
         let mut new_users = Vec::<User>::new();
 
         // Users do daily actions
         for user in &mut users {
-            // TODO: Refactor out connections from return
-            let mut invited_friends = user.daily_tasks(&state, &mut bridges, &mut censors).await;
+            let mut invited_friends = user.daily_tasks(&sconfig, &mut bridges, &mut censor).await;
 
             // If this user invited any friends, add them to the list of users
             new_users.append(&mut invited_friends);
@@ -147,15 +148,13 @@ pub async fn main() {
         users.append(&mut new_users);
 
         // CENSOR TASKS
-        for (_, censor) in censors.iter_mut() {
-            censor.end_of_day_tasks(&state, &mut bridges).await;
-        }
+        censor.end_of_day_tasks(&sconfig, &mut bridges).await;
 
         // BRIDGE TASKS
         let mut new_extra_infos = HashSet::<ExtraInfo>::new();
         for (_, bridge) in bridges.iter_mut() {
             // Bridge reports its connections for the day
-            new_extra_infos.insert(bridge.gen_extra_info());
+            new_extra_infos.insert(bridge.gen_extra_info(&sconfig.country));
 
             // Bridge resets for tomorrow
             bridge.reset_for_tomorrow();
@@ -178,11 +177,12 @@ pub async fn main() {
         for (bridge, ccs) in new_blockages {
             let fingerprint = array_bytes::hex2array(bridge).unwrap();
             for cc in ccs {
-                let censor = censors.get(&cc).unwrap();
-                if censor.knows_bridge(&fingerprint) {
-                    tp += 1;
-                } else {
-                    fp += 1;
+                if cc == sconfig.country {
+                    if censor.knows_bridge(&fingerprint) {
+                        tp += 1;
+                    } else {
+                        fp += 1;
+                    }
                 }
             }
         }

+ 1 - 1
src/lib.rs

@@ -31,8 +31,8 @@ pub mod request_handler;
 pub mod simulation {
     pub mod bridge;
     pub mod censor;
+    pub mod config;
     pub mod extra_infos_server;
-    pub mod state;
     pub mod user;
 }
 

+ 22 - 33
src/simulation/bridge.rs

@@ -1,20 +1,20 @@
 use crate::{extra_info::ExtraInfo, get_date};
 use lox_library::bridge_table::BridgeLine;
-use std::collections::{BTreeMap, HashMap};
+use std::collections::BTreeMap;
 
 // The Bridge struct only tracks data for today
 pub struct Bridge {
     pub fingerprint: [u8; 20],
-    real_connections: HashMap<String, u32>,
-    total_connections: BTreeMap<String, u32>,
+    real_connections: u32,
+    total_connections: u32,
 }
 
 impl Bridge {
     pub fn new(fingerprint: &[u8; 20]) -> Self {
         Self {
             fingerprint: *fingerprint,
-            real_connections: HashMap::<String, u32>::new(),
-            total_connections: BTreeMap::<String, u32>::new(),
+            real_connections: 0,
+            total_connections: 0,
         }
     }
 
@@ -22,49 +22,38 @@ impl Bridge {
         Self::new(&bridgeline.get_hashed_fingerprint())
     }
 
-    pub fn connect_real(&mut self, country: &str) {
-        if self.real_connections.contains_key(country) {
-            let prev = self.real_connections.get(country).unwrap();
-            self.real_connections.insert(country.to_string(), prev + 1);
-        } else {
-            self.real_connections.insert(country.to_string(), 1);
-        }
-        self.connect_total(country);
+    pub fn connect_real(&mut self) {
+        self.real_connections += 1;
+        self.total_connections += 1;
     }
 
-    pub fn connect_total(&mut self, country: &str) {
-        if self.total_connections.contains_key(country) {
-            let prev = self.total_connections.get(country).unwrap();
-            self.total_connections.insert(country.to_string(), prev + 1);
-        } else {
-            self.total_connections.insert(country.to_string(), 1);
-        }
+    pub fn connect_total(&mut self) {
+        self.total_connections += 1;
     }
 
     // Let the censor simulate a bunch of connections at once
-    pub fn censor_flood(&mut self, country: &str, num_connections: u32) {
-        if self.total_connections.contains_key(country) {
-            let prev = self.total_connections.get(country).unwrap();
-            self.total_connections
-                .insert(country.to_string(), prev + num_connections);
-        } else {
-            self.total_connections
-                .insert(country.to_string(), num_connections);
-        }
+    pub fn censor_flood(&mut self, num_connections: u32) {
+        self.total_connections += num_connections;
     }
 
     // Generate an extra-info report for today
-    pub fn gen_extra_info(&self) -> ExtraInfo {
+    pub fn gen_extra_info(&self, country: &str) -> ExtraInfo {
+        let mut bridge_ips = BTreeMap::<String, u32>::new();
+        // Round up to a multiple of 8
+        let rounded_connection_count =
+            self.total_connections + 7 - (self.total_connections + 7) % 8;
+        //let rounded_connection_count = (self.total_connections + 7) / 8 * 8;
+        bridge_ips.insert(country.to_string(), rounded_connection_count);
         ExtraInfo {
             nickname: String::from("simulation-bridge"),
             fingerprint: self.fingerprint,
             date: get_date(),
-            bridge_ips: self.total_connections.clone(),
+            bridge_ips,
         }
     }
 
     pub fn reset_for_tomorrow(&mut self) {
-        self.real_connections = HashMap::<String, u32>::new();
-        self.total_connections = BTreeMap::<String, u32>::new();
+        self.real_connections = 0;
+        self.total_connections = 0;
     }
 }

+ 49 - 46
src/simulation/censor.rs

@@ -1,59 +1,52 @@
 use crate::{
     get_date,
-    simulation::{bridge::Bridge, state::State},
+    simulation::{bridge::Bridge, config::Config},
     PositiveReport,
 };
 
 use lox_cli::{get_lox_pub, networking::Networking};
 use lox_library::{cred::Lox, scalar_u32};
 use rand::Rng;
+use serde::Deserialize;
 use std::collections::{HashMap, HashSet};
 
 pub struct Censor {
-    pub country: String,
     pub known_bridges: HashSet<[u8; 20]>,
-    pub lox_credentials: HashMap<[u8; 20], Lox>,
 
-    // How fast does this censor block bridges after learning about them?
-    pub speed: Speed,
+    // We don't actually implement the technical restriction to prevent
+    // one Lox credential from being used to submit many reports, so we
+    // just implement this as a map of bridge fingerprint to (most
+    // recent Lox credential for this bridge, count of unique level 3+
+    // credentials we have for this bridge).
+    pub lox_credentials: HashMap<[u8; 20], (Lox, u32)>,
+
     // If censor implements random blocking, this is the date when it
     // will start blocking all the bridges it knows.
     pub delay_date: u32,
 
-    // Does the censor attempt to hide the fact that a bridge has been blocked?
-    pub hides: Hides,
-
-    // Does the censor block bridges uniformly across the country?
-    pub totality: Totality,
     // If censor implements partial blocking, what percent of
-    // connections are blocked? If totality is not partial, this is set
-    // to 100%.
+    // connections are blocked?
     pub partial_blocking_percent: f64,
 }
 
 impl Censor {
-    pub fn new(country: String, speed: Speed, hides: Hides, totality: Totality) -> Self {
+    pub fn new(config: &Config) -> Self {
         let mut rng = rand::thread_rng();
-        let delay_date = if speed == Speed::Random {
+        let delay_date = if config.censor_speed == Speed::Random {
             let num: u32 = rng.gen_range(1..365);
             get_date() + num
         } else {
             0
         };
-        let partial_blocking_percent = if totality == Totality::Partial {
-            let num: f64 = rng.gen_range(0.0..1.0);
-            num
+        let partial_blocking_percent = if config.censor_totality == Totality::Partial {
+            config.censor_partial_blocking_percent
         } else {
             1.0
         };
         Censor {
-            country: country,
             known_bridges: HashSet::<[u8; 20]>::new(),
-            lox_credentials: HashMap::<[u8; 20], Lox>::new(),
-            speed: speed,
+            lox_credentials: HashMap::<[u8; 20], (Lox, u32)>::new(),
             delay_date: delay_date,
-            hides: hides,
-            totality: totality,
             partial_blocking_percent: partial_blocking_percent,
         }
     }
@@ -78,41 +71,52 @@ impl Censor {
             // We want to clone the credential, but that's not allowed,
             // so we're going to serialize it and then deserialize it.
             let cloned_cred = bincode::deserialize(&bincode::serialize(&cred).unwrap()).unwrap();
-            self.lox_credentials.insert(*fingerprint, cloned_cred);
+
+            // Insert the new credential and add to the count of unique
+            // credentials we have. We assume that a duplicate
+            // credential will never be given. If we don't want to make
+            // this assumption, we could change the count from a u32 to
+            // a set of credential IDs and get the count as its length.
+            let count = match self.lox_credentials.get(fingerprint) {
+                Some((_cred, count)) => *count,
+                None => 0,
+            };
+            self.lox_credentials
+                .insert(*fingerprint, (cloned_cred, count + 1));
         }
     }
 
     // Make a bunch of connections and submit positive reports if possible
-    async fn flood(&self, state: &State, bridges: &mut HashMap<[u8; 20], Bridge>) {
+    async fn flood(&self, config: &Config, bridges: &mut HashMap<[u8; 20], Bridge>) {
         // Only do this if Flooding censor
-        if self.hides == Hides::Flooding {
+        if config.censor_hides == Hides::Flooding {
             for fingerprint in &self.known_bridges {
                 // Only do this if we're blocking the bridge
-                if self.speed == Speed::Fast
-                    || self.speed == Speed::Lox && self.has_lox_cred(fingerprint)
-                    || self.speed == Speed::Random && self.delay_date <= get_date()
+                if 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()
                 {
                     let bridge = bridges.get_mut(fingerprint).unwrap();
                     let mut rng = rand::thread_rng();
                     let num_connections = rng.gen_range(1000..30000);
 
                     // Make a bunch of connections to the bridge
-                    bridge.censor_flood(&self.country, num_connections);
+                    bridge.censor_flood(num_connections);
 
                     // If we have a lv3+ credential, submit a bunch of
                     // positive reports
                     if self.has_lox_cred(fingerprint) {
-                        let lox_pub = get_lox_pub(&state.la_pubkeys);
+                        let lox_pub = get_lox_pub(&config.la_pubkeys);
                         for _ in 0..num_connections {
                             let pr = PositiveReport::from_lox_credential(
                                 bridge.fingerprint,
                                 None,
-                                &self.lox_credentials.get(&bridge.fingerprint).unwrap(),
+                                &self.lox_credentials.get(&bridge.fingerprint).unwrap().0,
                                 lox_pub,
-                                self.country.clone(),
+                                config.country.clone(),
                             )
                             .unwrap();
-                            state
+                            config
                                 .tp_net
                                 .request("/positivereport".to_string(), pr.to_json().into_bytes())
                                 .await;
@@ -123,11 +127,11 @@ impl Censor {
         }
     }
 
-    // TODO: How do we want to do this? We don't want to stop blocking
-    // bridges the day after we start.
-    fn recompute_delay(&mut self) {
+    fn recompute_delay(&mut self, config: &Config) {
         // Only do this if Random censor
-        if self.speed == Speed::Random && self.delay_date <= get_date() {
+        if config.censor_speed == Speed::Random
+            && self.delay_date + config.censor_event_duration <= get_date()
+        {
             // Compute new delay date
             self.delay_date = {
                 let mut rng = rand::thread_rng();
@@ -139,35 +143,34 @@ impl Censor {
 
     pub async fn end_of_day_tasks(
         &mut self,
-        state: &State,
+        config: &Config,
         bridges: &mut HashMap<[u8; 20], Bridge>,
     ) {
-        if self.hides == Hides::Flooding
-            && !(self.speed == Speed::Random && self.delay_date <= get_date())
+        if config.censor_hides == Hides::Flooding
+            && !(config.censor_speed == Speed::Random && self.delay_date <= get_date())
         {
-            self.flood(state, bridges).await;
+            self.flood(config, bridges).await;
         }
 
-        // TODO: recompute_delay sometimes
-        //self.recompute_delay();
+        self.recompute_delay(config);
     }
 }
 
-#[derive(PartialEq)]
+#[derive(Debug, Deserialize, PartialEq)]
 pub enum Speed {
     Fast,
     Lox,
     Random,
 }
 
-#[derive(PartialEq)]
+#[derive(Debug, Deserialize, PartialEq)]
 pub enum Hides {
     Overt,
     Hiding,
     Flooding,
 }
 
-#[derive(PartialEq)]
+#[derive(Debug, Deserialize, PartialEq)]
 pub enum Totality {
     Full,
     Partial,

+ 25 - 0
src/simulation/config.rs

@@ -0,0 +1,25 @@
+use crate::simulation::censor;
+
+use lox_cli::networking::*;
+use lox_library::IssuerPubKey;
+
+pub struct Config {
+    pub la_pubkeys: Vec<IssuerPubKey>,
+    pub la_net: HyperNet,
+    pub tp_net: HyperNet,
+    // Define censor behavior
+    pub censor_hides: censor::Hides,
+    pub censor_speed: censor::Speed,
+    pub censor_event_duration: u32,
+    pub censor_totality: censor::Totality,
+    pub censor_partial_blocking_percent: f64,
+    // We model only one country at a time because Lox assumes censors
+    // share information with each other.
+    pub country: String,
+    // Probability that a connection randomly fails, even though censor
+    // does not block the bridge
+    pub prob_connection_fails: f64,
+    pub prob_user_invites_friend: f64,
+    pub prob_user_is_censor: f64,
+    pub prob_user_submits_reports: f64,
+}

+ 0 - 21
src/simulation/state.rs

@@ -1,21 +0,0 @@
-use lox_cli::networking::*;
-use lox_library::IssuerPubKey;
-
-pub struct State {
-    pub la_pubkeys: Vec<IssuerPubKey>,
-    pub la_net: HyperNet,
-    pub tp_net: HyperNet,
-    // Probability that a connection randomly fails, even though censor
-    // does not block the bridge
-    pub prob_connection_fails: f64,
-    // Probability that if Alice invites Bob, Alice and Bob are in the same
-    // country. This is in *addition* to the regular probability that Bob is in
-    // that country by random selection.
-    pub prob_friend_in_same_country: f64,
-    pub prob_user_invites_friend: f64,
-    pub prob_user_is_censor: f64,
-    pub prob_user_submits_reports: f64,
-    pub probs_user_in_country: Vec<(String, f64)>,
-    // Do the censors talk to each other?
-    pub sharing: bool,
-}

+ 274 - 157
src/simulation/user.rs

@@ -7,9 +7,9 @@ use crate::{
     simulation::{
         bridge::Bridge,
         censor::{Censor, Hides::*, Speed::*, Totality::*},
-        state::State,
+        config::Config,
     },
-    BridgeDistributor, COUNTRY_CODES,
+    BridgeDistributor,
 };
 use lox_cli::{networking::*, *};
 use lox_library::{
@@ -28,10 +28,7 @@ pub fn event_happens(probability: f64) -> bool {
 
 pub struct User {
     // Does this user cooperate with a censor?
-    censor: bool,
-
-    // 2-character country code
-    country: String,
+    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
@@ -47,46 +44,32 @@ pub struct User {
 }
 
 impl User {
-    pub async fn new(state: &State) -> Self {
+    pub async fn new(config: &Config) -> Self {
         let cred = get_lox_credential(
-            &state.la_net,
-            &get_open_invitation(&state.la_net).await,
-            get_lox_pub(&state.la_pubkeys),
+            &config.la_net,
+            &get_open_invitation(&config.la_net).await,
+            get_lox_pub(&config.la_pubkeys),
         )
         .await
         .0;
 
         // Probabilistically decide whether this user cooperates with a censor
-        let censor = event_happens(state.prob_user_is_censor);
+        let is_censor = event_happens(config.prob_user_is_censor);
 
         // Probabilistically decide whether this user submits reports
-        let submits_reports = event_happens(state.prob_user_submits_reports);
-
-        // Probabilistically decide user's country
-        let mut rng = rand::thread_rng();
-        let mut num: f64 = rng.gen_range(0.0..1.0);
-        let cc = {
-            let mut cc = String::default();
-            for (country, prob) in &state.probs_user_in_country {
-                let prob = *prob;
-                if num < prob {
-                    cc = country.to_string();
-                    break;
-                } else {
-                    num -= prob;
-                }
-            }
-            cc
+        let submits_reports = if is_censor {
+            false
+        } else {
+            event_happens(config.prob_user_submits_reports)
         };
-        assert!(COUNTRY_CODES.contains(cc.as_str()));
 
         // 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);
 
         Self {
-            censor: censor,
-            country: cc,
+            is_censor,
             primary_cred: cred,
             secondary_cred: None,
             submits_reports: submits_reports,
@@ -95,55 +78,50 @@ impl User {
     }
 
     // TODO: This should probably return an actual error type
-    pub async fn invite(&mut self, state: &State) -> Result<Self, String> {
-        let etable = get_reachability_credential(&state.la_net).await;
+    pub async fn invite(&mut self, config: &Config, censor: &mut Censor) -> Result<Self, String> {
+        let etable = get_reachability_credential(&config.la_net).await;
         let (new_cred, invite) = issue_invite(
-            &state.la_net,
+            &config.la_net,
             &self.primary_cred,
             &etable,
-            get_lox_pub(&state.la_pubkeys),
-            get_reachability_pub(&state.la_pubkeys),
-            get_invitation_pub(&state.la_pubkeys),
+            get_lox_pub(&config.la_pubkeys),
+            get_reachability_pub(&config.la_pubkeys),
+            get_invitation_pub(&config.la_pubkeys),
         )
         .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;
+            for bl in bucket {
+                let fingerprint = bl.get_hashed_fingerprint();
+                censor.learn_bridge(&fingerprint);
+                censor.give_lox_cred(&fingerprint, &self.primary_cred);
+            }
+        }
         let friend_cred = redeem_invite(
-            &state.la_net,
+            &config.la_net,
             &invite,
-            get_lox_pub(&state.la_pubkeys),
-            get_invitation_pub(&state.la_pubkeys),
+            get_lox_pub(&config.la_pubkeys),
+            get_invitation_pub(&config.la_pubkeys),
         )
         .await
         .0;
 
-        // Probabilistically decide whether this user cooperates with a censor
-        // We do not influence this by the inviting friend's status. Anyone
-        // might have friends who are untrustworthy, and censors may invite
-        // non-censors to maintain an illusion of trustworthiness. Also, a
-        // "censor" user may not be knowingly helping a censor.
-        let censor = event_happens(state.prob_user_is_censor);
+        // 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)
+        };
 
         // Probabilistically decide whether this user submits reports
-        let submits_reports = event_happens(state.prob_user_submits_reports);
-
-        // Determine user's country
-        let cc = if event_happens(state.prob_friend_in_same_country) {
-            self.country.to_string()
+        let submits_reports = if is_censor {
+            false
         } else {
-            // Probabilistically decide user's country
-            let mut rng = rand::thread_rng();
-            let mut num: f64 = rng.gen_range(0.0..1.0);
-            let mut cc = String::default();
-            for (country, prob) in &state.probs_user_in_country {
-                let prob = *prob;
-                if num < prob {
-                    cc = country.to_string();
-                    break;
-                } else {
-                    num -= prob;
-                }
-            }
-            cc
+            event_happens(config.prob_user_submits_reports)
         };
 
         // Randomly determine how likely this user is to use bridges on
@@ -152,8 +130,7 @@ impl User {
         let prob_use_bridges = rng.gen_range(0.0..=1.0);
 
         Ok(Self {
-            censor: censor,
-            country: cc,
+            is_censor,
             primary_cred: friend_cred,
             secondary_cred: None,
             submits_reports: submits_reports,
@@ -162,21 +139,22 @@ impl User {
     }
 
     // Attempt to "connect" to the bridge, returns true if successful
-    pub fn connect(&self, state: &State, bridge: &mut Bridge, censor: &Censor) -> bool {
+    pub fn connect(&self, config: &Config, bridge: &mut Bridge, censor: &Censor) -> bool {
         if censor.knows_bridge(&bridge.fingerprint) {
-            if censor.speed == Fast
-                || censor.speed == Random && censor.delay_date <= get_date()
-                || censor.speed == Lox && censor.has_lox_cred(&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.totality == Full
-                    || censor.totality == Partial && event_happens(censor.partial_blocking_percent)
-                    || 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 censor.hides == Hiding || censor.totality == Throttling {
-                        bridge.connect_total(&self.country);
+                    if config.censor_hides == Hiding || config.censor_totality == Throttling {
+                        bridge.connect_total();
                     }
 
                     // Return false because the connection failed
@@ -186,19 +164,19 @@ impl User {
         }
 
         // Connection may randomly fail, without censor intervention
-        if event_happens(state.prob_connection_fails) {
+        if event_happens(config.prob_connection_fails) {
             return false;
         }
 
         // If we haven't returned yet, the connection succeeded
-        bridge.connect_real(&self.country);
+        bridge.connect_real();
         true
     }
 
-    pub async fn send_negative_reports(state: &State, reports: Vec<NegativeReport>) {
+    pub async fn send_negative_reports(config: &Config, reports: Vec<NegativeReport>) {
         let date = get_date();
         let pubkey = serde_json::from_slice::<Option<PublicKey>>(
-            &state
+            &config
                 .tp_net
                 .request(
                     "/nrkey".to_string(),
@@ -209,7 +187,7 @@ impl User {
         .unwrap()
         .unwrap();
         for report in reports {
-            state
+            config
                 .tp_net
                 .request(
                     "/negativereport".to_string(),
@@ -219,32 +197,44 @@ impl User {
         }
     }
 
-    pub async fn send_positive_reports(state: &State, reports: Vec<PositiveReport>) {
+    pub async fn send_positive_reports(config: &Config, reports: Vec<PositiveReport>) {
         for report in reports {
-            state
+            config
                 .tp_net
                 .request("/positivereport".to_string(), report.to_json().into_bytes())
                 .await;
         }
     }
 
-    // User performs daily connection attempts, etc. and returns a
-    // vector of newly invited friends and a vector of fingerprints of
-    // successfully contacted bridges.
-    // TODO: The maps of bridges and censors should be Arc<Mutex<>> or
-    // something so we can parallelize this.
     pub async fn daily_tasks(
         &mut self,
-        state: &State,
+        config: &Config,
         bridges: &mut HashMap<[u8; 20], Bridge>,
-        censors: &mut HashMap<String, Censor>,
+        censor: &mut Censor,
+    ) -> Vec<User> {
+        if self.is_censor {
+            self.daily_tasks_censor(config, bridges, censor).await
+        } else {
+            self.daily_tasks_non_censor(config, bridges, censor).await
+        }
+    }
+
+    // User performs daily connection attempts, etc. and returns a
+    // vector of newly invited friends.
+    // TODO: The map of bridges and the censor should be Arc<Mutex<>>
+    // or something so we can parallelize this.
+    pub async fn daily_tasks_non_censor(
+        &mut self,
+        config: &Config,
+        bridges: &mut HashMap<[u8; 20], Bridge>,
+        censor: &mut Censor,
     ) -> Vec<User> {
         // 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(&state.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
@@ -254,30 +244,15 @@ impl User {
                         let bridge = Bridge::from_bridge_line(&bridgeline);
                         bridges.insert(bridgeline.get_hashed_fingerprint(), bridge);
                     }
-                    // Also, if this user cooperates with censors, make sure
-                    // each applicable censor knows about their bridges.
-                    if self.censor {
-                        if state.sharing {
-                            for c in censors.values_mut() {
-                                if !c.knows_bridge(&bridgeline.get_hashed_fingerprint()) {
-                                    c.learn_bridge(&bridgeline.get_hashed_fingerprint());
-                                }
-                            }
-                        } else {
-                            let censor = censors.get_mut(&self.country).unwrap();
-                            if !censor.knows_bridge(&bridgeline.get_hashed_fingerprint()) {
-                                censor.learn_bridge(&bridgeline.get_hashed_fingerprint());
-                            }
-                        }
-                    }
                 }
             }
 
             // Can we level up the main credential?
             let can_level_up = reachcred.is_some()
                 && (level == 0
-                    && eligible_for_trust_promotion(&state.la_net, &self.primary_cred).await
-                    || level > 0 && eligible_for_level_up(&state.la_net, &self.primary_cred).await);
+                    && eligible_for_trust_promotion(&config.la_net, &self.primary_cred).await
+                    || level > 0
+                        && eligible_for_level_up(&config.la_net, &self.primary_cred).await);
 
             // Can we migrate the main credential?
             let can_migrate = reachcred.is_none() && level >= MIN_TRUST_LEVEL;
@@ -285,18 +260,18 @@ impl User {
             // Can we level up the secondary credential?
             let mut second_level_up = false;
 
-            // Attempt to connect to each bridge
             let mut failed = Vec::<BridgeLine>::new();
             let mut succeeded = Vec::<BridgeLine>::new();
+            // Try to connect to each bridge
             for i in 0..bucket.len() {
                 // At level 0, we only have 1 bridge
                 if bucket[i] != BridgeLine::default() {
                     if self.connect(
-                        &state,
+                        &config,
                         bridges
                             .get_mut(&bucket[i].get_hashed_fingerprint())
                             .unwrap(),
-                        &censors.get(&self.country).unwrap(),
+                        &censor,
                     ) {
                         succeeded.push(bucket[i]);
                     } else {
@@ -304,15 +279,18 @@ impl User {
                     }
                 }
             }
+
+            // If we were not able to connect to any bridges, get a
+            // second credential
             let second_cred = if succeeded.len() < 1 {
                 if self.secondary_cred.is_some() {
                     std::mem::replace(&mut self.secondary_cred, None)
                 } else {
                     // Get new credential
                     let cred = get_lox_credential(
-                        &state.la_net,
-                        &get_open_invitation(&state.la_net).await,
-                        get_lox_pub(&state.la_pubkeys),
+                        &config.la_net,
+                        &get_open_invitation(&config.la_net).await,
+                        get_lox_pub(&config.la_pubkeys),
                     )
                     .await
                     .0;
@@ -326,7 +304,7 @@ impl User {
             if second_cred.is_some() {
                 let second_cred = second_cred.as_ref().unwrap();
                 let (second_bucket, second_reachcred) =
-                    get_bucket(&state.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()) {
@@ -335,16 +313,17 @@ impl User {
                                 Bridge::from_bridge_line(&bridgeline),
                             );
                         }
+                        // Attempt to connect to second cred's bridge
                         if self.connect(
-                            &state,
+                            &config,
                             bridges
                                 .get_mut(&bridgeline.get_hashed_fingerprint())
                                 .unwrap(),
-                            &censors.get(&self.country).unwrap(),
+                            censor,
                         ) {
                             succeeded.push(bridgeline);
                             if second_reachcred.is_some()
-                                && eligible_for_trust_promotion(&state.la_net, &second_cred).await
+                                && eligible_for_trust_promotion(&config.la_net, &second_cred).await
                             {
                                 second_level_up = true;
                             }
@@ -357,11 +336,12 @@ impl User {
 
             let mut negative_reports = Vec::<NegativeReport>::new();
             let mut positive_reports = Vec::<PositiveReport>::new();
+
             if self.submits_reports {
                 for bridgeline in &failed {
                     negative_reports.push(NegativeReport::from_bridgeline(
                         *bridgeline,
-                        self.country.to_string(),
+                        config.country.to_string(),
                         BridgeDistributor::Lox,
                     ));
                 }
@@ -372,8 +352,8 @@ impl User {
                                 bridgeline.get_hashed_fingerprint(),
                                 None,
                                 &self.primary_cred,
-                                get_lox_pub(&state.la_pubkeys),
-                                self.country.to_string(),
+                                get_lox_pub(&config.la_pubkeys),
+                                config.country.to_string(),
                             )
                             .unwrap(),
                         );
@@ -385,54 +365,68 @@ impl User {
             // we do assume the user can contact the LA somehow, so
             // let's just allow it.
             if can_level_up {
-                let cred = level_up(
-                    &state.la_net,
-                    &self.primary_cred,
-                    &reachcred.unwrap(),
-                    get_lox_pub(&state.la_pubkeys),
-                    get_reachability_pub(&state.la_pubkeys),
-                )
-                .await;
+                let cred = if level == 0 {
+                    trust_migration(
+                        &config.la_net,
+                        &self.primary_cred,
+                        &trust_promotion(
+                            &config.la_net,
+                            &self.primary_cred,
+                            get_lox_pub(&config.la_pubkeys),
+                        )
+                        .await,
+                        get_lox_pub(&config.la_pubkeys),
+                        get_migration_pub(&config.la_pubkeys),
+                    )
+                    .await
+                } else {
+                    level_up(
+                        &config.la_net,
+                        &self.primary_cred,
+                        &reachcred.unwrap(),
+                        get_lox_pub(&config.la_pubkeys),
+                        get_reachability_pub(&config.la_pubkeys),
+                    )
+                    .await
+                };
                 self.primary_cred = cred;
                 self.secondary_cred = None;
-
-                if self.censor {
-                    // Make sure censor has access to each bridge and
-                    // each credential
-                    let censor = censors.get_mut(&self.country).unwrap();
-                    let (bucket, reachcred) = get_bucket(&state.la_net, &self.primary_cred).await;
-                    for bl in bucket {
-                        censor.learn_bridge(&bl.get_hashed_fingerprint());
-                        censor.give_lox_cred(&bl.get_hashed_fingerprint(), &self.primary_cred);
-                    }
-                }
             }
-            // We favor starting over at level 1 to migrating
-            else if second_level_up {
+            // We favor starting over at level 1 to migrating to level
+            // 1, but if we have a level 4 credential for a bridge that
+            // hasn't been marked blocked, save the credential so we can
+            // migrate to a level 2 cred. Note that second_level_up is
+            // only true if we were unable to connect with bridges from
+            // our primary credential.
+            else if second_level_up && (level <= MIN_TRUST_LEVEL || reachcred.is_none()) {
                 let second_cred = second_cred.as_ref().unwrap();
                 let cred = trust_migration(
-                    &state.la_net,
+                    &config.la_net,
                     &second_cred,
-                    &trust_promotion(&state.la_net, &second_cred, get_lox_pub(&state.la_pubkeys))
-                        .await,
-                    get_lox_pub(&state.la_pubkeys),
-                    get_migration_pub(&state.la_pubkeys),
+                    &trust_promotion(
+                        &config.la_net,
+                        &second_cred,
+                        get_lox_pub(&config.la_pubkeys),
+                    )
+                    .await,
+                    get_lox_pub(&config.la_pubkeys),
+                    get_migration_pub(&config.la_pubkeys),
                 )
                 .await;
                 self.primary_cred = cred;
                 self.secondary_cred = None;
             } else if can_migrate {
                 let cred = blockage_migration(
-                    &state.la_net,
+                    &config.la_net,
                     &self.primary_cred,
                     &check_blockage(
-                        &state.la_net,
+                        &config.la_net,
                         &self.primary_cred,
-                        get_lox_pub(&state.la_pubkeys),
+                        get_lox_pub(&config.la_pubkeys),
                     )
                     .await,
-                    get_lox_pub(&state.la_pubkeys),
-                    get_migration_pub(&state.la_pubkeys),
+                    get_lox_pub(&config.la_pubkeys),
+                    get_migration_pub(&config.la_pubkeys),
                 )
                 .await;
                 self.primary_cred = cred;
@@ -446,18 +440,18 @@ impl User {
             }
 
             if negative_reports.len() > 0 {
-                Self::send_negative_reports(&state, negative_reports).await;
+                Self::send_negative_reports(&config, negative_reports).await;
             }
             if positive_reports.len() > 0 {
-                Self::send_positive_reports(&state, positive_reports).await;
+                Self::send_positive_reports(&config, positive_reports).await;
             }
 
             // Invite friends if applicable
             let invitations = scalar_u32(&self.primary_cred.invites_remaining).unwrap();
             let mut new_friends = Vec::<User>::new();
             for _i in 0..invitations {
-                if event_happens(state.prob_user_invites_friend) {
-                    match self.invite(&state).await {
+                if event_happens(config.prob_user_invites_friend) {
+                    match self.invite(&config, censor).await {
                         Ok(friend) => {
                             // You really shouldn't push your friends,
                             // especially new ones whose boundaries you
@@ -476,4 +470,127 @@ impl User {
             Vec::<User>::new()
         }
     }
+
+    // User cooperates with censor and performs daily tasks to try to
+    // learn more bridges.
+    pub async fn daily_tasks_censor(
+        &mut self,
+        config: &Config,
+        bridges: &mut HashMap<[u8; 20], Bridge>,
+        censor: &mut Censor,
+    ) -> Vec<User> {
+        // 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 level = scalar_u32(&self.primary_cred.trust_level).unwrap();
+
+        // Make sure each bridge is in global bridges set and known by
+        // censor
+        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());
+            }
+        }
+
+        // Censor user tries to level up their primary credential
+        if reachcred.is_some() {
+            if level == 0 && eligible_for_trust_promotion(&config.la_net, &self.primary_cred).await
+                || level > 0 && eligible_for_level_up(&config.la_net, &self.primary_cred).await
+            {
+                let new_cred = if level == 0 {
+                    trust_migration(
+                        &config.la_net,
+                        &self.primary_cred,
+                        &trust_promotion(
+                            &config.la_net,
+                            &self.primary_cred,
+                            get_lox_pub(&config.la_pubkeys),
+                        )
+                        .await,
+                        get_lox_pub(&config.la_pubkeys),
+                        get_migration_pub(&config.la_pubkeys),
+                    )
+                    .await
+                } else {
+                    level_up(
+                        &config.la_net,
+                        &self.primary_cred,
+                        &reachcred.unwrap(),
+                        get_lox_pub(&config.la_pubkeys),
+                        get_reachability_pub(&config.la_pubkeys),
+                    )
+                    .await
+                };
+                self.primary_cred = new_cred;
+                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 {
+                    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.give_lox_cred(&fingerprint, &self.primary_cred);
+                }
+            }
+        } else {
+            // LA has identified this bucket as blocked. This change
+            // 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 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;
+        }
+
+        // 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::<User>::new();
+        for _ in 0..invitations {
+            match self.invite(&config, censor).await {
+                Ok(friend) => {
+                    new_friends.push(friend);
+                }
+                Err(e) => {
+                    println!("{}", e);
+                }
+            }
+        }
+        new_friends
+    }
 }