Change how censor-cooperating users join the simulation
This commit is contained in:
parent
19fc667b36
commit
ebc508b64f
|
@ -57,6 +57,10 @@ impl Censor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_active(&self) -> bool {
|
||||||
|
get_date() >= self.start_date
|
||||||
|
}
|
||||||
|
|
||||||
pub fn knows_bridge(&self, fingerprint: &[u8; 20]) -> bool {
|
pub fn knows_bridge(&self, fingerprint: &[u8; 20]) -> bool {
|
||||||
self.known_bridges.contains(fingerprint)
|
self.known_bridges.contains(fingerprint)
|
||||||
}
|
}
|
||||||
|
@ -218,7 +222,9 @@ impl Censor {
|
||||||
config: &Config,
|
config: &Config,
|
||||||
bridges: &mut HashMap<[u8; 20], Bridge>,
|
bridges: &mut HashMap<[u8; 20], Bridge>,
|
||||||
) {
|
) {
|
||||||
if get_date() >= self.start_date {
|
// This check is redundant because the simulation driver only
|
||||||
|
// calls this function if the censor is active.
|
||||||
|
if self.is_active() {
|
||||||
if config.censor_secrecy == Secrecy::Flooding
|
if config.censor_secrecy == Secrecy::Flooding
|
||||||
&& !(config.censor_speed == Speed::Random && self.delay_date <= get_date())
|
&& !(config.censor_speed == Speed::Random && self.delay_date <= get_date())
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,7 +19,9 @@ pub struct Config {
|
||||||
pub country: String,
|
pub country: String,
|
||||||
pub one_positive_report_per_cred: bool,
|
pub one_positive_report_per_cred: bool,
|
||||||
// Probability that a censor-cooperating user can convince an honest
|
// Probability that a censor-cooperating user can convince an honest
|
||||||
// user to give them an invite.
|
// user to give them an invite. This number is the baseline, for
|
||||||
|
// soliciting an invitation from a level 1 user. We assume it is
|
||||||
|
// harder for the censor to get invitations from higher-level users.
|
||||||
pub prob_censor_gets_invite: f64,
|
pub prob_censor_gets_invite: f64,
|
||||||
// Probability that a connection randomly fails, even though censor
|
// Probability that a connection randomly fails, even though censor
|
||||||
// does not block the bridge
|
// does not block the bridge
|
||||||
|
|
70
src/main.rs
70
src/main.rs
|
@ -137,6 +137,9 @@ pub async fn main() {
|
||||||
|
|
||||||
// Main loop
|
// Main loop
|
||||||
for day in 1..=config.num_days {
|
for day in 1..=config.num_days {
|
||||||
|
// Save some function calls by storing this
|
||||||
|
let date = get_date();
|
||||||
|
|
||||||
println!("Starting day {} of the simulation", day);
|
println!("Starting day {} of the simulation", day);
|
||||||
println!(
|
println!(
|
||||||
" We have {} users and {} bridges",
|
" We have {} users and {} bridges",
|
||||||
|
@ -167,33 +170,9 @@ pub async fn main() {
|
||||||
// USER TASKS
|
// USER TASKS
|
||||||
|
|
||||||
// Number of users who want to join today
|
// Number of users who want to join today
|
||||||
let mut num_users_requesting_invites: u32 =
|
let num_users_requesting_invites: u32 =
|
||||||
rng.gen_range(config.min_new_users_per_day..=config.max_new_users_per_day);
|
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();
|
let mut new_users = Vec::<User>::new();
|
||||||
|
|
||||||
// Shuffle users so they act in a random order
|
// Shuffle users so they act in a random order
|
||||||
|
@ -205,7 +184,6 @@ pub async fn main() {
|
||||||
.daily_tasks(
|
.daily_tasks(
|
||||||
&sconfig,
|
&sconfig,
|
||||||
num_users_requesting_invites,
|
num_users_requesting_invites,
|
||||||
num_censor_invitations,
|
|
||||||
&mut bridges,
|
&mut bridges,
|
||||||
&mut censor,
|
&mut censor,
|
||||||
)
|
)
|
||||||
|
@ -214,21 +192,6 @@ pub async fn main() {
|
||||||
if invited_friends.is_ok() {
|
if invited_friends.is_ok() {
|
||||||
let mut invited_friends = invited_friends.unwrap();
|
let mut invited_friends = invited_friends.unwrap();
|
||||||
if invited_friends.len() > 0 {
|
if invited_friends.len() > 0 {
|
||||||
if !user.is_censor {
|
|
||||||
// Censors always invite as many censor friends
|
|
||||||
// as possible. Honest users may invite honest
|
|
||||||
// friends, or they may accidentally invite
|
|
||||||
// censor friends.
|
|
||||||
for inv_friend in &invited_friends {
|
|
||||||
if inv_friend.is_censor {
|
|
||||||
num_censor_invitations -= 1;
|
|
||||||
} else {
|
|
||||||
num_users_requesting_invites -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If this user invited any friends, add them to the
|
|
||||||
// list of users
|
|
||||||
new_users.append(&mut invited_friends);
|
new_users.append(&mut invited_friends);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -240,7 +203,7 @@ pub async fn main() {
|
||||||
// If any users couldn't get invites, they join with open-entry
|
// If any users couldn't get invites, they join with open-entry
|
||||||
// invitations
|
// invitations
|
||||||
for _ in 0..num_users_requesting_invites {
|
for _ in 0..num_users_requesting_invites {
|
||||||
let user = User::new(&sconfig, false).await;
|
let user = User::new(&sconfig, false, &mut bridges, &mut censor).await;
|
||||||
if user.is_ok() {
|
if user.is_ok() {
|
||||||
users.push(user.unwrap());
|
users.push(user.unwrap());
|
||||||
} else {
|
} else {
|
||||||
|
@ -248,19 +211,14 @@ pub async fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If any censor users couldn't get invites, they also join with
|
|
||||||
// open-entry invitations
|
|
||||||
for _ in 0..(num_new_censor_users + num_censor_invitations) {
|
|
||||||
let user = User::new(&sconfig, true).await;
|
|
||||||
if user.is_ok() {
|
|
||||||
users.push(user.unwrap());
|
|
||||||
} else {
|
|
||||||
eprintln!("Failed to create new censor user.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CENSOR TASKS
|
// CENSOR TASKS
|
||||||
censor.end_of_day_tasks(&sconfig, &mut bridges).await;
|
if censor.is_active() {
|
||||||
|
// Censor gets as many open-entry invites as possible
|
||||||
|
while let Ok(new_user) = User::new(&sconfig, true, &mut bridges, &mut censor).await {
|
||||||
|
users.push(new_user);
|
||||||
|
}
|
||||||
|
censor.end_of_day_tasks(&sconfig, &mut bridges).await;
|
||||||
|
}
|
||||||
|
|
||||||
// BRIDGE TASKS
|
// BRIDGE TASKS
|
||||||
let mut new_extra_infos = HashSet::<ExtraInfo>::new();
|
let mut new_extra_infos = HashSet::<ExtraInfo>::new();
|
||||||
|
@ -319,13 +277,13 @@ pub async fn main() {
|
||||||
// If this is the first day Troll Patrol has determined this
|
// If this is the first day Troll Patrol has determined this
|
||||||
// bridge is blocked, note that for stats
|
// bridge is blocked, note that for stats
|
||||||
if detected_blocked && bridge.first_detected_blocked == 0 {
|
if detected_blocked && bridge.first_detected_blocked == 0 {
|
||||||
bridge.first_detected_blocked = get_date();
|
bridge.first_detected_blocked = date;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if censor actually blocks this bridge
|
// Check if censor actually blocks this bridge
|
||||||
let really_blocked = censor.blocks_bridge(&sconfig, fingerprint);
|
let really_blocked = censor.blocks_bridge(&sconfig, fingerprint);
|
||||||
if really_blocked && bridge.first_blocked == 0 {
|
if really_blocked && bridge.first_blocked == 0 {
|
||||||
bridge.first_blocked = get_date();
|
bridge.first_blocked = date;
|
||||||
}
|
}
|
||||||
if detected_blocked && really_blocked {
|
if detected_blocked && really_blocked {
|
||||||
true_pos += 1;
|
true_pos += 1;
|
||||||
|
|
140
src/user.rs
140
src/user.rs
|
@ -43,14 +43,13 @@ pub struct User {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl User {
|
impl User {
|
||||||
pub async fn new(config: &Config, is_censor: bool) -> Result<Self> {
|
pub async fn new(
|
||||||
let cred = get_lox_credential(
|
config: &Config,
|
||||||
&config.la_net,
|
is_censor: bool,
|
||||||
&get_open_invitation(&config.la_net).await?,
|
bridges: &mut HashMap<[u8; 20], Bridge>,
|
||||||
get_lox_pub(&config.la_pubkeys),
|
censor: &mut Censor,
|
||||||
)
|
) -> Result<Self> {
|
||||||
.await?
|
let cred = Self::get_new_credential(&config).await?.0;
|
||||||
.0;
|
|
||||||
|
|
||||||
// Probabilistically decide whether this user submits reports
|
// Probabilistically decide whether this user submits reports
|
||||||
let submits_reports = if is_censor {
|
let submits_reports = if is_censor {
|
||||||
|
@ -64,6 +63,21 @@ impl User {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let prob_use_bridges = rng.gen_range(0.0..=1.0);
|
let prob_use_bridges = rng.gen_range(0.0..=1.0);
|
||||||
|
|
||||||
|
// If the user cooperates with the censor, immediately tell the
|
||||||
|
// censor about all the bridges
|
||||||
|
if is_censor {
|
||||||
|
let (bucket, _reachcred) = get_bucket(&config.la_net, &cred).await?;
|
||||||
|
for bridgeline in bucket {
|
||||||
|
if bridgeline != BridgeLine::default() {
|
||||||
|
if !bridges.contains_key(&bridgeline.get_hashed_fingerprint()) {
|
||||||
|
let bridge = Bridge::from_bridge_line(&bridgeline);
|
||||||
|
bridges.insert(bridgeline.get_hashed_fingerprint(), bridge);
|
||||||
|
}
|
||||||
|
censor.learn_bridge(&bridgeline.get_hashed_fingerprint());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
is_censor,
|
is_censor,
|
||||||
primary_cred: cred,
|
primary_cred: cred,
|
||||||
|
@ -73,27 +87,11 @@ impl User {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn trusted_user(config: &Config) -> Result<Self> {
|
|
||||||
let cred = get_lox_credential(
|
|
||||||
&config.la_net,
|
|
||||||
&get_open_invitation(&config.la_net).await?,
|
|
||||||
get_lox_pub(&config.la_pubkeys),
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
.0;
|
|
||||||
Ok(Self {
|
|
||||||
is_censor: false,
|
|
||||||
primary_cred: cred,
|
|
||||||
secondary_cred: None,
|
|
||||||
submits_reports: true,
|
|
||||||
prob_use_bridges: 1.0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: This should probably return an actual error type
|
// TODO: This should probably return an actual error type
|
||||||
pub async fn invite(
|
pub async fn invite(
|
||||||
&mut self,
|
&mut self,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
|
bridges: &mut HashMap<[u8; 20], Bridge>,
|
||||||
censor: &mut Censor,
|
censor: &mut Censor,
|
||||||
invited_user_is_censor: bool,
|
invited_user_is_censor: bool,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
|
@ -130,17 +128,31 @@ impl User {
|
||||||
// Calling function decides if the invited user is a censor
|
// Calling function decides if the invited user is a censor
|
||||||
let is_censor = invited_user_is_censor;
|
let is_censor = invited_user_is_censor;
|
||||||
|
|
||||||
// Probabilistically decide whether this user submits reports
|
// Decide how likely this user is to use bridges on a given day
|
||||||
let submits_reports = if is_censor {
|
// and whether they submit reports
|
||||||
false
|
let (prob_use_bridges, submits_reports) = if is_censor {
|
||||||
|
(0.0, false)
|
||||||
} else {
|
} else {
|
||||||
event_happens(config.prob_user_submits_reports)
|
let mut rng = rand::thread_rng();
|
||||||
|
let prob_use_bridges = rng.gen_range(0.0..=1.0);
|
||||||
|
let submits_reports = event_happens(config.prob_user_submits_reports);
|
||||||
|
(prob_use_bridges, submits_reports)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Randomly determine how likely this user is to use bridges on
|
// If the user cooperates with the censor, immediately tell the
|
||||||
// a given day
|
// censor about all the bridges
|
||||||
let mut rng = rand::thread_rng();
|
if is_censor {
|
||||||
let prob_use_bridges = rng.gen_range(0.0..=1.0);
|
let (bucket, _reachcred) = get_bucket(&config.la_net, &self.primary_cred).await?;
|
||||||
|
for bridgeline in bucket {
|
||||||
|
if bridgeline != BridgeLine::default() {
|
||||||
|
if !bridges.contains_key(&bridgeline.get_hashed_fingerprint()) {
|
||||||
|
let bridge = Bridge::from_bridge_line(&bridgeline);
|
||||||
|
bridges.insert(bridgeline.get_hashed_fingerprint(), bridge);
|
||||||
|
}
|
||||||
|
censor.learn_bridge(&bridgeline.get_hashed_fingerprint());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
is_censor,
|
is_censor,
|
||||||
|
@ -153,8 +165,7 @@ impl User {
|
||||||
|
|
||||||
// Attempt to "connect" to the bridge, returns true if successful.
|
// Attempt to "connect" to the bridge, returns true if successful.
|
||||||
// Note that this does not involve making a real connection to a
|
// Note that this does not involve making a real connection to a
|
||||||
// real bridge. The function is async because the *censor* might
|
// real bridge.
|
||||||
// submit a positive report during this function.
|
|
||||||
pub fn connect(&self, config: &Config, bridge: &mut Bridge, censor: &Censor) -> bool {
|
pub fn connect(&self, config: &Config, bridge: &mut Bridge, censor: &Censor) -> bool {
|
||||||
if censor.blocks_bridge(config, &bridge.fingerprint) {
|
if censor.blocks_bridge(config, &bridge.fingerprint) {
|
||||||
if config.censor_totality == Full
|
if config.censor_totality == Full
|
||||||
|
@ -254,21 +265,14 @@ impl User {
|
||||||
&mut self,
|
&mut self,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
num_users_requesting_invites: u32,
|
num_users_requesting_invites: u32,
|
||||||
num_censor_invites: u32,
|
|
||||||
bridges: &mut HashMap<[u8; 20], Bridge>,
|
bridges: &mut HashMap<[u8; 20], Bridge>,
|
||||||
censor: &mut Censor,
|
censor: &mut Censor,
|
||||||
) -> Result<Vec<User>> {
|
) -> Result<Vec<User>> {
|
||||||
if self.is_censor {
|
if self.is_censor {
|
||||||
self.daily_tasks_censor(config, bridges, censor).await
|
self.daily_tasks_censor(config, bridges, censor).await
|
||||||
} else {
|
} else {
|
||||||
self.daily_tasks_non_censor(
|
self.daily_tasks_non_censor(config, num_users_requesting_invites, bridges, censor)
|
||||||
config,
|
.await
|
||||||
num_users_requesting_invites,
|
|
||||||
num_censor_invites,
|
|
||||||
bridges,
|
|
||||||
censor,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,7 +284,6 @@ impl User {
|
||||||
&mut self,
|
&mut self,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
num_users_requesting_invites: u32,
|
num_users_requesting_invites: u32,
|
||||||
num_censor_invites: u32,
|
|
||||||
bridges: &mut HashMap<[u8; 20], Bridge>,
|
bridges: &mut HashMap<[u8; 20], Bridge>,
|
||||||
censor: &mut Censor,
|
censor: &mut Censor,
|
||||||
) -> Result<Vec<User>> {
|
) -> Result<Vec<User>> {
|
||||||
|
@ -518,10 +521,31 @@ impl User {
|
||||||
None => 0, // This is probably an error case that should not happen
|
None => 0, // This is probably an error case that should not happen
|
||||||
};
|
};
|
||||||
let mut new_friends = Vec::<User>::new();
|
let mut new_friends = Vec::<User>::new();
|
||||||
|
|
||||||
|
// Scale the probability of inviting a censor, based on the
|
||||||
|
// user's own trust level. We assume that users with
|
||||||
|
// more-trusted credentials are less likely to invite
|
||||||
|
// censors because they have more to lose.
|
||||||
|
let level_scale = match scalar_u32(&self.primary_cred.trust_level) {
|
||||||
|
// These numbers are fairly arbitrary.
|
||||||
|
Some(4) => 0.01,
|
||||||
|
Some(3) => 0.1,
|
||||||
|
Some(2) => 0.5,
|
||||||
|
_ => 1.0,
|
||||||
|
};
|
||||||
for _i in 0..min(invitations, num_users_requesting_invites) {
|
for _i in 0..min(invitations, num_users_requesting_invites) {
|
||||||
if event_happens(config.prob_user_invites_friend) {
|
if event_happens(config.prob_user_invites_friend) {
|
||||||
// Invite non-censor friend
|
// With some probability, the user is convinced to
|
||||||
match self.invite(&config, censor, false).await {
|
// 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) => {
|
Ok(friend) => {
|
||||||
// You really shouldn't push your friends,
|
// You really shouldn't push your friends,
|
||||||
// especially new ones whose boundaries you
|
// especially new ones whose boundaries you
|
||||||
|
@ -535,22 +559,6 @@ impl User {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invite censor users if applicable
|
|
||||||
let invitations = invitations - new_friends.len() as u32;
|
|
||||||
for _i in 0..min(invitations, num_censor_invites) {
|
|
||||||
if event_happens(config.prob_user_invites_friend) {
|
|
||||||
// Invite non-censor friend
|
|
||||||
match self.invite(&config, censor, true).await {
|
|
||||||
Ok(friend) => {
|
|
||||||
new_friends.push(friend);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
println!("{}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(new_friends)
|
Ok(new_friends)
|
||||||
} else {
|
} else {
|
||||||
Ok(Vec::<User>::new())
|
Ok(Vec::<User>::new())
|
||||||
|
@ -641,8 +649,6 @@ impl User {
|
||||||
censor.learn_bridge(&fingerprint);
|
censor.learn_bridge(&fingerprint);
|
||||||
// Censor doesn't want new_cred yet
|
// Censor doesn't want new_cred yet
|
||||||
self.primary_cred = new_cred;
|
self.primary_cred = new_cred;
|
||||||
} else {
|
|
||||||
eprintln!("Censor failed to get new credential");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -660,15 +666,13 @@ impl User {
|
||||||
censor.learn_bridge(&fingerprint);
|
censor.learn_bridge(&fingerprint);
|
||||||
// Censor doesn't want new_cred. User doesn't actually use
|
// Censor doesn't want new_cred. User doesn't actually use
|
||||||
// secondary_cred, so don't store it.
|
// secondary_cred, so don't store it.
|
||||||
} else {
|
|
||||||
eprintln!("Censor failed to get new credential");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Censor user invites as many censor friends as possible
|
// Censor user invites as many censor friends as possible
|
||||||
let invitations = scalar_u32(&self.primary_cred.invites_remaining).unwrap();
|
let invitations = scalar_u32(&self.primary_cred.invites_remaining).unwrap();
|
||||||
let mut new_friends = Vec::<User>::new();
|
let mut new_friends = Vec::<User>::new();
|
||||||
for _ in 0..invitations {
|
for _ in 0..invitations {
|
||||||
match self.invite(&config, censor, true).await {
|
match self.invite(&config, bridges, censor, true).await {
|
||||||
Ok(friend) => {
|
Ok(friend) => {
|
||||||
new_friends.push(friend);
|
new_friends.push(friend);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue