2018-12-14 11:15:23 -08:00
//! Fork Selection Simulation
//!
2024-01-03 06:06:06 -08:00
//! Description of the algorithm can be found in [Managing Forks](https://docs.solanalabs.com/consensus/managing-forks).
2018-12-14 11:15:23 -08:00
//!
//! A test library function exists for configuring networks.
//! ```
//! /// * num_partitions - 1 to 100 partitions
//! /// * fail_rate - 0 to 1.0 rate of packet receive failure
//! /// * delay_count - number of forks to observe before voting
2022-03-02 07:10:29 -08:00
//! /// * parasite_rate - percentage of parasite nodes that vote opposite the greedy choice
2018-12-14 11:15:23 -08:00
//! fn test_with_partitions(num_partitions: usize, fail_rate: f64, delay_count: usize, parasite_rate: f64);
//! ```
//! Modify the test function
//! ```
//! #[test]
//! #[ignore]
//! fn test_all_partitions() {
//! test_with_partitions(100, 0.0, 5, 0.25, false)
//! }
//! ```
//! Run with cargo
//!
//! ```
//! cargo test all_partitions --release -- --nocapture --ignored
//! ```
//!
//! The output will look like this
//! ```
2019-01-21 10:31:57 -08:00
//! time: 336, tip converged: 76, trunk id: 434, trunk time: 334, trunk converged 98, trunk height 65
2018-12-14 11:15:23 -08:00
//! ```
2019-01-21 10:31:57 -08:00
//! * time - The current cluster time. Each packet is transmitted to the cluster at a different time value.
//! * tip converged - Percentage of nodes voting on the tip.
//! * trunk id - ID of the newest most common fork for the largest converged set of nodes.
2018-12-14 11:15:23 -08:00
//! * trunk time - Time when the trunk fork was created.
2019-01-21 10:31:57 -08:00
//! * trunk converged - Number of voters that have converged on this fork.
//! * trunk height - Ledger height of the trunk.
2018-12-14 11:15:23 -08:00
//!
//!
//! ### Simulating Greedy Choice
//!
//! Parasitic nodes reverse the weighted function and pick the fork that has the least amount of economic finality, but without fully committing to a dead fork.
//!
//! ```
//! // Each run starts with 100 partitions, and it takes about 260 forks for a dominant trunk to emerge
//! // fully parasitic, 5 vote delay, 17% efficient
//! test_with_partitions(100, 0.0, 5, 1.0)
2019-01-21 10:31:57 -08:00
//! time: 1000, tip converged: 100, trunk id: 1095, trunk time: 995, trunk converged 100, trunk height 125
2018-12-14 11:15:23 -08:00
//! // 50% parasitic, 5 vote delay, 30% efficient
//! test_with_partitions(100, 0.0, 5, 0.5)
2019-01-21 10:31:57 -08:00
//! time: 1000, tip converged: 51, trunk id: 1085, trunk time: 985, trunk converged 100, trunk
//! height 223
2018-12-14 11:15:23 -08:00
//! // 25% parasitic, 5 vote delay, 49% efficient
//! test_with_partitions(100, 0.0, 5, 0.25)
2019-01-21 10:31:57 -08:00
//! time: 1000, tip converged: 79, trunk id: 1096, trunk time: 996, trunk converged 100, trunk
//! height 367
2018-12-14 11:15:23 -08:00
//! // 0% parasitic, 5 vote delay, 62% efficient
//! test_with_partitions(100, 0.0, 5, 0.0)
2019-01-21 10:31:57 -08:00
//! time: 1000, tip converged: 100, trunk id: 1099, trunk time: 999, trunk converged 100, trunk height 463
2018-12-14 11:15:23 -08:00
//! // 0% parasitic, 0 vote delay, 100% efficient
//! test_with_partitions(100, 0.0, 0, 0.0)
2019-01-21 10:31:57 -08:00
//! time: 1000, tip converged: 100, trunk id: 1100, trunk time: 1000, trunk converged 100, trunk height 740
2018-12-14 11:15:23 -08:00
//! ```
//!
//! ### Impact of Receive Errors
//!
2019-01-21 10:31:57 -08:00
//! * with 10% of packet drops, the height of the trunk is about 77% of the max possible
2018-12-14 11:15:23 -08:00
//! ```
2019-01-21 10:31:57 -08:00
//! time: 4007, tip converged: 94, trunk id: 4005, trunk time: 4002, trunk converged 100, trunk height 3121
2018-12-14 11:15:23 -08:00
//! ```
2019-01-21 10:31:57 -08:00
//! * with 90% of packet drops, the height of the trunk is about 8.6% of the max possible
2018-12-14 11:15:23 -08:00
//! ```
2019-01-21 10:31:57 -08:00
//! time: 4007, tip converged: 10, trunk id: 3830, trunk time: 3827, trunk converged 100, trunk height 348
2018-12-14 11:15:23 -08:00
//! ```
2023-09-01 00:26:13 -07:00
#![ allow(clippy::arithmetic_side_effects) ]
2018-12-14 11:15:23 -08:00
extern crate rand ;
2021-12-03 09:00:31 -08:00
use {
rand ::{ thread_rng , Rng } ,
std ::collections ::{ HashMap , VecDeque } ,
} ;
2018-12-14 11:15:23 -08:00
#[ derive(Clone, Default, Debug, Hash, Eq, PartialEq) ]
pub struct Fork {
id : usize ,
base : usize ,
}
impl Fork {
fn is_trunk_of ( & self , other : & Fork , fork_tree : & HashMap < usize , Fork > ) -> bool {
2019-01-21 10:58:47 -08:00
let mut current = other ;
2018-12-14 11:15:23 -08:00
loop {
// found it
if current . id = = self . id {
return true ;
}
// base is 0, and this id is 0
if current . base = = 0 & & self . id = = 0 {
assert! ( fork_tree . get ( & 0 ) . is_none ( ) ) ;
return true ;
}
// base is 0
if fork_tree . get ( & current . base ) . is_none ( ) {
return false ;
}
2019-01-21 10:58:47 -08:00
current = fork_tree . get ( & current . base ) . unwrap ( ) ;
2018-12-14 11:15:23 -08:00
}
}
}
#[ derive(Clone, Default, Debug, Hash, Eq, PartialEq) ]
pub struct Vote {
fork : Fork ,
time : usize ,
lockout : usize ,
}
impl Vote {
pub fn new ( fork : Fork , time : usize ) -> Vote {
Self {
fork ,
time ,
lockout : 2 ,
}
}
pub fn lock_height ( & self ) -> usize {
self . time + self . lockout
}
pub fn is_trunk_of ( & self , other : & Vote , fork_tree : & HashMap < usize , Fork > ) -> bool {
self . fork . is_trunk_of ( & other . fork , fork_tree )
}
}
#[ derive(Debug) ]
2019-09-26 13:29:05 -07:00
pub struct Tower {
2018-12-14 11:15:23 -08:00
votes : VecDeque < Vote > ,
max_size : usize ,
fork_trunk : Fork ,
converge_depth : usize ,
delay_count : usize ,
delayed_votes : VecDeque < Vote > ,
parasite : bool ,
}
2019-09-26 13:29:05 -07:00
impl Tower {
2018-12-14 11:15:23 -08:00
pub fn new ( max_size : usize , converge_depth : usize , delay_count : usize ) -> Self {
Self {
votes : VecDeque ::new ( ) ,
max_size ,
fork_trunk : Fork ::default ( ) ,
converge_depth ,
delay_count ,
delayed_votes : VecDeque ::new ( ) ,
parasite : false ,
}
}
2019-01-21 10:31:57 -08:00
pub fn submit_vote (
2018-12-14 11:15:23 -08:00
& mut self ,
vote : Vote ,
fork_tree : & HashMap < usize , Fork > ,
converge_map : & HashMap < usize , usize > ,
scores : & HashMap < Vote , usize > ,
) {
let is_valid = self
. get_vote ( self . converge_depth )
. map ( | v | v . is_trunk_of ( & vote , fork_tree ) )
. unwrap_or ( true ) ;
if is_valid {
self . delayed_votes . push_front ( vote ) ;
}
loop {
if self . delayed_votes . len ( ) < = self . delay_count {
break ;
}
let votes = self . pop_best_votes ( fork_tree , scores ) ;
for vote in votes {
self . push_vote ( vote , fork_tree , converge_map ) ;
}
}
let trunk = self . votes . get ( self . converge_depth ) . cloned ( ) ;
2020-05-15 09:35:43 -07:00
if let Some ( t ) = trunk {
2018-12-14 11:15:23 -08:00
self . delayed_votes . retain ( | v | v . fork . id > t . fork . id ) ;
2020-05-15 09:35:43 -07:00
}
2018-12-14 11:15:23 -08:00
}
pub fn pop_best_votes (
& mut self ,
fork_tree : & HashMap < usize , Fork > ,
scores : & HashMap < Vote , usize > ,
) -> VecDeque < Vote > {
let mut best : Vec < ( usize , usize , usize ) > = self
. delayed_votes
. iter ( )
. enumerate ( )
2021-06-18 06:34:46 -07:00
. map ( | ( i , v ) | ( * scores . get ( v ) . unwrap_or ( & 0 ) , v . time , i ) )
2018-12-14 11:15:23 -08:00
. collect ( ) ;
// highest score, latest vote first
2020-12-13 17:26:34 -08:00
best . sort_unstable ( ) ;
2018-12-14 11:15:23 -08:00
if self . parasite {
best . reverse ( ) ;
}
// best vote is last
let mut votes : VecDeque < Vote > = best
. last ( )
. and_then ( | v | self . delayed_votes . remove ( v . 2 ) )
. into_iter ( )
. collect ( ) ;
// plus any ancestors
if votes . is_empty ( ) {
return votes ;
}
let mut restart = true ;
// should really be using heap here
while restart {
restart = false ;
for i in 0 .. self . delayed_votes . len ( ) {
let is_trunk = {
let v = & self . delayed_votes [ i ] ;
v . is_trunk_of ( votes . front ( ) . unwrap ( ) , fork_tree )
} ;
if is_trunk {
votes . push_front ( self . delayed_votes . remove ( i ) . unwrap ( ) ) ;
restart = true ;
break ;
}
}
}
votes
}
pub fn push_vote (
& mut self ,
vote : Vote ,
fork_tree : & HashMap < usize , Fork > ,
converge_map : & HashMap < usize , usize > ,
) -> bool {
self . rollback ( vote . time ) ;
if ! self . is_valid ( & vote , fork_tree ) {
return false ;
}
if ! self . is_converged ( converge_map ) {
return false ;
}
2019-01-21 10:31:57 -08:00
self . process_vote ( vote ) ;
2018-12-14 11:15:23 -08:00
if self . is_full ( ) {
self . pop_full ( ) ;
}
true
}
2019-01-21 10:31:57 -08:00
/// check if the vote at `height` has over 50% of the cluster committed
2018-12-14 11:15:23 -08:00
fn is_converged ( & self , converge_map : & HashMap < usize , usize > ) -> bool {
self . get_vote ( self . converge_depth )
. map ( | v | {
let v = * converge_map . get ( & v . fork . id ) . unwrap_or ( & 0 ) ;
2019-01-21 10:31:57 -08:00
// hard-coded to 100 nodes
2018-12-14 11:15:23 -08:00
assert! ( v < = 100 ) ;
v > 50
2018-12-14 12:36:50 -08:00
} )
. unwrap_or ( true )
2018-12-14 11:15:23 -08:00
}
2019-01-21 10:31:57 -08:00
2018-12-14 11:15:23 -08:00
pub fn score ( & self , vote : & Vote , fork_tree : & HashMap < usize , Fork > ) -> usize {
let st = self . rollback_count ( vote . time ) ;
if st < self . votes . len ( ) & & ! self . votes [ st ] . is_trunk_of ( vote , fork_tree ) {
return 0 ;
}
let mut rv = 0 ;
for i in st .. self . votes . len ( ) {
let lockout = self . votes [ i ] . lockout ;
rv + = lockout ;
if i = = 0 | | self . votes [ i - 1 ] . lockout * 2 = = lockout {
// double the lockout from this vote
rv + = lockout ;
}
}
rv
}
fn rollback_count ( & self , time : usize ) -> usize {
let mut last : usize = 0 ;
for ( i , v ) in self . votes . iter ( ) . enumerate ( ) {
if v . lock_height ( ) < time {
last = i + 1 ;
}
}
last
}
/// if a vote is expired, pop it and all the votes leading up to it
fn rollback ( & mut self , time : usize ) {
let last = self . rollback_count ( time ) ;
for _ in 0 .. last {
self . votes . pop_front ( ) ;
}
}
/// only add votes that are descendent from the last vote in the stack
fn is_valid ( & self , vote : & Vote , fork_tree : & HashMap < usize , Fork > ) -> bool {
self . last_fork ( ) . is_trunk_of ( & vote . fork , fork_tree )
}
2019-01-21 10:31:57 -08:00
fn process_vote ( & mut self , vote : Vote ) {
2018-12-14 11:15:23 -08:00
let vote_time = vote . time ;
assert! ( ! self . is_full ( ) ) ;
assert_eq! ( vote . lockout , 2 ) ;
2019-01-21 10:31:57 -08:00
// push the new vote to the front
2018-12-14 11:15:23 -08:00
self . votes . push_front ( vote ) ;
2019-01-21 10:31:57 -08:00
// double the lockouts if the threshold to double is met
2018-12-14 11:15:23 -08:00
for i in 1 .. self . votes . len ( ) {
assert! ( self . votes [ i ] . time < = vote_time ) ;
if self . votes [ i ] . lockout = = self . votes [ i - 1 ] . lockout {
self . votes [ i ] . lockout * = 2 ;
}
}
}
fn pop_full ( & mut self ) {
assert! ( self . is_full ( ) ) ;
self . fork_trunk = self . votes . pop_back ( ) . unwrap ( ) . fork ;
}
fn is_full ( & self ) -> bool {
assert! ( self . votes . len ( ) < = self . max_size ) ;
self . votes . len ( ) = = self . max_size
}
fn last_vote ( & self ) -> Option < & Vote > {
self . votes . front ( )
}
fn get_vote ( & self , ix : usize ) -> Option < & Vote > {
self . votes . get ( ix )
}
pub fn first_vote ( & self ) -> Option < & Vote > {
self . votes . back ( )
}
pub fn last_fork ( & self ) -> Fork {
self . last_vote ( )
. map ( | v | v . fork . clone ( ) )
2019-01-21 10:58:47 -08:00
. unwrap_or_else ( | | self . fork_trunk . clone ( ) )
2018-12-14 11:15:23 -08:00
}
}
#[ test ]
fn test_is_trunk_of_1 ( ) {
let tree = HashMap ::new ( ) ;
let b1 = Fork { id : 1 , base : 0 } ;
let b2 = Fork { id : 2 , base : 0 } ;
assert! ( ! b1 . is_trunk_of ( & b2 , & tree ) ) ;
}
#[ test ]
fn test_is_trunk_of_2 ( ) {
let tree = HashMap ::new ( ) ;
let b1 = Fork { id : 1 , base : 0 } ;
let b2 = Fork { id : 0 , base : 0 } ;
assert! ( ! b1 . is_trunk_of ( & b2 , & tree ) ) ;
}
#[ test ]
fn test_is_trunk_of_3 ( ) {
let tree = HashMap ::new ( ) ;
let b1 = Fork { id : 1 , base : 0 } ;
let b2 = Fork { id : 1 , base : 0 } ;
assert! ( b1 . is_trunk_of ( & b2 , & tree ) ) ;
}
#[ test ]
fn test_is_trunk_of_4 ( ) {
let mut tree = HashMap ::new ( ) ;
let b1 = Fork { id : 1 , base : 0 } ;
let b2 = Fork { id : 2 , base : 1 } ;
tree . insert ( b1 . id , b1 . clone ( ) ) ;
assert! ( b1 . is_trunk_of ( & b2 , & tree ) ) ;
assert! ( ! b2 . is_trunk_of ( & b1 , & tree ) ) ;
}
#[ test ]
2020-05-15 09:35:43 -07:00
#[ allow(clippy::cognitive_complexity) ]
2018-12-14 11:15:23 -08:00
fn test_push_vote ( ) {
let tree = HashMap ::new ( ) ;
let bmap = HashMap ::new ( ) ;
let b0 = Fork { id : 0 , base : 0 } ;
2019-09-26 13:29:05 -07:00
let mut tower = Tower ::new ( 32 , 7 , 0 ) ;
2018-12-14 11:15:23 -08:00
let vote = Vote ::new ( b0 . clone ( ) , 0 ) ;
2019-01-21 10:31:57 -08:00
assert! ( tower . push_vote ( vote , & tree , & bmap ) ) ;
assert_eq! ( tower . votes . len ( ) , 1 ) ;
2018-12-14 11:15:23 -08:00
let vote = Vote ::new ( b0 . clone ( ) , 1 ) ;
2019-01-21 10:31:57 -08:00
assert! ( tower . push_vote ( vote , & tree , & bmap ) ) ;
assert_eq! ( tower . votes . len ( ) , 2 ) ;
2018-12-14 11:15:23 -08:00
let vote = Vote ::new ( b0 . clone ( ) , 2 ) ;
2019-01-21 10:31:57 -08:00
assert! ( tower . push_vote ( vote , & tree , & bmap ) ) ;
assert_eq! ( tower . votes . len ( ) , 3 ) ;
2018-12-14 11:15:23 -08:00
let vote = Vote ::new ( b0 . clone ( ) , 3 ) ;
2019-01-21 10:31:57 -08:00
assert! ( tower . push_vote ( vote , & tree , & bmap ) ) ;
assert_eq! ( tower . votes . len ( ) , 4 ) ;
2018-12-14 11:15:23 -08:00
2019-01-21 10:31:57 -08:00
assert_eq! ( tower . votes [ 0 ] . lockout , 2 ) ;
assert_eq! ( tower . votes [ 1 ] . lockout , 4 ) ;
assert_eq! ( tower . votes [ 2 ] . lockout , 8 ) ;
assert_eq! ( tower . votes [ 3 ] . lockout , 16 ) ;
2018-12-14 11:15:23 -08:00
2019-01-21 10:31:57 -08:00
assert_eq! ( tower . votes [ 1 ] . lock_height ( ) , 6 ) ;
assert_eq! ( tower . votes [ 2 ] . lock_height ( ) , 9 ) ;
2018-12-14 11:15:23 -08:00
let vote = Vote ::new ( b0 . clone ( ) , 7 ) ;
2019-01-21 10:31:57 -08:00
assert! ( tower . push_vote ( vote , & tree , & bmap ) ) ;
2018-12-14 11:15:23 -08:00
2019-01-21 10:31:57 -08:00
assert_eq! ( tower . votes [ 0 ] . lockout , 2 ) ;
2018-12-14 11:15:23 -08:00
let b1 = Fork { id : 1 , base : 1 } ;
2020-05-15 09:35:43 -07:00
let vote = Vote ::new ( b1 , 8 ) ;
2019-01-21 10:31:57 -08:00
assert! ( ! tower . push_vote ( vote , & tree , & bmap ) ) ;
2018-12-14 11:15:23 -08:00
let vote = Vote ::new ( b0 . clone ( ) , 8 ) ;
2019-01-21 10:31:57 -08:00
assert! ( tower . push_vote ( vote , & tree , & bmap ) ) ;
2018-12-14 11:15:23 -08:00
2019-01-21 10:31:57 -08:00
assert_eq! ( tower . votes . len ( ) , 4 ) ;
assert_eq! ( tower . votes [ 0 ] . lockout , 2 ) ;
assert_eq! ( tower . votes [ 1 ] . lockout , 4 ) ;
assert_eq! ( tower . votes [ 2 ] . lockout , 8 ) ;
assert_eq! ( tower . votes [ 3 ] . lockout , 16 ) ;
2018-12-14 11:15:23 -08:00
2020-05-15 09:35:43 -07:00
let vote = Vote ::new ( b0 , 10 ) ;
2019-01-21 10:31:57 -08:00
assert! ( tower . push_vote ( vote , & tree , & bmap ) ) ;
assert_eq! ( tower . votes . len ( ) , 2 ) ;
assert_eq! ( tower . votes [ 0 ] . lockout , 2 ) ;
assert_eq! ( tower . votes [ 1 ] . lockout , 16 ) ;
2018-12-14 11:15:23 -08:00
}
2019-09-26 13:29:05 -07:00
fn create_towers ( sz : usize , height : usize , delay_count : usize ) -> Vec < Tower > {
2018-12-14 11:15:23 -08:00
( 0 .. sz )
2019-09-26 13:29:05 -07:00
. map ( | _ | Tower ::new ( 32 , height , delay_count ) )
2018-12-14 11:15:23 -08:00
. collect ( )
}
2019-01-21 10:31:57 -08:00
/// The "height" of this fork. How many forks until it connects to fork 0
2018-12-14 11:15:23 -08:00
fn calc_fork_depth ( fork_tree : & HashMap < usize , Fork > , id : usize ) -> usize {
2019-01-21 10:31:57 -08:00
let mut height = 0 ;
2018-12-14 11:15:23 -08:00
let mut start = fork_tree . get ( & id ) ;
loop {
if start . is_none ( ) {
break ;
}
2019-01-21 10:31:57 -08:00
height + = 1 ;
2018-12-14 11:15:23 -08:00
start = fork_tree . get ( & start . unwrap ( ) . base ) ;
}
2019-01-21 10:31:57 -08:00
height
2018-12-14 11:15:23 -08:00
}
2019-01-21 10:31:57 -08:00
/// map of `fork id` to `tower count`
/// This map contains the number of nodes that have the fork as an ancestor.
/// The fork with the highest count that is the newest is the cluster "trunk".
2020-05-15 09:35:43 -07:00
fn calc_fork_map ( towers : & [ Tower ] , fork_tree : & HashMap < usize , Fork > ) -> HashMap < usize , usize > {
2018-12-14 11:15:23 -08:00
let mut lca_map : HashMap < usize , usize > = HashMap ::new ( ) ;
2019-01-21 10:31:57 -08:00
for tower in towers {
let mut start = tower . last_fork ( ) ;
2018-12-14 11:15:23 -08:00
loop {
* lca_map . entry ( start . id ) . or_insert ( 0 ) + = 1 ;
2019-01-21 10:31:57 -08:00
if ! fork_tree . contains_key ( & start . base ) {
2018-12-14 11:15:23 -08:00
break ;
}
start = fork_tree . get ( & start . base ) . unwrap ( ) . clone ( ) ;
}
}
lca_map
}
/// find the fork with the highest count of nodes that have it as an ancestor
/// as well as with the highest possible fork id, which indicates it is the newest
fn calc_newest_trunk ( bmap : & HashMap < usize , usize > ) -> ( usize , usize ) {
let mut data : Vec < _ > = bmap . iter ( ) . collect ( ) ;
data . sort_by_key ( | x | ( x . 1 , x . 0 ) ) ;
data . last ( ) . map ( | v | ( * v . 0 , * v . 1 ) ) . unwrap ( )
}
/// how common is the latest fork of all the nodes
2020-05-15 09:35:43 -07:00
fn calc_tip_converged ( towers : & [ Tower ] , bmap : & HashMap < usize , usize > ) -> usize {
2019-01-21 10:31:57 -08:00
let sum : usize = towers
2018-12-14 11:15:23 -08:00
. iter ( )
. map ( | n | * bmap . get ( & n . last_fork ( ) . id ) . unwrap_or ( & 0 ) )
. sum ( ) ;
2019-01-21 10:31:57 -08:00
sum / towers . len ( )
2018-12-14 11:15:23 -08:00
}
#[ test ]
fn test_no_partitions ( ) {
let mut tree = HashMap ::new ( ) ;
let len = 100 ;
2019-01-21 10:31:57 -08:00
let mut towers = create_towers ( len , 32 , 0 ) ;
2018-12-14 11:15:23 -08:00
for rounds in 0 .. 1 {
2019-01-21 10:31:57 -08:00
for i in 0 .. towers . len ( ) {
2018-12-14 11:15:23 -08:00
let time = rounds * len + i ;
2019-01-21 10:31:57 -08:00
let base = towers [ i ] . last_fork ( ) . clone ( ) ;
2018-12-14 11:15:23 -08:00
let fork = Fork {
id : time + 1 ,
base : base . id ,
} ;
tree . insert ( fork . id , fork . clone ( ) ) ;
let vote = Vote ::new ( fork , time ) ;
2019-01-21 10:31:57 -08:00
let bmap = calc_fork_map ( & towers , & tree ) ;
for tower in towers . iter_mut ( ) {
assert! ( tower . push_vote ( vote . clone ( ) , & tree , & bmap ) ) ;
2018-12-14 11:15:23 -08:00
}
2022-10-19 08:25:11 -07:00
//println!("{} {}", time, calc_tip_converged(&towers, &bmap));
2018-12-14 11:15:23 -08:00
}
}
2019-01-21 10:31:57 -08:00
let bmap = calc_fork_map ( & towers , & tree ) ;
assert_eq! ( calc_tip_converged ( & towers , & bmap ) , len ) ;
2018-12-14 11:15:23 -08:00
}
/// * num_partitions - 1 to 100 partitions
/// * fail_rate - 0 to 1.0 rate of packet receive failure
/// * delay_count - number of forks to observe before voting
2022-03-02 07:10:29 -08:00
/// * parasite_rate - percentage of parasite nodes that vote opposite the greedy choice
2018-12-14 11:15:23 -08:00
fn test_with_partitions (
num_partitions : usize ,
fail_rate : f64 ,
delay_count : usize ,
parasite_rate : f64 ,
break_early : bool ,
) {
let mut fork_tree = HashMap ::new ( ) ;
let len = 100 ;
let warmup = 8 ;
2019-01-21 10:31:57 -08:00
let mut towers = create_towers ( len , warmup , delay_count ) ;
2018-12-14 11:15:23 -08:00
for time in 0 .. warmup {
2019-01-21 10:31:57 -08:00
let bmap = calc_fork_map ( & towers , & fork_tree ) ;
for tower in towers . iter_mut ( ) {
let mut fork = tower . last_fork ( ) . clone ( ) ;
2018-12-14 11:15:23 -08:00
if fork . id = = 0 {
2023-08-21 10:11:21 -07:00
fork . id = thread_rng ( ) . gen_range ( 1 .. 1 + num_partitions ) ;
2018-12-14 11:15:23 -08:00
fork_tree . insert ( fork . id , fork . clone ( ) ) ;
}
let vote = Vote ::new ( fork , time ) ;
2019-01-21 10:31:57 -08:00
assert! ( tower . is_valid ( & vote , & fork_tree ) ) ;
2019-01-21 10:58:47 -08:00
assert! ( tower . push_vote ( vote , & fork_tree , & bmap ) ) ;
2018-12-14 11:15:23 -08:00
}
}
2019-01-21 10:31:57 -08:00
for tower in towers . iter_mut ( ) {
assert_eq! ( tower . votes . len ( ) , warmup ) ;
assert_eq! ( tower . first_vote ( ) . unwrap ( ) . lockout , 1 < < warmup ) ;
assert! ( tower . first_vote ( ) . unwrap ( ) . lock_height ( ) > = 1 < < warmup ) ;
2023-08-21 10:11:21 -07:00
tower . parasite = parasite_rate > thread_rng ( ) . gen_range ( 0. 0 .. 1.0 ) ;
2018-12-14 11:15:23 -08:00
}
2019-01-21 10:31:57 -08:00
let converge_map = calc_fork_map ( & towers , & fork_tree ) ;
assert_ne! ( calc_tip_converged ( & towers , & converge_map ) , len ) ;
2018-12-14 11:15:23 -08:00
for rounds in 0 .. 10 {
for i in 0 .. len {
let time = warmup + rounds * len + i ;
2019-01-21 10:58:47 -08:00
let base = towers [ i ] . last_fork ( ) ;
2018-12-14 11:15:23 -08:00
let fork = Fork {
id : time + num_partitions ,
base : base . id ,
} ;
fork_tree . insert ( fork . id , fork . clone ( ) ) ;
2019-01-21 10:31:57 -08:00
let converge_map = calc_fork_map ( & towers , & fork_tree ) ;
2018-12-14 11:15:23 -08:00
let vote = Vote ::new ( fork , time ) ;
let mut scores : HashMap < Vote , usize > = HashMap ::new ( ) ;
2019-01-21 10:31:57 -08:00
towers . iter ( ) . for_each ( | n | {
2018-12-14 11:15:23 -08:00
n . delayed_votes . iter ( ) . for_each ( | v | {
2021-06-18 06:34:46 -07:00
* scores . entry ( v . clone ( ) ) . or_insert ( 0 ) + = n . score ( v , & fork_tree ) ;
2018-12-14 11:15:23 -08:00
} )
} ) ;
2019-01-21 10:31:57 -08:00
for tower in towers . iter_mut ( ) {
2023-08-21 10:11:21 -07:00
if thread_rng ( ) . gen_range ( 0 f64 .. 1.0 f64 ) < fail_rate {
2018-12-14 11:15:23 -08:00
continue ;
}
2019-01-21 10:31:57 -08:00
tower . submit_vote ( vote . clone ( ) , & fork_tree , & converge_map , & scores ) ;
2018-12-14 11:15:23 -08:00
}
2019-01-21 10:31:57 -08:00
let converge_map = calc_fork_map ( & towers , & fork_tree ) ;
2018-12-14 11:15:23 -08:00
let trunk = calc_newest_trunk ( & converge_map ) ;
let trunk_time = if trunk . 0 > num_partitions {
trunk . 0 - num_partitions
} else {
trunk . 0
} ;
println! (
2019-01-21 10:31:57 -08:00
" time: {}, tip converged: {}, trunk id: {}, trunk time: {}, trunk converged {}, trunk height {} " ,
2018-12-14 11:15:23 -08:00
time ,
2019-01-21 10:31:57 -08:00
calc_tip_converged ( & towers , & converge_map ) ,
2018-12-14 11:15:23 -08:00
trunk . 0 ,
trunk_time ,
trunk . 1 ,
calc_fork_depth ( & fork_tree , trunk . 0 )
) ;
2019-01-21 10:31:57 -08:00
if break_early & & calc_tip_converged ( & towers , & converge_map ) = = len {
2018-12-14 11:15:23 -08:00
break ;
}
}
if break_early {
2019-01-21 10:31:57 -08:00
let converge_map = calc_fork_map ( & towers , & fork_tree ) ;
if calc_tip_converged ( & towers , & converge_map ) = = len {
2018-12-14 11:15:23 -08:00
break ;
}
}
}
2019-01-21 10:31:57 -08:00
let converge_map = calc_fork_map ( & towers , & fork_tree ) ;
2018-12-14 11:15:23 -08:00
let trunk = calc_newest_trunk ( & converge_map ) ;
assert_eq! ( trunk . 1 , len ) ;
}
#[ test ]
2019-02-14 17:30:42 -08:00
#[ ignore ]
2018-12-14 11:15:23 -08:00
fn test_3_partitions ( ) {
test_with_partitions ( 3 , 0.0 , 0 , 0.0 , true )
}
#[ test ]
#[ ignore ]
fn test_3_partitions_large_packet_drop ( ) {
test_with_partitions ( 3 , 0.9 , 0 , 0.0 , false )
}
#[ test ]
#[ ignore ]
fn test_all_partitions ( ) {
test_with_partitions ( 100 , 0.0 , 5 , 0.25 , false )
}