sim.rs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  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. pub struct Simulation {
  8. pub done_init: bool,
  9. pub now: TimeCount,
  10. pub quorums: Vec<Quorum>,
  11. pub regions: Vec<Region>,
  12. pub rand: Pcg64,
  13. pub lg_regions_per_quorum: RegionCount,
  14. pub num_region_mask: RegionCount,
  15. pub cur_stats: CurrentStats,
  16. pub cum_stats: CumulativeStats,
  17. pub k: NodeCount,
  18. pub g: NodeCount,
  19. }
  20. impl Simulation {
  21. fn rand_region(&mut self) -> RegionCount {
  22. (self.rand.next_u64() as RegionCount) & self.num_region_mask
  23. }
  24. // Reservoir-sampling to pick which nodes to kick out for CCR
  25. fn pick_honest_malicious_nodes_to_kick_out(&mut self, m: NodeCount, n: NodeCount, n_bad: NodeCount) -> NodeCount {
  26. let mut selected_indices: Vec<usize> = vec![0usize; m as usize];
  27. // Picks m indices out of 0..n using the optimized L reservoir sampling algo.
  28. l(0usize..n, selected_indices.as_mut_slice(), &mut self.rand);
  29. // First 0..n_bad - 1 indices out of total are treated as malicious
  30. let num_bad = selected_indices.iter().fold(0, |bad_count, &index| if index < n_bad { bad_count + 1 } else { bad_count });
  31. num_bad
  32. }
  33. // Kick-out nodes as per CCR.
  34. fn kick_out_nodes(&mut self, region: RegionCount) {
  35. let quorum = region >> self.lg_regions_per_quorum;
  36. let current_region_size = self.regions[region].num_malicious + self.regions[region].num_honest;
  37. let number_to_kick_out : usize = ((self.k * current_region_size / self.g ) as f64).round() as usize;
  38. let num_malicious_to_kick_out = self.pick_honest_malicious_nodes_to_kick_out(number_to_kick_out, current_region_size, self.regions[region].num_malicious);
  39. let num_honest_to_kick_out = number_to_kick_out - num_malicious_to_kick_out;
  40. self.regions[region].num_malicious -= num_malicious_to_kick_out;
  41. self.regions[region].num_honest -= num_honest_to_kick_out;
  42. self.quorums[quorum].tot_malicious -= num_malicious_to_kick_out;
  43. self.quorums[quorum].tot_honest -= num_honest_to_kick_out;
  44. // Re-insert each node that was kicked out earlier, into new quorums, while maintaining
  45. // honest/malicious status.
  46. for _ in 0..num_honest_to_kick_out {
  47. let secondary_join_region = self.rand_region();
  48. self.insert(false, secondary_join_region, false);
  49. }
  50. for _ in 0..num_malicious_to_kick_out {
  51. let secondary_join_region = self.rand_region();
  52. self.insert(true, secondary_join_region, false);
  53. }
  54. }
  55. // Insert a new node into a given region in the DHT.
  56. fn insert(&mut self, is_malicious: bool, region: RegionCount, is_primary_join: bool) {
  57. let quorum = region >> self.lg_regions_per_quorum;
  58. // Insert the new node into that region.
  59. // Also update honest/malicious counts for region + quorum.
  60. if is_malicious {
  61. self.regions[region].num_malicious += 1;
  62. self.quorums[quorum].tot_malicious += 1;
  63. } else {
  64. self.regions[region].num_honest += 1;
  65. self.quorums[quorum].tot_honest += 1;
  66. }
  67. if is_primary_join {
  68. self.quorums[quorum].num_nodes_since_last_primary_join = 0
  69. } else {
  70. self.quorums[quorum].num_nodes_since_last_primary_join += 1
  71. }
  72. if self.done_init {
  73. self.cur_stats.update(quorum, &self.quorums[quorum], false);
  74. }
  75. }
  76. pub fn init(&mut self, num_honest: NodeCount, num_malicious: NodeCount) {
  77. for _ in 0..num_honest {
  78. // The original honest nodes are simply "mapped" to random locations -
  79. let target_region = self.rand_region();
  80. self.insert(false, target_region, false);
  81. }
  82. for _ in 0..num_malicious {
  83. self.cuckoo_insert();
  84. }
  85. self.done_init = true;
  86. self.cur_stats.dirty = true;
  87. self.collect_stats();
  88. self.update_cumstats(true);
  89. }
  90. // Insert a new node into a random region in the DHT, then
  91. // evict a fraction of nodes in that region into random
  92. // regions in the DHT (but don't evict nodes from the regions
  93. // _those_ nodes land in).
  94. pub fn cuckoo_insert(&mut self) {
  95. loop {
  96. // Pick a random region to put the new node into. Also get the quorum for that region.
  97. let region = self.rand_region();
  98. let quorum = region >> self.lg_regions_per_quorum;
  99. if self.quorums[quorum].num_nodes_since_last_primary_join >= self.k - 1 {
  100. self.kick_out_nodes(region);
  101. self.insert(true, region, true); // True for primary join
  102. // Update the age of the region that was emptied out.
  103. // self.quorums[quorum].tot_last_join += self.now - last_join;
  104. self.now += 1;
  105. // Cuckoo-ing after initialization: update stats for the quorum with the cuckood-out region
  106. if self.done_init {
  107. self.cur_stats.update(quorum, &self.quorums[quorum], false);
  108. self.collect_stats();
  109. self.update_cumstats(false);
  110. }
  111. break;
  112. }
  113. }
  114. }
  115. // Remove an existing malicious node from the quorum that has the lowest fraction of faulty nodes
  116. pub fn move_malicious(&mut self) {
  117. let find_min_b_0_quorum = || {
  118. let compute_b_0 = |q: Quorum| {
  119. let b_0: f64 = if q.tot_honest > 0 {
  120. (q.tot_malicious as f64) /
  121. (q.tot_honest as f64)
  122. } else if q.tot_malicious > 0 {
  123. 1000000.0
  124. } else {
  125. 0.0
  126. };
  127. b_0
  128. };
  129. let b_0_array: Vec<f64> = self.quorums.iter().map(|&q| compute_b_0(q)).collect();
  130. let (min_b_0_quorum, _) = b_0_array.iter().enumerate().fold((0, 0.0), |(min_index, min_val), (index, &val)| if val < min_val {
  131. (index, val)
  132. } else {
  133. (min_index, min_val)
  134. });
  135. min_b_0_quorum
  136. };
  137. // Pick quorum with least fraction of byz nodes
  138. let min_b_0_quorum = find_min_b_0_quorum();
  139. if self.quorums[min_b_0_quorum].tot_malicious > 0 {
  140. self.quorums[min_b_0_quorum].tot_malicious -= 1;
  141. self.cur_stats.update(min_b_0_quorum, &self.quorums[min_b_0_quorum], false);
  142. // Insert it back into the DHT
  143. self.cuckoo_insert();
  144. }
  145. }
  146. pub fn collect_stats(&mut self) {
  147. if self.cur_stats.dirty {
  148. for (i, q) in self.quorums.iter().enumerate() {
  149. self.cur_stats.update(i, q, i==0);
  150. }
  151. self.cur_stats.dirty = false;
  152. }
  153. }
  154. pub fn update_cumstats(&mut self, force: bool) {
  155. let stat = &self.cur_stats;
  156. if force || stat.min_tot_nodes < self.cum_stats.min_tot_nodes {
  157. self.cum_stats.min_tot_nodes = stat.min_tot_nodes;
  158. }
  159. if force || stat.max_tot_nodes > self.cum_stats.max_tot_nodes {
  160. self.cum_stats.max_tot_nodes = stat.max_tot_nodes;
  161. }
  162. if force || stat.min_tot_honest < self.cum_stats.min_tot_honest {
  163. self.cum_stats.min_tot_honest = stat.min_tot_honest;
  164. }
  165. if force || stat.max_tot_honest > self.cum_stats.max_tot_honest {
  166. self.cum_stats.max_tot_honest = stat.max_tot_honest;
  167. }
  168. if force || stat.min_tot_malicious < self.cum_stats.min_tot_malicious {
  169. self.cum_stats.min_tot_malicious = stat.min_tot_malicious;
  170. }
  171. if force || stat.max_tot_malicious > self.cum_stats.max_tot_malicious {
  172. self.cum_stats.max_tot_malicious = stat.max_tot_malicious;
  173. }
  174. /* let min_age = (self.now<<self.lg_regions_per_quorum) - stat.max_tot_last_join;
  175. let max_age = (self.now<<self.lg_regions_per_quorum) - stat.min_tot_last_join;
  176. if force || min_age < self.cum_stats.min_age {
  177. self.cum_stats.min_age = min_age;
  178. }
  179. if force || max_age > self.cum_stats.max_age {
  180. self.cum_stats.max_age = max_age;
  181. }
  182. */ if force || stat.min_b_0 < self.cum_stats.min_b_0 {
  183. self.cum_stats.min_b_0 = stat.min_b_0;
  184. }
  185. if force || stat.max_b_0 > self.cum_stats.max_b_0 {
  186. self.cum_stats.max_b_0 = stat.max_b_0;
  187. }
  188. }
  189. }