Compare commits

...

4 Commits

5 changed files with 179 additions and 62 deletions

View File

@ -57,10 +57,12 @@ pub struct Config {
// We start with this many level 4 users
pub num_initial_trusted_users: u32,
pub one_positive_report_per_cred: bool,
pub prob_censor_gets_invite: f64,
pub prob_connection_fails: f64,
pub prob_user_invites_friend: f64,
pub prob_user_is_censor: f64,
pub prob_user_submits_reports: f64,
pub prob_user_treats_throttling_as_blocking: f64,
}
#[tokio::main]
@ -101,10 +103,12 @@ pub async fn main() {
censor_partial_blocking_percent: config.censor_partial_blocking_percent,
country: config.country,
one_positive_report_per_cred: config.one_positive_report_per_cred,
prob_censor_gets_invite: config.prob_censor_gets_invite,
prob_connection_fails: config.prob_connection_fails,
prob_user_invites_friend: config.prob_user_invites_friend,
prob_user_is_censor: config.prob_user_is_censor,
prob_user_submits_reports: config.prob_user_submits_reports,
prob_user_treats_throttling_as_blocking: config.prob_user_treats_throttling_as_blocking,
};
let mut rng = rand::thread_rng();
@ -197,10 +201,6 @@ pub async fn main() {
}
}
// Have Troll Patrol run its update process so we have a negative
// report key for tomorrow
tp_net_test.request("/update".to_string(), vec![]).await;
// Advance LA's time to tomorrow
la_net_test
.request(
@ -232,12 +232,12 @@ pub async fn main() {
for day in 1..=config.num_days {
println!("Starting day {} of the simulation", day);
println!(
"We have {} users and {} bridges",
" We have {} users and {} bridges",
users.len(),
bridges.len()
);
println!(
"The censor has learned {} bridges",
" The censor has learned {} bridges",
censor.known_bridges.len()
);
@ -258,6 +258,30 @@ pub async fn main() {
let mut num_users_requesting_invites: u32 =
rng.gen_range(config.min_new_users_per_day..=config.max_new_users_per_day);
// How many of the new users are censors?
let mut num_new_censor_users = 0;
for _ in 0..num_users_requesting_invites {
let num: f64 = rng.gen_range(0.0..1.0);
if num < config.prob_user_is_censor {
num_new_censor_users += 1;
num_users_requesting_invites -= 1;
}
}
// Determine whether each new censor user can get an invite from
// an existing trusted user or needs to join via open-entry
// invite. Note: We still favor honest users by giving them
// invites *first*. This means if only a small number of invites
// are available, the censor may still not get invited.
let mut num_censor_invitations = 0;
for _ in 0..num_new_censor_users {
let num: f64 = rng.gen_range(0.0..1.0);
if num < config.prob_censor_gets_invite {
num_censor_invitations += 1;
num_new_censor_users -= 1;
}
}
let mut new_users = Vec::<User>::new();
// Shuffle users so they act in a random order
@ -269,6 +293,7 @@ pub async fn main() {
.daily_tasks(
&sconfig,
num_users_requesting_invites,
num_censor_invitations,
&mut bridges,
&mut censor,
)
@ -278,10 +303,17 @@ pub async fn main() {
let mut invited_friends = invited_friends.unwrap();
if invited_friends.len() > 0 {
if !user.is_censor {
// Users should never invite more friends than
// need invitations, so this should never become
// negative
num_users_requesting_invites -= invited_friends.len() as u32;
// Censors always invite as many censor friends
// as possible. Honest users may invite honest
// friends, or they may accidentally invite
// censor friends.
for inv_friend in &invited_friends {
if inv_friend.is_censor {
num_censor_invitations -= 1;
} else {
num_users_requesting_invites -= 1;
}
}
}
// If this user invited any friends, add them to the
// list of users
@ -296,7 +328,7 @@ pub async fn main() {
// If any users couldn't get invites, they join with open-entry
// invitations
for _ in 0..num_users_requesting_invites {
let user = User::new(&sconfig).await;
let user = User::new(&sconfig, false).await;
if user.is_ok() {
users.push(user.unwrap());
} else {
@ -304,6 +336,17 @@ pub async fn main() {
}
}
// If any censor users couldn't get invites, they also join with
// open-entry invitations
for _ in 0..(num_new_censor_users + num_censor_invitations) {
let user = User::new(&sconfig, true).await;
if user.is_ok() {
users.push(user.unwrap());
} else {
eprintln!("Failed to create new censor user.");
}
}
// CENSOR TASKS
censor.end_of_day_tasks(&sconfig, &mut bridges).await;

View File

@ -33,7 +33,14 @@ pub async fn handle(db: &Db, req: Request<Body>) -> Result<Response<Body>, Infal
return Ok(prepare_header(val));
}
};
let pubkey = get_negative_report_public_key(&db, date);
// Get the current key or generate a new one. Note that
// this code is only called in simulation. In
// production, users should not be able to induce Troll
// Patrol to generate new keys.
let pubkey = match get_negative_report_public_key(&db, date) {
Some(k) => Some(k),
None => new_negative_report_key(&db, date),
};
prepare_header(serde_json::to_string(&pubkey).unwrap())
}),
(&Method::POST, "/negativereport") => Ok::<_, Infallible>({

View File

@ -94,6 +94,31 @@ impl Censor {
}
}
// Censor sends a positive report for the given bridge. Returns true
// if successful, false otherwise.
pub async fn send_positive_report(&self, config: &Config, fingerprint: &[u8; 20]) -> bool {
// If we don't have an appropriate Lox credential, we can't send
// a report. Return false.
if !self.has_lox_cred(fingerprint) {
return false;
}
let (cred, _) = &self.lox_credentials.get(fingerprint).unwrap();
let pr = PositiveReport::from_lox_credential(
*fingerprint,
None,
cred,
get_lox_pub(&config.la_pubkeys),
config.country.clone(),
)
.unwrap();
config
.tp_net
.request("/positivereport".to_string(), pr.to_json().into_bytes())
.await;
true
}
// Make a bunch of connections and submit positive reports if possible
async fn flood(&self, config: &Config, bridges: &mut HashMap<[u8; 20], Bridge>) {
// Only do this if Flooding censor
@ -115,8 +140,7 @@ impl Censor {
// If we have a lv3+ credential, submit a bunch of
// positive reports
if self.has_lox_cred(fingerprint) {
let lox_pub = get_lox_pub(&config.la_pubkeys);
let (cred, cred_count) =
let (_cred, cred_count) =
&self.lox_credentials.get(&bridge.fingerprint).unwrap();
let num_prs = if config.one_positive_report_per_cred {
*cred_count
@ -124,18 +148,7 @@ impl Censor {
rng.gen_range(1000..30000)
};
for _ in 0..num_prs {
let pr = PositiveReport::from_lox_credential(
bridge.fingerprint,
None,
cred,
lox_pub,
config.country.clone(),
)
.unwrap();
config
.tp_net
.request("/positivereport".to_string(), pr.to_json().into_bytes())
.await;
self.send_positive_report(config, &bridge.fingerprint).await;
}
}
}

View File

@ -17,10 +17,14 @@ pub struct Config {
// share information with each other.
pub country: String,
pub one_positive_report_per_cred: bool,
// Probability that a censor-cooperating user can convince an honest
// user to give them an invite.
pub prob_censor_gets_invite: f64,
// Probability that a connection randomly fails, even though censor
// does not block the bridge
pub prob_connection_fails: f64,
pub prob_user_invites_friend: f64,
pub prob_user_is_censor: f64,
pub prob_user_submits_reports: f64,
pub prob_user_treats_throttling_as_blocking: f64,
}

View File

@ -45,7 +45,7 @@ pub struct User {
}
impl User {
pub async fn new(config: &Config) -> Result<Self, Error> {
pub async fn new(config: &Config, is_censor: bool) -> Result<Self, Error> {
let cred = get_lox_credential(
&config.la_net,
&get_open_invitation(&config.la_net).await?,
@ -54,9 +54,6 @@ impl User {
.await?
.0;
// Probabilistically decide whether this user cooperates with a censor
let is_censor = event_happens(config.prob_user_is_censor);
// Probabilistically decide whether this user submits reports
let submits_reports = if is_censor {
false
@ -96,7 +93,12 @@ impl User {
}
// TODO: This should probably return an actual error type
pub async fn invite(&mut self, config: &Config, censor: &mut Censor) -> Result<Self, Error> {
pub async fn invite(
&mut self,
config: &Config,
censor: &mut Censor,
invited_user_is_censor: bool,
) -> Result<Self, Error> {
let etable = get_reachability_credential(&config.la_net).await?;
let (new_cred, invite) = issue_invite(
&config.la_net,
@ -127,13 +129,8 @@ impl User {
.await?
.0;
// If the inviting user is a censor, the invitee will also be a
// censor. If not, probabilistically decide.
let is_censor = if self.is_censor {
true
} else {
event_happens(config.prob_user_is_censor)
};
// Calling function decides if the invited user is a censor
let is_censor = invited_user_is_censor;
// Probabilistically decide whether this user submits reports
let submits_reports = if is_censor {
@ -156,23 +153,45 @@ impl User {
})
}
// Attempt to "connect" to the bridge, returns true if successful
pub fn connect(&self, config: &Config, bridge: &mut Bridge, censor: &Censor) -> bool {
// Attempt to "connect" to the bridge, returns true if successful.
// Note that this does not involve making a real connection to a
// real bridge. The function is async because the *censor* might
// submit a positive report during this function.
pub async fn connect(&self, config: &Config, bridge: &mut Bridge, censor: &Censor) -> bool {
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 censor tries to hide its censorship or
// throttles rather than actually blocking, record a
// If censor tries to hide its censorship, record a
// false connection
if config.censor_hides == Hiding || config.censor_totality == Throttling {
if config.censor_hides == Hiding {
bridge.connect_total();
}
// Return false because the connection failed
return false;
} else if config.censor_totality == Throttling {
// With some probability, the user connects but gives up
// because there is too much interference. In this case,
// a real connection occurs, but we treat it like a
// false connection from the censor.
if event_happens(config.prob_user_treats_throttling_as_blocking) {
bridge.connect_total();
// A Hiding censor does not make an additional
// connection here, but it will make a false
// positive report if possible.
if config.censor_hides == Hiding && censor.has_lox_cred(&bridge.fingerprint) {
censor
.send_positive_report(config, &bridge.fingerprint)
.await;
}
// Return false because there was interference
// detected in the connection
return false;
}
}
}
@ -232,14 +251,21 @@ impl User {
&mut self,
config: &Config,
num_users_requesting_invites: u32,
num_censor_invites: u32,
bridges: &mut HashMap<[u8; 20], Bridge>,
censor: &mut Censor,
) -> Result<Vec<User>, Error> {
if self.is_censor {
self.daily_tasks_censor(config, bridges, censor).await
} else {
self.daily_tasks_non_censor(config, num_users_requesting_invites, bridges, censor)
.await
self.daily_tasks_non_censor(
config,
num_users_requesting_invites,
num_censor_invites,
bridges,
censor,
)
.await
}
}
@ -251,6 +277,7 @@ impl User {
&mut self,
config: &Config,
num_users_requesting_invites: u32,
num_censor_invites: u32,
bridges: &mut HashMap<[u8; 20], Bridge>,
censor: &mut Censor,
) -> Result<Vec<User>, Error> {
@ -291,13 +318,16 @@ impl User {
for i in 0..bucket.len() {
// At level 0, we only have 1 bridge
if bucket[i] != BridgeLine::default() {
if self.connect(
&config,
bridges
.get_mut(&bucket[i].get_hashed_fingerprint())
.unwrap(),
&censor,
) {
if self
.connect(
&config,
bridges
.get_mut(&bucket[i].get_hashed_fingerprint())
.unwrap(),
&censor,
)
.await
{
succeeded.push(bucket[i]);
} else {
failed.push(bucket[i]);
@ -338,13 +368,16 @@ impl User {
);
}
// Attempt to connect to second cred's bridge
if self.connect(
&config,
bridges
.get_mut(&bridgeline.get_hashed_fingerprint())
.unwrap(),
censor,
) {
if self
.connect(
&config,
bridges
.get_mut(&bridgeline.get_hashed_fingerprint())
.unwrap(),
censor,
)
.await
{
succeeded.push(bridgeline);
if second_reachcred.is_some()
&& eligible_for_trust_promotion(&config.la_net, &second_cred).await
@ -484,7 +517,8 @@ impl User {
let mut new_friends = Vec::<User>::new();
for _i in 0..min(invitations, num_users_requesting_invites) {
if event_happens(config.prob_user_invites_friend) {
match self.invite(&config, censor).await {
// Invite non-censor friend
match self.invite(&config, censor, false).await {
Ok(friend) => {
// You really shouldn't push your friends,
// especially new ones whose boundaries you
@ -498,6 +532,22 @@ impl User {
}
}
// Invite censor users if applicable
let invitations = invitations - new_friends.len() as u32;
for _i in 0..min(invitations, num_censor_invites) {
if event_happens(config.prob_user_invites_friend) {
// Invite non-censor friend
match self.invite(&config, censor, true).await {
Ok(friend) => {
new_friends.push(friend);
}
Err(e) => {
println!("{}", e);
}
}
}
}
Ok(new_friends)
} else {
Ok(Vec::<User>::new())
@ -615,7 +665,7 @@ impl User {
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 {
match self.invite(&config, censor, true).await {
Ok(friend) => {
new_friends.push(friend);
}