1
0
Просмотр исходного кода

Improve error handling and stats collection

Vecna 9 месяцев назад
Родитель
Сommit
f245ee21f9
4 измененных файлов с 269 добавлено и 134 удалено
  1. 125 39
      src/bin/simulation.rs
  2. 25 0
      src/simulation/bridge.rs
  3. 11 3
      src/simulation/censor.rs
  4. 108 92
      src/simulation/user.rs

+ 125 - 39
src/bin/simulation.rs

@@ -18,7 +18,7 @@ use troll_patrol::{
 use clap::Parser;
 use lox_cli::{networking::*, *};
 use lox_library::proto::{level_up::LEVEL_INTERVAL, trust_promotion::UNTRUSTED_INTERVAL};
-use rand::Rng;
+use rand::{prelude::SliceRandom, Rng};
 use serde::Deserialize;
 use std::{
     collections::{HashMap, HashSet},
@@ -87,7 +87,7 @@ pub async fn main() {
         hostname: "http://localhost:8004".to_string(),
     };
 
-    let la_pubkeys = get_lox_auth_keys(&la_net).await;
+    let la_pubkeys = get_lox_auth_keys(&la_net).await.unwrap();
 
     let sconfig = SConfig {
         la_pubkeys,
@@ -120,7 +120,10 @@ pub async fn main() {
     if config.num_initial_trusted_users > 0 {
         // Add some number of trusted users initially
         for _ in 0..config.num_initial_trusted_users {
-            users.push(User::trusted_user(&sconfig).await);
+            let new_user = User::trusted_user(&sconfig).await;
+            if new_user.is_ok() {
+                users.push(new_user.unwrap());
+            }
         }
 
         // Level trusted users up to level 4
@@ -138,19 +141,25 @@ pub async fn main() {
         set_simulated_date(get_date() + UNTRUSTED_INTERVAL);
 
         for user in &mut users {
-            user.primary_cred = trust_migration(
+            let migcred = trust_promotion(
                 &sconfig.la_net,
                 &user.primary_cred,
-                &trust_promotion(
+                get_lox_pub(&sconfig.la_pubkeys),
+            )
+            .await;
+            if migcred.is_ok() {
+                let new_cred = trust_migration(
                     &sconfig.la_net,
                     &user.primary_cred,
+                    &migcred.unwrap(),
                     get_lox_pub(&sconfig.la_pubkeys),
+                    get_migration_pub(&sconfig.la_pubkeys),
                 )
-                .await,
-                get_lox_pub(&sconfig.la_pubkeys),
-                get_migration_pub(&sconfig.la_pubkeys),
-            )
-            .await;
+                .await;
+                if new_cred.is_ok() {
+                    user.primary_cred = new_cred.unwrap();
+                }
+            }
         }
 
         for i in 1..LEVEL_INTERVAL.len() - 2 {
@@ -167,15 +176,23 @@ pub async fn main() {
             set_simulated_date(get_date() + LEVEL_INTERVAL[i]);
 
             for user in &mut users {
-                let reachcred = get_bucket(&sconfig.la_net, &user.primary_cred).await.1;
-                user.primary_cred = level_up(
-                    &sconfig.la_net,
-                    &user.primary_cred,
-                    &reachcred.unwrap(),
-                    get_lox_pub(&sconfig.la_pubkeys),
-                    get_reachability_pub(&sconfig.la_pubkeys),
-                )
-                .await;
+                let reachcred_res = get_bucket(&sconfig.la_net, &user.primary_cred).await;
+                if reachcred_res.is_ok() {
+                    let reachcred = reachcred_res.unwrap().1;
+                    let new_cred = level_up(
+                        &sconfig.la_net,
+                        &user.primary_cred,
+                        &reachcred.unwrap(),
+                        get_lox_pub(&sconfig.la_pubkeys),
+                        get_reachability_pub(&sconfig.la_pubkeys),
+                    )
+                    .await;
+                    if new_cred.is_ok() {
+                        user.primary_cred = new_cred.unwrap();
+                    } else {
+                        eprintln!("Failed to level up trusted user's credential at start");
+                    }
+                }
             }
         }
 
@@ -201,8 +218,10 @@ pub async fn main() {
     });
     sleep(Duration::from_millis(1)).await;
 
-    let mut fp = 0;
-    let mut tp = 0;
+    let mut false_neg = 0;
+    let mut false_pos = 0;
+    let mut true_neg = 0;
+    let mut true_pos = 0;
 
     // Main loop
     for day in 1..=config.num_days {
@@ -210,26 +229,56 @@ pub async fn main() {
 
         // USER TASKS
 
-        // Add some new users
-        let num_new_users: u32 =
+        // Number of users who want to join today
+        let mut num_users_requesting_invites: 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(&sconfig).await);
-        }
 
         let mut new_users = Vec::<User>::new();
 
+        // Shuffle users so they act in a random order
+        users.shuffle(&mut rng);
+
         // Users do daily actions
         for user in &mut users {
-            let mut invited_friends = user.daily_tasks(&sconfig, &mut bridges, &mut censor).await;
+            let invited_friends = user
+                .daily_tasks(
+                    &sconfig,
+                    num_users_requesting_invites,
+                    &mut bridges,
+                    &mut censor,
+                )
+                .await;
 
-            // If this user invited any friends, add them to the list of users
-            new_users.append(&mut invited_friends);
+            if invited_friends.is_ok() {
+                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;
+                    }
+                    // If this user invited any friends, add them to the
+                    // list of users
+                    new_users.append(&mut invited_friends);
+                }
+            }
         }
 
         // Add new users
         users.append(&mut new_users);
 
+        // 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;
+            if user.is_ok() {
+                users.push(user.unwrap());
+            } else {
+                eprintln!("Failed to create new user.");
+            }
+        }
+
         // CENSOR TASKS
         censor.end_of_day_tasks(&sconfig, &mut bridges).await;
 
@@ -257,16 +306,37 @@ pub async fn main() {
             serde_json::from_slice(&new_blockages_resp).unwrap();
 
         // TODO: Track more stats about new blockages
+        // Since we have only one censor, just convert to a set of bridges
+        let mut blocked_bridges = HashSet::<[u8; 20]>::new();
         for (bridge, ccs) in new_blockages {
             let fingerprint = array_bytes::hex2array(bridge).unwrap();
-            for cc in ccs {
-                if cc == sconfig.country {
-                    if censor.knows_bridge(&fingerprint) {
-                        tp += 1;
-                    } else {
-                        fp += 1;
-                    }
-                }
+            if ccs.contains(&sconfig.country) {
+                blocked_bridges.insert(fingerprint);
+            }
+        }
+
+        for (fingerprint, bridge) in &mut bridges {
+            let detected_blocked = blocked_bridges.contains(fingerprint);
+
+            // 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();
+            }
+
+            // 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();
+            }
+            if detected_blocked && really_blocked {
+                true_pos += 1;
+            } else if detected_blocked {
+                false_pos += 1;
+            } else if really_blocked {
+                false_neg += 1;
+            } else {
+                true_neg += 1;
             }
         }
 
@@ -286,6 +356,22 @@ pub async fn main() {
         increment_simulated_date();
     }
 
-    println!("True Positives: {}", tp);
-    println!("False Positives: {}", fp);
+    println!("True Positives: {}", true_pos);
+    println!("True Negatives: {}", true_neg);
+    println!("False Positives: {}", false_pos);
+    println!("False Negatives: {}", false_neg);
+
+    println!(
+        "Fingerprint,first_distributed,first_blocked,first_detected_blocked,first_positive_report"
+    );
+    for (fingerprint, bridge) in bridges {
+        println!(
+            "{},{},{},{},{}",
+            array_bytes::bytes2hex("", fingerprint),
+            bridge.first_distributed,
+            bridge.first_blocked,
+            bridge.first_detected_blocked,
+            bridge.first_positive_report
+        );
+    }
 }

+ 25 - 0
src/simulation/bridge.rs

@@ -5,6 +5,27 @@ use std::collections::BTreeMap;
 // The Bridge struct only tracks data for today
 pub struct Bridge {
     pub fingerprint: [u8; 20],
+
+    // The following four values are Julian dates used to track
+    // accuracy of the Troll Patrol system. A value of 0 is used to
+    // indicate this event *has not happened yet*. Note that 0 is not a
+    // date we will ever encounter.
+
+    // Date the bridge was first distributed to a user, i.e., the date
+    // we created this Bridge object
+    pub first_distributed: u32,
+
+    // First date a censor blocked this bridge
+    pub first_blocked: u32,
+
+    // First date Troll Patrol detected that this bridge was blocked
+    // (whether or not it was correct)
+    pub first_detected_blocked: u32,
+
+    // First date Troll Patrol received a positive report for this
+    // bridge (for identifying stage three)
+    pub first_positive_report: u32,
+
     real_connections: u32,
     total_connections: u32,
 }
@@ -13,6 +34,10 @@ impl Bridge {
     pub fn new(fingerprint: &[u8; 20]) -> Self {
         Self {
             fingerprint: *fingerprint,
+            first_distributed: get_date(),
+            first_blocked: 0,
+            first_detected_blocked: 0,
+            first_positive_report: 0,
             real_connections: 0,
             total_connections: 0,
         }

+ 11 - 3
src/simulation/censor.rs

@@ -55,12 +55,20 @@ impl Censor {
         self.known_bridges.contains(fingerprint)
     }
 
+    pub fn blocks_bridge(&self, config: &Config, fingerprint: &[u8; 20]) -> bool {
+        self.knows_bridge(fingerprint)
+            && (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())
+    }
+
     pub fn learn_bridge(&mut self, fingerprint: &[u8; 20]) {
         self.known_bridges.insert(*fingerprint);
     }
 
     pub fn has_lox_cred(&self, fingerprint: &[u8; 20]) -> bool {
         self.lox_credentials.contains_key(fingerprint)
+            && self.lox_credentials.get(fingerprint).unwrap().1 > 0
     }
 
     pub fn give_lox_cred(&mut self, fingerprint: &[u8; 20], cred: &Lox) {
@@ -164,21 +172,21 @@ impl Censor {
     }
 }
 
-#[derive(Debug, Deserialize, PartialEq)]
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
 pub enum Speed {
     Fast,
     Lox,
     Random,
 }
 
-#[derive(Debug, Deserialize, PartialEq)]
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
 pub enum Hides {
     Overt,
     Hiding,
     Flooding,
 }
 
-#[derive(Debug, Deserialize, PartialEq)]
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
 pub enum Totality {
     Full,
     Partial,

+ 108 - 92
src/simulation/user.rs

@@ -6,7 +6,7 @@ use crate::{
     positive_report::PositiveReport,
     simulation::{
         bridge::Bridge,
-        censor::{Censor, Hides::*, Speed::*, Totality::*},
+        censor::{Censor, Hides::*, Totality::*},
         config::Config,
     },
     BridgeDistributor,
@@ -16,7 +16,8 @@ use lox_library::{
     bridge_table::BridgeLine, cred::Lox, proto::check_blockage::MIN_TRUST_LEVEL, scalar_u32,
 };
 use rand::Rng;
-use std::collections::HashMap;
+use serde_json::error::Error;
+use std::{cmp::min, collections::HashMap};
 use x25519_dalek::PublicKey;
 
 // Helper function to probabilistically return true or false
@@ -28,7 +29,7 @@ pub fn event_happens(probability: f64) -> bool {
 
 pub struct User {
     // Does this user cooperate with a censor?
-    is_censor: bool,
+    pub 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
@@ -44,13 +45,13 @@ pub struct User {
 }
 
 impl User {
-    pub async fn new(config: &Config) -> Self {
+    pub async fn new(config: &Config) -> Result<Self, Error> {
         let cred = get_lox_credential(
             &config.la_net,
-            &get_open_invitation(&config.la_net).await,
+            &get_open_invitation(&config.la_net).await?,
             get_lox_pub(&config.la_pubkeys),
         )
-        .await
+        .await?
         .0;
 
         // Probabilistically decide whether this user cooperates with a censor
@@ -68,35 +69,35 @@ impl User {
         let mut rng = rand::thread_rng();
         let prob_use_bridges = rng.gen_range(0.0..=1.0);
 
-        Self {
+        Ok(Self {
             is_censor,
             primary_cred: cred,
             secondary_cred: None,
             submits_reports: submits_reports,
             prob_use_bridges: prob_use_bridges,
-        }
+        })
     }
 
-    pub async fn trusted_user(config: &Config) -> Self {
+    pub async fn trusted_user(config: &Config) -> Result<Self, Error> {
         let cred = get_lox_credential(
             &config.la_net,
-            &get_open_invitation(&config.la_net).await,
+            &get_open_invitation(&config.la_net).await?,
             get_lox_pub(&config.la_pubkeys),
         )
-        .await
+        .await?
         .0;
-        Self {
+        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, censor: &mut Censor) -> Result<Self, String> {
-        let etable = get_reachability_credential(&config.la_net).await;
+    pub async fn invite(&mut self, config: &Config, censor: &mut Censor) -> Result<Self, Error> {
+        let etable = get_reachability_credential(&config.la_net).await?;
         let (new_cred, invite) = issue_invite(
             &config.la_net,
             &self.primary_cred,
@@ -105,12 +106,12 @@ impl User {
             get_reachability_pub(&config.la_pubkeys),
             get_invitation_pub(&config.la_pubkeys),
         )
-        .await;
+        .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;
+            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);
@@ -123,7 +124,7 @@ impl User {
             get_lox_pub(&config.la_pubkeys),
             get_invitation_pub(&config.la_pubkeys),
         )
-        .await
+        .await?
         .0;
 
         // If the inviting user is a censor, the invitee will also be a
@@ -157,26 +158,21 @@ impl User {
 
     // Attempt to "connect" to the bridge, returns true if successful
     pub fn connect(&self, config: &Config, bridge: &mut Bridge, censor: &Censor) -> bool {
-        if censor.knows_bridge(&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.blocks_bridge(config, &bridge.fingerprint) {
+            if config.censor_totality == Full
+                || config.censor_totality == Partial
+                    && event_happens(censor.partial_blocking_percent)
+                || config.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 config.censor_hides == Hiding || config.censor_totality == Throttling {
-                        bridge.connect_total();
-                    }
-
-                    // Return false because the connection failed
-                    return false;
+                // If censor tries to hide its censorship or
+                // throttles rather than actually blocking, record a
+                // false connection
+                if config.censor_hides == Hiding || config.censor_totality == Throttling {
+                    bridge.connect_total();
                 }
+
+                // Return false because the connection failed
+                return false;
             }
         }
 
@@ -190,6 +186,15 @@ impl User {
         true
     }
 
+    pub async fn get_new_credential(config: &Config) -> Result<(Lox, BridgeLine), Error> {
+        get_lox_credential(
+            &config.la_net,
+            &get_open_invitation(&config.la_net).await?,
+            get_lox_pub(&config.la_pubkeys),
+        )
+        .await
+    }
+
     pub async fn send_negative_reports(config: &Config, reports: Vec<NegativeReport>) {
         let date = get_date();
         let pubkey = serde_json::from_slice::<Option<PublicKey>>(
@@ -226,13 +231,15 @@ 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,
-    ) -> Vec<User> {
+    ) -> Result<Vec<User>, Error> {
         if self.is_censor {
             self.daily_tasks_censor(config, bridges, censor).await
         } else {
-            self.daily_tasks_non_censor(config, bridges, censor).await
+            self.daily_tasks_non_censor(config, num_users_requesting_invites, bridges, censor)
+                .await
         }
     }
 
@@ -243,15 +250,16 @@ 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,
-    ) -> Vec<User> {
+    ) -> Result<Vec<User>, Error> {
         // 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(&config.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
@@ -304,14 +312,13 @@ impl User {
                     std::mem::replace(&mut self.secondary_cred, None)
                 } else {
                     // Get new credential
-                    let cred = get_lox_credential(
-                        &config.la_net,
-                        &get_open_invitation(&config.la_net).await,
-                        get_lox_pub(&config.la_pubkeys),
-                    )
-                    .await
-                    .0;
-                    Some(cred)
+                    match Self::get_new_credential(&config).await {
+                        Ok((cred, _bl)) => Some(cred),
+                        Err(e) => {
+                            eprintln!("Failed to get new Lox credential. Error: {}", e);
+                            None
+                        }
+                    }
                 }
             } else {
                 // If we're able to connect with the primary credential, don't
@@ -321,7 +328,7 @@ impl User {
             if second_cred.is_some() {
                 let second_cred = second_cred.as_ref().unwrap();
                 let (second_bucket, second_reachcred) =
-                    get_bucket(&config.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()) {
@@ -364,6 +371,15 @@ impl User {
                 }
                 if level >= 3 {
                     for bridgeline in &succeeded {
+                        // If we haven't received a positive report yet,
+                        // add a record about it with today's date
+                        let bridge = bridges
+                            .get_mut(&bridgeline.get_hashed_fingerprint())
+                            .unwrap();
+                        if bridge.first_positive_report == 0 {
+                            bridge.first_positive_report = get_date();
+                        }
+
                         positive_reports.push(
                             PositiveReport::from_lox_credential(
                                 bridgeline.get_hashed_fingerprint(),
@@ -391,11 +407,11 @@ impl User {
                             &self.primary_cred,
                             get_lox_pub(&config.la_pubkeys),
                         )
-                        .await,
+                        .await?,
                         get_lox_pub(&config.la_pubkeys),
                         get_migration_pub(&config.la_pubkeys),
                     )
-                    .await
+                    .await?
                 } else {
                     level_up(
                         &config.la_net,
@@ -404,7 +420,7 @@ impl User {
                         get_lox_pub(&config.la_pubkeys),
                         get_reachability_pub(&config.la_pubkeys),
                     )
-                    .await
+                    .await?
                 };
                 self.primary_cred = cred;
                 self.secondary_cred = None;
@@ -425,11 +441,11 @@ impl User {
                         &second_cred,
                         get_lox_pub(&config.la_pubkeys),
                     )
-                    .await,
+                    .await?,
                     get_lox_pub(&config.la_pubkeys),
                     get_migration_pub(&config.la_pubkeys),
                 )
-                .await;
+                .await?;
                 self.primary_cred = cred;
                 self.secondary_cred = None;
             } else if can_migrate {
@@ -441,11 +457,11 @@ impl User {
                         &self.primary_cred,
                         get_lox_pub(&config.la_pubkeys),
                     )
-                    .await,
+                    .await?,
                     get_lox_pub(&config.la_pubkeys),
                     get_migration_pub(&config.la_pubkeys),
                 )
-                .await;
+                .await?;
                 self.primary_cred = cred;
                 self.secondary_cred = None;
             } else if second_cred.is_some() {
@@ -466,7 +482,7 @@ impl User {
             // 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 {
+            for _i in 0..min(invitations, num_users_requesting_invites) {
                 if event_happens(config.prob_user_invites_friend) {
                     match self.invite(&config, censor).await {
                         Ok(friend) => {
@@ -482,9 +498,9 @@ impl User {
                 }
             }
 
-            new_friends
+            Ok(new_friends)
         } else {
-            Vec::<User>::new()
+            Ok(Vec::<User>::new())
         }
     }
 
@@ -495,10 +511,10 @@ impl User {
         config: &Config,
         bridges: &mut HashMap<[u8; 20], Bridge>,
         censor: &mut Censor,
-    ) -> Vec<User> {
+    ) -> Result<Vec<User>, Error> {
         // 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 (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
@@ -527,11 +543,11 @@ impl User {
                             &self.primary_cred,
                             get_lox_pub(&config.la_pubkeys),
                         )
-                        .await,
+                        .await?,
                         get_lox_pub(&config.la_pubkeys),
                         get_migration_pub(&config.la_pubkeys),
                     )
-                    .await
+                    .await?
                 } else {
                     level_up(
                         &config.la_net,
@@ -540,10 +556,10 @@ impl User {
                         get_lox_pub(&config.la_pubkeys),
                         get_reachability_pub(&config.la_pubkeys),
                     )
-                    .await
+                    .await?
                 };
                 self.primary_cred = new_cred;
-                let (bucket, _reachcred) = get_bucket(&config.la_net, &self.primary_cred).await;
+                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 {
@@ -561,39 +577,39 @@ impl User {
             // 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);
+            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;
+            } else {
+                eprintln!("Censor failed to get new credential");
             }
-            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);
+        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.
+        } else {
+            eprintln!("Censor failed to get new credential");
         }
-        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();
@@ -608,6 +624,6 @@ impl User {
                 }
             }
         }
-        new_friends
+        Ok(new_friends)
     }
 }