sim.rs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. use crate::types::*;
  2. // use rand_xoshiro::{SplitMix64, rand_core::RngCore};
  3. use crate::stats::{CumulativeStats, CurrentStats};
  4. use reservoir_sampling::unweighted::core::l;
  5. use rand_pcg::Pcg64;
  6. use rand::RngCore;
  7. use rand::seq::SliceRandom;
  8. pub struct Simulation {
  9. pub done_init: bool,
  10. pub now: TimeCount,
  11. pub quorums: Vec<Quorum>,
  12. pub regions: Vec<Region>,
  13. pub rand: Pcg64,
  14. pub lg_regions_per_quorum: RegionCount,
  15. pub num_region_mask: RegionCount,
  16. pub cur_stats: CurrentStats,
  17. pub cum_stats: CumulativeStats,
  18. pub k: NodeCount,
  19. pub g: NodeCount,
  20. }
  21. impl Simulation {
  22. fn rand_region(&mut self) -> RegionCount {
  23. (self.rand.next_u64() as RegionCount) & self.num_region_mask
  24. }
  25. pub fn random_shuffle_regions(&mut self, n:usize) -> Vec<u32> {
  26. let mut vec: Vec<u32> = (0..n as u32).collect();
  27. vec.shuffle(&mut self.rand);
  28. vec
  29. }
  30. // Reservoir-sampling to pick which nodes to kick out for CCR
  31. fn pick_honest_malicious_nodes_to_kick_out(&mut self, m: NodeCount, n: NodeCount, n_bad: NodeCount) -> NodeCount {
  32. let mut selected_indices: Vec<usize> = vec![0usize; m as usize];
  33. // Picks m indices out of 0..n using the optimized L reservoir sampling algo.
  34. l(0usize..n, selected_indices.as_mut_slice(), &mut self.rand);
  35. // First 0..n_bad - 1 indices out of total are treated as malicious
  36. let num_bad = selected_indices.iter().fold(0, |bad_count, &index| if index < n_bad { bad_count + 1 } else { bad_count });
  37. num_bad
  38. }
  39. // Kick-out nodes as per CCR.
  40. fn kick_out_nodes(&mut self, region: RegionCount) -> (bool, usize) {
  41. // This func will always be called after at least 1 malicious node is inserted into the quorum.
  42. // TODO Check: We only want to chuck out k*g'/g nodes out of the *existing* nodes in the quorum.
  43. // So effectively, the no. of malicious nodes is decremented by 1 here.
  44. let num_malicious = self.regions[region].num_malicious - 1;
  45. let current_region_size = num_malicious + self.regions[region].num_honest;
  46. let number_to_kick_out : usize = (self.k as f64 * current_region_size as f64/ self.g as f64).round() as usize;
  47. // println!(
  48. // "KICKING OUT: ***Region {}*** had {} honest {} malicious nodes = total of {} nodes.",
  49. // region,
  50. // self.regions[region].num_honest,
  51. // num_malicious,
  52. // current_region_size
  53. // );
  54. let quorum = region >> self.lg_regions_per_quorum;
  55. if number_to_kick_out > 0
  56. {
  57. let num_malicious_to_kick_out = self.pick_honest_malicious_nodes_to_kick_out(number_to_kick_out, current_region_size, num_malicious);
  58. let num_honest_to_kick_out = number_to_kick_out - num_malicious_to_kick_out;
  59. // println!("KICKING OUT: We choose to kick out {} honest {} malicious nodes = total of {} nodes.",
  60. // num_honest_to_kick_out,
  61. // num_malicious_to_kick_out,
  62. // number_to_kick_out
  63. // );
  64. self.regions[region].num_malicious -= num_malicious_to_kick_out;
  65. self.regions[region].num_honest -= num_honest_to_kick_out;
  66. self.quorums[quorum].tot_malicious -= num_malicious_to_kick_out;
  67. self.quorums[quorum].tot_honest -= num_honest_to_kick_out;
  68. // Re-insert each node that was kicked out earlier, into new quorums, while maintaining
  69. // honest/malicious status.
  70. for _ctr in 0..num_honest_to_kick_out {
  71. let secondary_join_region = self.rand_region();
  72. // Don't need to check the return value as it'll continue to be true (only honest nodes were inserted)
  73. self.insert(false, secondary_join_region, false);
  74. // println!(
  75. // "KICKING OUT: honest node {} to region {}",
  76. // ctr, secondary_join_region
  77. // );
  78. }
  79. for ctr in 0..num_malicious_to_kick_out {
  80. let secondary_join_region = self.rand_region();
  81. let below_bmax = self.insert(true, secondary_join_region, false);
  82. if !below_bmax
  83. {
  84. return (below_bmax, ctr + 1);
  85. }
  86. // println!(
  87. // "KICKING OUT: malicious node {} to region {}",
  88. // ctr, secondary_join_region
  89. // );
  90. }
  91. return (true, num_malicious_to_kick_out)
  92. }
  93. (true, 0)
  94. }
  95. // Insert a new node into a given region in the DHT.
  96. fn insert(&mut self, is_malicious: bool, region: RegionCount, is_primary_join: bool) -> bool {
  97. let quorum = region >> self.lg_regions_per_quorum;
  98. // Insert the new node into that region.
  99. // Also update honest/malicious counts for region + quorum.
  100. if is_malicious {
  101. self.regions[region].num_malicious += 1;
  102. self.quorums[quorum].tot_malicious += 1;
  103. } else {
  104. self.regions[region].num_honest += 1;
  105. self.quorums[quorum].tot_honest += 1;
  106. }
  107. if is_primary_join {
  108. self.regions[region].num_nodes_since_last_primary_join = 0
  109. } else {
  110. self.regions[region].num_nodes_since_last_primary_join += 1
  111. }
  112. if self.done_init {
  113. return self.cur_stats.update(quorum, &self.quorums[quorum], false);
  114. // if broke_b_max {
  115. // print!("FAILED INVARIANT b_0 < 1/3 while re-inserting cuckoo-ed out nodes");
  116. // }
  117. }
  118. true
  119. }
  120. pub fn init(&mut self, num_honest: NodeCount, num_malicious: NodeCount) -> (usize, bool, bool, bool, usize) {
  121. for _ in 0..num_honest {
  122. // The original honest nodes are simply "mapped" to random locations -
  123. let target_region = self.rand_region();
  124. // Don't need to check the return value as it'll always be true (b_0 max won't be broken as only honest nodes were inserted)
  125. self.insert(false, target_region, false);
  126. }
  127. for ctr in 0..num_malicious {
  128. let (inserted, inserted_below_bmax, rejoined_below_bmax, number_rejoined) = self.cuckoo_insert();
  129. if !inserted || !inserted_below_bmax || !rejoined_below_bmax {
  130. return (ctr, inserted, inserted_below_bmax, rejoined_below_bmax, number_rejoined );
  131. }
  132. }
  133. self.done_init = true;
  134. self.cur_stats.dirty = true;
  135. self.collect_stats();
  136. self.update_cumstats(true);
  137. (num_malicious, true, true, true, 0)
  138. }
  139. // Insert a new node into a random region in the DHT, then
  140. // evict a fraction of nodes in that region into random
  141. // regions in the DHT (but don't evict nodes from the regions
  142. // _those_ nodes land in).
  143. pub fn cuckoo_insert(&mut self) -> (bool, bool, bool, usize) {
  144. let regions_ordering = self.random_shuffle_regions(self.regions.len());
  145. // println!("Random permutation of target regions: {:?}", regions_ordering);
  146. for ctr in 0..regions_ordering.len() {
  147. // Pick a random region to put the new node into. Also get the quorum for that region.
  148. let region = regions_ordering[ctr] as usize;
  149. // println!(
  150. // "TRYING CUCKOO INSERT: target region {} quorum {} no. of nodes since last 1ary join {} k {}",
  151. // region, quorum, self.regions[region].num_nodes_since_last_primary_join, self.k
  152. // );
  153. if self.regions[region].num_nodes_since_last_primary_join >= self.k - 1 {
  154. // println!("------ SUCCESSFUL CUCKOO INSERT: target region {}", region);
  155. let below_bmax_insert_own = self.insert(true, region, true); // True for primary join
  156. let (below_bmax_insert_kicked_out, no_of_malicious_kicked_out) = self.kick_out_nodes(region);
  157. // Update the age of the region that was emptied out.
  158. // self.quorums[quorum].tot_last_join += self.now - last_join;
  159. self.now += 1;
  160. // Cuckoo-ing after initialization: update stats for the quorum with the cuckood-out region
  161. if self.done_init {
  162. // as it's being updated in the collect_stats call essentially.
  163. // self.cur_stats.update(quorum, &self.quorums[quorum], false);
  164. self.collect_stats();
  165. self.update_cumstats(false);
  166. }
  167. return (true, below_bmax_insert_own, below_bmax_insert_kicked_out, no_of_malicious_kicked_out)
  168. }
  169. }
  170. (false, true, true, 0)
  171. }
  172. // Remove an existing malicious node from the quorum that has the lowest fraction of faulty nodes
  173. pub fn move_malicious(&mut self) -> (bool, bool, bool, usize) {
  174. let find_min_b_0_region = || {
  175. let compute_b_0 = |r: Region| {
  176. (r.num_malicious as f64) / (r.num_honest as f64 + r.num_malicious as f64)
  177. };
  178. let b_0_array: Vec<f64> = self.regions.iter().map(|&q| compute_b_0(q)).collect();
  179. // eprintln!("b_0 array for");
  180. // let mut ctr: i8 = 0;
  181. // for &b_0 in &b_0_array {
  182. // print!("{} {},", ctr, b_0);
  183. // ctr = ctr + 1;
  184. // }
  185. // println!("\n");
  186. let (min_b_0_region, _) = b_0_array.iter().enumerate().fold((0, 1.0), |(min_index, min_val), (index, &val)| if val < min_val && val > 0.0 {
  187. // eprintln!(
  188. // "Replacing {} index {} with current val {} index {}",
  189. // min_val, min_index, val, index
  190. // );
  191. (index, val)
  192. } else {
  193. (min_index, min_val)
  194. });
  195. min_b_0_region
  196. };
  197. // Pick quorum with least fraction of byz nodes
  198. let min_b_0_region = find_min_b_0_region();
  199. let min_b_0_quorum = min_b_0_region >> self.lg_regions_per_quorum;
  200. // eprintln!(
  201. // "MOVE MALICIOUS out of region {} which has {} honest {} malicious nodes",
  202. // min_b_0_region,
  203. // self.regions[min_b_0_region].num_honest,
  204. // self.regions[min_b_0_region].num_malicious
  205. // );
  206. self.regions[min_b_0_region].num_malicious -= 1;
  207. self.quorums[min_b_0_quorum].tot_malicious -= 1;
  208. // Don't need to check return value as b_0 for min_b_0_quorum decreases.
  209. self.cur_stats
  210. .update(min_b_0_quorum, &self.quorums[min_b_0_quorum], false);
  211. // Insert it back into the DHT
  212. return self.cuckoo_insert();
  213. }
  214. pub fn collect_stats(&mut self) {
  215. if self.cur_stats.dirty {
  216. for (i, q) in self.quorums.iter().enumerate() {
  217. self.cur_stats.update(i, q, i==0);
  218. }
  219. self.cur_stats.dirty = false;
  220. }
  221. }
  222. pub fn update_cumstats(&mut self, force: bool) {
  223. let stat = &self.cur_stats;
  224. if force || stat.min_tot_nodes < self.cum_stats.min_tot_nodes {
  225. self.cum_stats.min_tot_nodes = stat.min_tot_nodes;
  226. }
  227. if force || stat.max_tot_nodes > self.cum_stats.max_tot_nodes {
  228. self.cum_stats.max_tot_nodes = stat.max_tot_nodes;
  229. }
  230. if force || stat.min_tot_honest < self.cum_stats.min_tot_honest {
  231. self.cum_stats.min_tot_honest = stat.min_tot_honest;
  232. }
  233. if force || stat.max_tot_honest > self.cum_stats.max_tot_honest {
  234. self.cum_stats.max_tot_honest = stat.max_tot_honest;
  235. }
  236. if force || stat.min_tot_malicious < self.cum_stats.min_tot_malicious {
  237. self.cum_stats.min_tot_malicious = stat.min_tot_malicious;
  238. }
  239. if force || stat.max_tot_malicious > self.cum_stats.max_tot_malicious {
  240. self.cum_stats.max_tot_malicious = stat.max_tot_malicious;
  241. }
  242. /* let min_age = (self.now<<self.lg_regions_per_quorum) - stat.max_tot_last_join;
  243. let max_age = (self.now<<self.lg_regions_per_quorum) - stat.min_tot_last_join;
  244. if force || min_age < self.cum_stats.min_age {
  245. self.cum_stats.min_age = min_age;
  246. }
  247. if force || max_age > self.cum_stats.max_age {
  248. self.cum_stats.max_age = max_age;
  249. }
  250. */ if force || stat.min_b_0 < self.cum_stats.min_b_0 {
  251. self.cum_stats.min_b_0 = stat.min_b_0;
  252. }
  253. if force || stat.max_b_0 > self.cum_stats.max_b_0 {
  254. self.cum_stats.max_b_0 = stat.max_b_0;
  255. }
  256. }
  257. }