From 141c1d3dfd0336c3dc8b0550a57f1d3dab3be2c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20G=C3=B3rski?= Date: Wed, 22 Jul 2020 19:24:57 +0200 Subject: [PATCH 01/17] HWY-85: Move weight boundaries checks to `build()` method. --- .../consensus/highway_core/highway_testing.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/node/src/components/consensus/highway_core/highway_testing.rs b/node/src/components/consensus/highway_core/highway_testing.rs index b4e44aa31e..666db1ab0d 100644 --- a/node/src/components/consensus/highway_core/highway_testing.rs +++ b/node/src/components/consensus/highway_core/highway_testing.rs @@ -636,8 +636,6 @@ impl, DS: DeliveryStrategy> } pub(crate) fn weight_limits(mut self, lower: u64, upper: u64) -> Self { - // TODO: More checks? - assert!(lower <= upper); self.weight_limits = (lower, upper); self } @@ -696,6 +694,16 @@ impl, DS: DeliveryStrategy> let round_exp = self.round_exp; let start_time = self.start_time; + let (lower, upper) = if self.weight_limits == (0, 0) { + return Err(BuilderError::WeightLimits); + } else { + let (l, u) = self.weight_limits; + if (l >= u) { + return Err(BuilderError::WeightLimits); + } + (l, u) + }; + let weights: Vec = match self.weight_distribution { Distribution::Constant => { let (lower, upper) = self.weight_limits; From a1e3535f0b9067cdb3f3925c8060062749629005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20G=C3=B3rski?= Date: Wed, 22 Jul 2020 19:25:42 +0200 Subject: [PATCH 02/17] HWY-85: Pass the rng to `build` method. --- .../components/consensus/highway_core/highway_testing.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/node/src/components/consensus/highway_core/highway_testing.rs b/node/src/components/consensus/highway_core/highway_testing.rs index 666db1ab0d..dfcce22ee7 100644 --- a/node/src/components/consensus/highway_core/highway_testing.rs +++ b/node/src/components/consensus/highway_core/highway_testing.rs @@ -665,7 +665,7 @@ impl, DS: DeliveryStrategy> self } - fn build(mut self) -> Result, BuilderError> { + fn build(mut self, rng: &mut R) -> Result, BuilderError> { if self.validators_secs.is_empty() { return Err(BuilderError::NoValidators); } @@ -878,7 +878,7 @@ mod test_harness { .consensus_values(vec![1]) .weight_limits(1, 5) .ftt(1) - .build() + .build(&mut rand) .ok() .expect("Construction was successful"); @@ -905,7 +905,7 @@ mod test_harness { .validators(validators) .consensus_values((0..10).collect()) .weight_limits(3, 5) - .build() + .build(&mut rand) .ok() .expect("Construction was successful"); From 7dbc830493f792ebac1a3510e8497811baa4b215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20G=C3=B3rski?= Date: Wed, 22 Jul 2020 19:51:29 +0200 Subject: [PATCH 03/17] HWY-85: Validators' weights are random. --- .../components/consensus/highway_core/highway_testing.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/node/src/components/consensus/highway_core/highway_testing.rs b/node/src/components/consensus/highway_core/highway_testing.rs index dfcce22ee7..93f26c1072 100644 --- a/node/src/components/consensus/highway_core/highway_testing.rs +++ b/node/src/components/consensus/highway_core/highway_testing.rs @@ -706,11 +706,12 @@ impl, DS: DeliveryStrategy> let weights: Vec = match self.weight_distribution { Distribution::Constant => { - let (lower, upper) = self.weight_limits; - let weight = Weight((lower + upper) / 2); + let weight = Weight(rng.gen_range(lower, upper)); (0..validators_num).map(|_| weight).collect() } - Distribution::Uniform => unimplemented!(), + Distribution::Uniform => (0..validators_num) + .map(|_| Weight(rng.gen_range(lower, upper))) + .collect(), // https://casperlabs.atlassian.net/browse/HWY-116 Distribution::Poisson(_) => unimplemented!("Poisson distribution of weights"), }; From 1c75a5d04f44cc43d870f26ea912c9da4f7d265b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20G=C3=B3rski?= Date: Wed, 22 Jul 2020 19:52:41 +0200 Subject: [PATCH 04/17] HWY-85: Random FTT (if not given). --- .../components/consensus/highway_core/highway_testing.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/node/src/components/consensus/highway_core/highway_testing.rs b/node/src/components/consensus/highway_core/highway_testing.rs index 93f26c1072..315c1de89d 100644 --- a/node/src/components/consensus/highway_core/highway_testing.rs +++ b/node/src/components/consensus/highway_core/highway_testing.rs @@ -716,9 +716,12 @@ impl, DS: DeliveryStrategy> Distribution::Poisson(_) => unimplemented!("Poisson distribution of weights"), }; - let ftt = self - .ftt - .unwrap_or_else(|| (weights.iter().sum::().0 - 1) / 3); + let weights_sum = weights.iter().sum::().0; + + // Network is not safe if weight of malicious validators is higher than ⅓. + let safe_ftt_limit = (weights_sum - 1) / 3; + // Random FTT that still creates secure network – where there's less equivocators than 1/3 of the weights. + let ftt = self.ftt.unwrap_or_else(|| rng.gen_range(1, safe_ftt_limit)); let validator_ids = (0..validators_num) .map(|i| ValidatorId(i as u64)) From 81420d445a798255a00ff67950193ab630bcdf94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20G=C3=B3rski?= Date: Thu, 23 Jul 2020 14:56:18 +0200 Subject: [PATCH 05/17] fixup! 7dbc830493f792ebac1a3510e8497811baa4b215 --- .../consensus/highway_core/highway_testing.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/node/src/components/consensus/highway_core/highway_testing.rs b/node/src/components/consensus/highway_core/highway_testing.rs index 315c1de89d..ee2ccacd78 100644 --- a/node/src/components/consensus/highway_core/highway_testing.rs +++ b/node/src/components/consensus/highway_core/highway_testing.rs @@ -694,11 +694,12 @@ impl, DS: DeliveryStrategy> let round_exp = self.round_exp; let start_time = self.start_time; - let (lower, upper) = if self.weight_limits == (0, 0) { - return Err(BuilderError::WeightLimits); - } else { + let (lower, upper) = { let (l, u) = self.weight_limits; - if (l >= u) { + // `rng.gen_range(x, y)` panics if `x >= y` + // and since `safe_ftt` is calculated as: `(weight_sum - 1) / 3` + // it may panic on `x` < 7 b/c it will round down to 1. + if (l >= u) || l < 7 { return Err(BuilderError::WeightLimits); } (l, u) @@ -880,8 +881,7 @@ mod test_harness { HighwayTestHarnessBuilder::new(0u64) .validators(validators) .consensus_values(vec![1]) - .weight_limits(1, 5) - .ftt(1) + .weight_limits(7, 10) .build(&mut rand) .ok() .expect("Construction was successful"); @@ -908,7 +908,7 @@ mod test_harness { HighwayTestHarnessBuilder::new(0u64) .validators(validators) .consensus_values((0..10).collect()) - .weight_limits(3, 5) + .weight_limits(7, 10) .build(&mut rand) .ok() .expect("Construction was successful"); From d1d68a3ac782dd3b48f7a4cbd47fc7c96ff53932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20G=C3=B3rski?= Date: Fri, 24 Jul 2020 11:23:39 +0200 Subject: [PATCH 06/17] HWY-85: Specify faulty validators by collective weight, not number. --- .../consensus/highway_core/highway_testing.rs | 166 ++++++++++++------ 1 file changed, 116 insertions(+), 50 deletions(-) diff --git a/node/src/components/consensus/highway_core/highway_testing.rs b/node/src/components/consensus/highway_core/highway_testing.rs index ee2ccacd78..46cf976e48 100644 --- a/node/src/components/consensus/highway_core/highway_testing.rs +++ b/node/src/components/consensus/highway_core/highway_testing.rs @@ -169,10 +169,20 @@ impl Display for TestRunError { enum Distribution { Uniform, - Constant, Poisson(f64), } +impl Distribution { + /// Returns vector of `count` elements of random values between `lower` and `uppwer`. + fn gen_range_vec(&self, rng: &mut R, lower: u64, upper: u64, count: u8) -> Vec { + match self { + Distribution::Uniform => (0..count).map(|_| rng.gen_range(lower, upper)).collect(), + // https://casperlabs.atlassian.net/browse/HWY-116 + Distribution::Poisson(_) => unimplemented!("Poisson distribution of weights"), + } + } +} + trait DeliveryStrategy { fn gen_delay( &self, @@ -518,16 +528,16 @@ enum BuilderError { NoValidators, EmptyConsensusValues, WeightLimits, - TooManyFaultyNodes, + TooManyFaultyNodes(String), EmptyFtt, } struct HighwayTestHarnessBuilder> { /// Validators (together with their secret keys) in the test run. validators_secs: HashMap, - /// Number of faulty validators (i.e. equivocators). - /// Defaults to 0. - faulty_num: u8, + /// Percentage of faulty validators' (i.e. equivocators) weight. + /// Defaults to 0 (network is perfectly secure). + faulty_weight: u64, /// FTT value for the finality detector. /// If not given, defaults to 1/3 of total validators' weight. ftt: Option, @@ -578,14 +588,14 @@ impl> fn new(instance_id: C::InstanceId) -> Self { HighwayTestHarnessBuilder { validators_secs: HashMap::new(), - faulty_num: 0, + faulty_weight: 0, ftt: None, consensus_values: None, - delivery_distribution: Distribution::Constant, + delivery_distribution: Distribution::Uniform, delivery_strategy: InstantDeliveryNoDropping, weight_limits: (0, 0), start_time: Timestamp::zero(), - weight_distribution: Distribution::Constant, + weight_distribution: Distribution::Uniform, instance_id, seed: 0, round_exp: 12, @@ -604,8 +614,14 @@ impl, DS: DeliveryStrategy> self } - pub(crate) fn faulty_num(mut self, faulty_num: u8) -> Self { - self.faulty_num = faulty_num; + /// Sets a percentage of weight that will be assigned to malicious nodes. + /// `faulty_weight` must be a value between 0 (inclusive) and 100 (inclusive). + pub(crate) fn faulty_weight(mut self, faulty_weight: u64) -> Self { + assert!( + faulty_weight <= 33, + "Expected value between 0 (inclusive) and 33 (inclusive)" + ); + self.faulty_weight = faulty_weight; self } @@ -621,7 +637,7 @@ impl, DS: DeliveryStrategy> ) -> HighwayTestHarnessBuilder { HighwayTestHarnessBuilder { validators_secs: self.validators_secs, - faulty_num: self.faulty_num, + faulty_weight: self.faulty_weight, ftt: self.ftt, consensus_values: self.consensus_values, delivery_distribution: self.delivery_distribution, @@ -677,18 +693,6 @@ impl, DS: DeliveryStrategy> .clone() .ok_or_else(|| BuilderError::EmptyConsensusValues)?; - if self.weight_limits == (0, 0) { - return Err(BuilderError::WeightLimits); - } - - // TODO: This should be a weight of faulty validators, not count. - // https://casperlabs.atlassian.net/browse/HWY-117 - let faulty_num = if self.faulty_num > (validators_num / 3) { - return Err(BuilderError::TooManyFaultyNodes); - } else { - self.faulty_num - }; - let instance_id = self.instance_id.clone(); let seed = self.seed; let round_exp = self.round_exp; @@ -705,37 +709,101 @@ impl, DS: DeliveryStrategy> (l, u) }; - let weights: Vec = match self.weight_distribution { - Distribution::Constant => { - let weight = Weight(rng.gen_range(lower, upper)); - (0..validators_num).map(|_| weight).collect() + let (faulty_weights, honest_weights): (Vec, Vec) = { + if (self.faulty_weight > 0 && validators_num == 1) { + return Err(BuilderError::TooManyFaultyNodes( + "Network has only 1 validator, it cannot be malicious. \ + Provide more validators or set `fauluty_weight` to 0." + .to_string(), + )); + } + + if (self.faulty_weight == 0) { + // All validators are honest. + let honest_validators: Vec = self + .weight_distribution + .gen_range_vec(rng, lower, upper, validators_num) + .into_iter() + .map(|w| Weight(w)) + .collect(); + + (vec![], honest_validators) + } else { + // At least 2 validators with some level of faults. + let honest_num = rng.gen_range(1, validators_num); + let faulty_num = validators_num - honest_num; + + assert!( + faulty_num > 0, + "Expected that at least one validator to be malicious." + ); + + let honest_weights = self + .weight_distribution + .gen_range_vec(rng, lower, upper, honest_num); + + let faulty_weights: Vec = { + // Weight of all malicious validators. + let mut weight_limit: u64 = + (self.faulty_weight / 100u64) * honest_weights.iter().sum::(); + let mut validators_left = faulty_num; + let mut weights: Vec = vec![]; + // Generate weight as long as there are empty validator slots and there's a weight left. + while validators_left > 0 { + if validators_left == 1 { + weights.push(weight_limit); + } else { + let weight: u64 = rng.gen_range(lower, weight_limit); + weight_limit -= weight; + weights.push(weight); + } + validators_left -= 1; + } + weights + }; + + ( + faulty_weights.into_iter().map(Weight).collect(), + honest_weights.into_iter().map(Weight).collect(), + ) } - Distribution::Uniform => (0..validators_num) - .map(|_| Weight(rng.gen_range(lower, upper))) - .collect(), - // https://casperlabs.atlassian.net/browse/HWY-116 - Distribution::Poisson(_) => unimplemented!("Poisson distribution of weights"), }; - let weights_sum = weights.iter().sum::().0; + let mut validator_ids = (0..validators_num).map(|i| ValidatorId(i as u64)); - // Network is not safe if weight of malicious validators is higher than ⅓. - let safe_ftt_limit = (weights_sum - 1) / 3; - // Random FTT that still creates secure network – where there's less equivocators than 1/3 of the weights. - let ftt = self.ftt.unwrap_or_else(|| rng.gen_range(1, safe_ftt_limit)); + let weights_sum = faulty_weights + .iter() + .chain(honest_weights.iter()) + .sum::(); - let validator_ids = (0..validators_num) - .map(|i| ValidatorId(i as u64)) + let faulty_validators = validator_ids + .by_ref() + .take(faulty_weights.len()) + .zip(faulty_weights) .collect::>(); - assert_eq!(weights.len(), validator_ids.len()); + let honest_validators = validator_ids + .by_ref() + .take(honest_weights.len()) + .zip(honest_weights) + .collect::>(); - let validators: Validators = { - let zipped: Vec<(ValidatorId, Weight)> = - validator_ids.clone().into_iter().zip(weights).collect(); - Validators::from_iter(zipped) - }; + // Sanity check. + assert_eq!( + faulty_validators.len() + honest_validators.len(), + validators_num as usize, + ); + let validators: Validators = Validators::from_iter( + faulty_validators + .clone() + .into_iter() + .chain(honest_validators.clone().into_iter()), + ); + + let ftt = self.ftt.unwrap_or_else(|| (weights_sum.0 - 1) / 3); + + // Local function creating an instance of `HighwayConsensus` for a single validator. let highway_consensus = |(vid, secrets): ( C::ValidatorId, &mut HashMap, @@ -769,12 +837,10 @@ impl, DS: DeliveryStrategy> let mut validators = vec![]; let mut init_messages = vec![]; - let (faulty_ids, honest_ids) = validator_ids.split_at(faulty_num as usize); - - for (vid, is_faulty) in faulty_ids + for (vid, is_faulty) in faulty_validators .iter() - .map(|vid| (*vid, true)) - .chain(honest_ids.iter().map(|vid| (*vid, false))) + .map(|(vid, _)| (*vid, true)) + .chain(honest_validators.iter().map(|(vid, _)| (*vid, false))) { let (consensus, msgs) = highway_consensus((vid, &mut self.validators_secs)); let validator = Validator::new(vid, is_faulty, consensus); From 575454bb6748394cfb0e7cd2f1d63f3aa182b0f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20G=C3=B3rski?= Date: Fri, 24 Jul 2020 14:26:42 +0200 Subject: [PATCH 07/17] HWY-85: Fix `HighwayTestHarness` for `TestContext`. --- .../consensus/highway_core/highway_testing.rs | 175 ++++++++++-------- 1 file changed, 93 insertions(+), 82 deletions(-) diff --git a/node/src/components/consensus/highway_core/highway_testing.rs b/node/src/components/consensus/highway_core/highway_testing.rs index 46cf976e48..18bbf32109 100644 --- a/node/src/components/consensus/highway_core/highway_testing.rs +++ b/node/src/components/consensus/highway_core/highway_testing.rs @@ -22,33 +22,35 @@ use crate::{ use std::iter::FromIterator; -struct HighwayConsensus { - highway: Highway, - finality_detector: FinalityDetector, +struct HighwayConsensus { + highway: Highway, + finality_detector: FinalityDetector, } -impl HighwayConsensus { - fn run_finality(&mut self) -> FinalityOutcome { +impl HighwayConsensus { + fn run_finality( + &mut self, + ) -> FinalityOutcome<::ConsensusValue, ValidatorIndex> { self.finality_detector.run(self.highway.state()) } - pub(crate) fn highway(&self) -> &Highway { + pub(crate) fn highway(&self) -> &Highway { &self.highway } - pub(crate) fn highway_mut(&mut self) -> &mut Highway { + pub(crate) fn highway_mut(&mut self) -> &mut Highway { &mut self.highway } } #[derive(Clone, Eq, PartialEq)] -enum HighwayMessage { +enum HighwayMessage { Timer(Timestamp), - NewVertex(Vertex), + NewVertex(Vertex), RequestBlock(BlockContext), } -impl Debug for HighwayMessage { +impl Debug for HighwayMessage { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Timer(t) => f.debug_tuple("Timer").field(&t.millis()).finish(), @@ -61,9 +63,9 @@ impl Debug for HighwayMessage { } } -impl HighwayMessage { - fn into_targeted(self, creator: ValidatorId) -> TargetedMessage> { - let create_msg = |hwm: HighwayMessage| Message::new(creator, hwm); +impl HighwayMessage { + fn into_targeted(self, creator: ValidatorId) -> TargetedMessage { + let create_msg = |hwm: HighwayMessage| Message::new(creator, hwm); match self { Timer(_) => TargetedMessage::new(create_msg(self), Target::SingleValidator(creator)), @@ -77,8 +79,8 @@ impl HighwayMessage { use HighwayMessage::*; -impl From> for HighwayMessage { - fn from(eff: Effect) -> Self { +impl From> for HighwayMessage { + fn from(eff: Effect) -> Self { match eff { // The effect is `ValidVertex` but we want to gossip it to other // validators so for them it's just `Vertex` that needs to be validated. @@ -95,14 +97,15 @@ use std::{ fmt::{Debug, Display, Formatter}, marker::PhantomData, }; +use test_harness::TestContext; -impl PartialOrd for HighwayMessage { +impl PartialOrd for HighwayMessage { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(&other)) } } -impl Ord for HighwayMessage { +impl Ord for HighwayMessage { fn cmp(&self, other: &Self) -> std::cmp::Ordering { match (self, other) { (Timer(t1), Timer(t2)) => t1.cmp(&t2), @@ -136,18 +139,18 @@ pub(crate) enum CrankOk { } #[derive(Debug, Eq, PartialEq)] -pub(crate) enum TestRunError { +pub(crate) enum TestRunError { /// VirtualNet was missing a validator when it was expected to exist. MissingValidator(ValidatorId), /// Sender sent a vertex for which it didn't have all dependencies. - SenderMissingDependency(ValidatorId, Dependency), + SenderMissingDependency(ValidatorId, Dependency), /// No more messages in the message queue. NoMessages, /// We run out of consensus values to propose before the end of a test. NoConsensusValues, } -impl Display for TestRunError { +impl Display for TestRunError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { TestRunError::NoMessages => write!( @@ -183,27 +186,27 @@ impl Distribution { } } -trait DeliveryStrategy { +trait DeliveryStrategy { fn gen_delay( &self, rng: &mut R, - message: &HighwayMessage, + message: &HighwayMessage, distributon: &Distribution, base_delivery_timestamp: Timestamp, ) -> DeliverySchedule; } -struct HighwayTestHarness +struct HighwayTestHarness where - C: Context, - DS: DeliveryStrategy, + DS: DeliveryStrategy, { - virtual_net: VirtualNet, HighwayMessage>, + virtual_net: + VirtualNet<::ConsensusValue, HighwayConsensus, HighwayMessage>, /// The instant the network was created. start_time: Timestamp, /// Consensus values to be proposed. /// Order of values in the vector defines the order in which they will be proposed. - consensus_values: VecDeque, + consensus_values: VecDeque<::ConsensusValue>, /// Number of consensus values that the test is started with. consensus_values_num: usize, /// A strategy to pseudo randomly change the message delivery times. @@ -212,15 +215,18 @@ where delivery_time_distribution: Distribution, } -impl HighwayTestHarness +impl HighwayTestHarness where - Ctx: Context, - DS: DeliveryStrategy, + DS: DeliveryStrategy, { fn new>( - virtual_net: VirtualNet, HighwayMessage>, + virtual_net: VirtualNet< + ::ConsensusValue, + HighwayConsensus, + HighwayMessage, + >, start_time: T, - consensus_values: VecDeque, + consensus_values: VecDeque<::ConsensusValue>, delivery_time_distribution: Distribution, delivery_time_strategy: DS, ) -> Self { @@ -240,7 +246,7 @@ where /// Pops one message from the message queue (if there are any) /// and pass it to the recipient validator for execution. /// Messages returned from the execution are scheduled for later delivery. - pub(crate) fn crank(&mut self, rand: &mut R) -> Result> { + pub(crate) fn crank(&mut self, rand: &mut R) -> Result { // Stop the test when each node finalized all consensus values. // Note that we're not testing the order of finalization here. // TODO: Consider moving out all the assertions to client side. @@ -287,7 +293,7 @@ where Ok(CrankOk::Continue) } - fn next_consensus_value(&mut self) -> Option { + fn next_consensus_value(&mut self) -> Option<::ConsensusValue> { self.consensus_values.pop_front() } @@ -297,8 +303,8 @@ where &mut self, validator_id: &ValidatorId, ) -> Result< - &mut Validator, HighwayConsensus>, - TestRunError, + &mut Validator<::ConsensusValue, HighwayMessage, HighwayConsensus>, + TestRunError, > { self.virtual_net .validator_mut(&validator_id) @@ -309,9 +315,9 @@ where &mut self, validator_id: &ValidatorId, f: F, - ) -> Result>, TestRunError> + ) -> Result, TestRunError> where - F: FnOnce(&mut Highway) -> Vec>, + F: FnOnce(&mut Highway) -> Vec>, { let res = f(self.validator_mut(validator_id)?.consensus.highway_mut()); let mut additional_effects = vec![]; @@ -337,8 +343,8 @@ where fn process_message( &mut self, validator_id: ValidatorId, - message: Message>, - ) -> Result>, TestRunError> { + message: Message, + ) -> Result, TestRunError> { self.validator_mut(&validator_id)? .push_messages_received(vec![message.clone()]); @@ -404,9 +410,8 @@ where &mut self, recipient: ValidatorId, sender: ValidatorId, - vertex: Vertex, - ) -> Result>, (Vertex, VertexError)>, TestRunError> - { + vertex: Vertex, + ) -> Result, (Vertex, VertexError)>, TestRunError> { // 1. pre_validate_vertex // 2. missing_dependency // 3. validate_vertex @@ -424,7 +429,7 @@ where match sync_result { Err(vertex_error) => Ok(Err(vertex_error)), Ok((prevalidated_vertex, mut sync_effects)) => { - let add_vertex_effects: Vec> = { + let add_vertex_effects: Vec = { match self .validator_mut(&recipient)? .consensus @@ -454,12 +459,15 @@ where &mut self, recipient: ValidatorId, sender: ValidatorId, - pvv: PreValidatedVertex, + pvv: PreValidatedVertex, ) -> Result< - Result<(PreValidatedVertex, Vec>), (Vertex, VertexError)>, - TestRunError, + Result< + (PreValidatedVertex, Vec), + (Vertex, VertexError), + >, + TestRunError, > { - let mut hwms: Vec> = vec![]; + let mut hwms: Vec = vec![]; loop { let validator = self @@ -492,11 +500,10 @@ where #[allow(clippy::type_complexity)] fn synchronize_dependency( &mut self, - missing_dependency: Dependency, + missing_dependency: Dependency, recipient: ValidatorId, sender: ValidatorId, - ) -> Result>, (Vertex, VertexError)>, TestRunError> - { + ) -> Result, (Vertex, VertexError)>, TestRunError> { let vertex = self .validator_mut(&sender)? .consensus @@ -510,14 +517,14 @@ where /// Returns a `MutableHandle` on the `HighwayTestHarness` object /// that allows for manipulating internal state of the test state. - fn mutable_handle(&mut self) -> MutableHandle { + fn mutable_handle(&mut self) -> MutableHandle { MutableHandle(self) } } -struct MutableHandle<'a, C: Context, DS: DeliveryStrategy>(&'a mut HighwayTestHarness); +struct MutableHandle<'a, DS: DeliveryStrategy>(&'a mut HighwayTestHarness); -impl<'a, C: Context, DS: DeliveryStrategy> MutableHandle<'a, C, DS> { +impl<'a, DS: DeliveryStrategy> MutableHandle<'a, DS> { /// Drops all messages from the queue. fn clear_message_queue(&mut self) { self.0.virtual_net.empty_queue(); @@ -532,9 +539,10 @@ enum BuilderError { EmptyFtt, } -struct HighwayTestHarnessBuilder> { +struct HighwayTestHarnessBuilder { /// Validators (together with their secret keys) in the test run. - validators_secs: HashMap, + validators_secs: + HashMap<::ValidatorId, ::ValidatorSecret>, /// Percentage of faulty validators' (i.e. equivocators) weight. /// Defaults to 0 (network is perfectly secure). faulty_weight: u64, @@ -542,7 +550,7 @@ struct HighwayTestHarnessBuilder> { /// If not given, defaults to 1/3 of total validators' weight. ftt: Option, /// Consensus values to be proposed by the nodes in the network. - consensus_values: Option>, + consensus_values: Option::ConsensusValue>>, /// Distribution of message delivery (delaying, dropping) delays.. delivery_distribution: Distribution, delivery_strategy: DS, @@ -554,7 +562,7 @@ struct HighwayTestHarnessBuilder> { /// Type of discrete distribution of validators' weights. /// Defaults to uniform. weight_distribution: Distribution, - instance_id: C::InstanceId, + instance_id: ::InstanceId, /// Seed for `Highway`. /// Defaults to 0. seed: u64, @@ -566,11 +574,11 @@ struct HighwayTestHarnessBuilder> { // Default strategy for message delivery. struct InstantDeliveryNoDropping; -impl DeliveryStrategy for InstantDeliveryNoDropping { +impl DeliveryStrategy for InstantDeliveryNoDropping { fn gen_delay( &self, _rng: &mut R, - message: &HighwayMessage, + message: &HighwayMessage, _distributon: &Distribution, base_delivery_timestamp: Timestamp, ) -> DeliverySchedule { @@ -582,10 +590,8 @@ impl DeliveryStrategy for InstantDeliveryNoDropping { } } -impl> - HighwayTestHarnessBuilder -{ - fn new(instance_id: C::InstanceId) -> Self { +impl HighwayTestHarnessBuilder { + fn new(instance_id: ::InstanceId) -> Self { HighwayTestHarnessBuilder { validators_secs: HashMap::new(), faulty_weight: 0, @@ -603,12 +609,13 @@ impl> } } -impl, DS: DeliveryStrategy> - HighwayTestHarnessBuilder -{ +impl HighwayTestHarnessBuilder { pub(crate) fn validators( mut self, - validators_secs: HashMap, + validators_secs: HashMap< + ::ValidatorId, + ::ValidatorSecret, + >, ) -> Self { self.validators_secs = validators_secs; self @@ -625,16 +632,19 @@ impl, DS: DeliveryStrategy> self } - pub(crate) fn consensus_values(mut self, cv: Vec) -> Self { + pub(crate) fn consensus_values( + mut self, + cv: Vec<::ConsensusValue>, + ) -> Self { assert!(!cv.is_empty()); self.consensus_values = Some(VecDeque::from(cv)); self } - pub(crate) fn delivery_strategy>( + pub(crate) fn delivery_strategy( self, ds: DS2, - ) -> HighwayTestHarnessBuilder { + ) -> HighwayTestHarnessBuilder { HighwayTestHarnessBuilder { validators_secs: self.validators_secs, faulty_weight: self.faulty_weight, @@ -681,7 +691,7 @@ impl, DS: DeliveryStrategy> self } - fn build(mut self, rng: &mut R) -> Result, BuilderError> { + fn build(mut self, rng: &mut R) -> Result, BuilderError> { if self.validators_secs.is_empty() { return Err(BuilderError::NoValidators); } @@ -805,13 +815,16 @@ impl, DS: DeliveryStrategy> // Local function creating an instance of `HighwayConsensus` for a single validator. let highway_consensus = |(vid, secrets): ( - C::ValidatorId, - &mut HashMap, + ::ValidatorId, + &mut HashMap< + ::ValidatorId, + ::ValidatorSecret, + >, )| { let v_sec = secrets.remove(&vid).expect("Secret key should exist."); let (highway, effects) = { - let highway_params: HighwayParams = HighwayParams { + let highway_params: HighwayParams = HighwayParams { instance_id: instance_id.clone(), validators: validators.clone(), }; @@ -844,7 +857,7 @@ impl, DS: DeliveryStrategy> { let (consensus, msgs) = highway_consensus((vid, &mut self.validators_secs)); let validator = Validator::new(vid, is_faulty, consensus); - let qm: Vec>> = msgs + let qm: Vec> = msgs .into_iter() .map(|hwm| { // These are messages crated on the start of the network. @@ -943,7 +956,7 @@ mod test_harness { let mut rand = XorShiftRng::from_seed(rand::random()); let validators = vec![(ValidatorId(0), TestSecret(0))].into_iter().collect(); - let mut highway_test_harness: HighwayTestHarness = + let mut highway_test_harness: HighwayTestHarness = HighwayTestHarnessBuilder::new(0u64) .validators(validators) .consensus_values(vec![1]) @@ -952,9 +965,7 @@ mod test_harness { .ok() .expect("Construction was successful"); - let mut mut_handle = highway_test_harness.mutable_handle(); - mut_handle.clear_message_queue(); - drop(mut_handle); + highway_test_harness.mutable_handle().clear_message_queue(); assert_eq!( highway_test_harness.crank(&mut rand), @@ -964,13 +975,13 @@ mod test_harness { } #[test] - fn done_when_all_finalized() -> Result<(), TestRunError> { + fn done_when_all_finalized() -> Result<(), TestRunError> { let mut rand = XorShiftRng::from_seed(rand::random()); let validators = (0..10u32) .map(|i| (ValidatorId(i as u64), TestSecret(i))) .collect(); - let mut highway_test_harness: HighwayTestHarness = + let mut highway_test_harness: HighwayTestHarness = HighwayTestHarnessBuilder::new(0u64) .validators(validators) .consensus_values((0..10).collect()) From f166add9d6bf51eee08033f42699379d5484d367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20G=C3=B3rski?= Date: Fri, 24 Jul 2020 23:45:30 +0200 Subject: [PATCH 08/17] HWY-85: `InstanceId` no longer has to be passed in. --- .../consensus/highway_core/highway_testing.rs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/node/src/components/consensus/highway_core/highway_testing.rs b/node/src/components/consensus/highway_core/highway_testing.rs index 18bbf32109..a93e884711 100644 --- a/node/src/components/consensus/highway_core/highway_testing.rs +++ b/node/src/components/consensus/highway_core/highway_testing.rs @@ -562,7 +562,6 @@ struct HighwayTestHarnessBuilder { /// Type of discrete distribution of validators' weights. /// Defaults to uniform. weight_distribution: Distribution, - instance_id: ::InstanceId, /// Seed for `Highway`. /// Defaults to 0. seed: u64, @@ -591,7 +590,7 @@ impl DeliveryStrategy for InstantDeliveryNoDropping { } impl HighwayTestHarnessBuilder { - fn new(instance_id: ::InstanceId) -> Self { + fn new() -> Self { HighwayTestHarnessBuilder { validators_secs: HashMap::new(), faulty_weight: 0, @@ -602,7 +601,6 @@ impl HighwayTestHarnessBuilder { weight_limits: (0, 0), start_time: Timestamp::zero(), weight_distribution: Distribution::Uniform, - instance_id, seed: 0, round_exp: 12, } @@ -655,7 +653,6 @@ impl HighwayTestHarnessBuilder { weight_limits: self.weight_limits, start_time: self.start_time, weight_distribution: self.weight_distribution, - instance_id: self.instance_id, seed: self.seed, round_exp: self.round_exp, } @@ -703,7 +700,7 @@ impl HighwayTestHarnessBuilder { .clone() .ok_or_else(|| BuilderError::EmptyConsensusValues)?; - let instance_id = self.instance_id.clone(); + let instance_id = 0; let seed = self.seed; let round_exp = self.round_exp; let start_time = self.start_time; @@ -954,11 +951,9 @@ mod test_harness { #[test] fn on_empty_queue_error() { let mut rand = XorShiftRng::from_seed(rand::random()); - let validators = vec![(ValidatorId(0), TestSecret(0))].into_iter().collect(); let mut highway_test_harness: HighwayTestHarness = - HighwayTestHarnessBuilder::new(0u64) - .validators(validators) + HighwayTestHarnessBuilder::new() .consensus_values(vec![1]) .weight_limits(7, 10) .build(&mut rand) @@ -977,13 +972,8 @@ mod test_harness { #[test] fn done_when_all_finalized() -> Result<(), TestRunError> { let mut rand = XorShiftRng::from_seed(rand::random()); - let validators = (0..10u32) - .map(|i| (ValidatorId(i as u64), TestSecret(i))) - .collect(); - let mut highway_test_harness: HighwayTestHarness = - HighwayTestHarnessBuilder::new(0u64) - .validators(validators) + HighwayTestHarnessBuilder::new() .consensus_values((0..10).collect()) .weight_limits(7, 10) .build(&mut rand) From 42b095dd72ba12527941bae4d747193d376f6175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20G=C3=B3rski?= Date: Fri, 24 Jul 2020 23:46:45 +0200 Subject: [PATCH 09/17] HWY-85: Generate test validators rather than passing them in. --- .../consensus/highway_core/highway_testing.rs | 121 ++++++++---------- 1 file changed, 55 insertions(+), 66 deletions(-) diff --git a/node/src/components/consensus/highway_core/highway_testing.rs b/node/src/components/consensus/highway_core/highway_testing.rs index a93e884711..fc9a6e8b40 100644 --- a/node/src/components/consensus/highway_core/highway_testing.rs +++ b/node/src/components/consensus/highway_core/highway_testing.rs @@ -97,7 +97,7 @@ use std::{ fmt::{Debug, Display, Formatter}, marker::PhantomData, }; -use test_harness::TestContext; +use test_harness::{TestContext, TestSecret}; impl PartialOrd for HighwayMessage { fn partial_cmp(&self, other: &Self) -> Option { @@ -532,7 +532,6 @@ impl<'a, DS: DeliveryStrategy> MutableHandle<'a, DS> { } enum BuilderError { - NoValidators, EmptyConsensusValues, WeightLimits, TooManyFaultyNodes(String), @@ -540,9 +539,9 @@ enum BuilderError { } struct HighwayTestHarnessBuilder { - /// Validators (together with their secret keys) in the test run. - validators_secs: - HashMap<::ValidatorId, ::ValidatorSecret>, + /// Maximum number of validators in the network. + /// Defaults to 10. + max_validators: u8, /// Percentage of faulty validators' (i.e. equivocators) weight. /// Defaults to 0 (network is perfectly secure). faulty_weight: u64, @@ -592,7 +591,7 @@ impl DeliveryStrategy for InstantDeliveryNoDropping { impl HighwayTestHarnessBuilder { fn new() -> Self { HighwayTestHarnessBuilder { - validators_secs: HashMap::new(), + max_validators: 10, faulty_weight: 0, ftt: None, consensus_values: None, @@ -608,24 +607,9 @@ impl HighwayTestHarnessBuilder { } impl HighwayTestHarnessBuilder { - pub(crate) fn validators( - mut self, - validators_secs: HashMap< - ::ValidatorId, - ::ValidatorSecret, - >, - ) -> Self { - self.validators_secs = validators_secs; - self - } - /// Sets a percentage of weight that will be assigned to malicious nodes. /// `faulty_weight` must be a value between 0 (inclusive) and 100 (inclusive). pub(crate) fn faulty_weight(mut self, faulty_weight: u64) -> Self { - assert!( - faulty_weight <= 33, - "Expected value between 0 (inclusive) and 33 (inclusive)" - ); self.faulty_weight = faulty_weight; self } @@ -644,7 +628,7 @@ impl HighwayTestHarnessBuilder { ds: DS2, ) -> HighwayTestHarnessBuilder { HighwayTestHarnessBuilder { - validators_secs: self.validators_secs, + max_validators: self.max_validators, faulty_weight: self.faulty_weight, ftt: self.ftt, consensus_values: self.consensus_values, @@ -688,13 +672,12 @@ impl HighwayTestHarnessBuilder { self } - fn build(mut self, rng: &mut R) -> Result, BuilderError> { - if self.validators_secs.is_empty() { - return Err(BuilderError::NoValidators); - } - - let validators_num = self.validators_secs.len() as u8; + fn max_faulty_validators(mut self, max_faulty_count: u8) -> Self { + self.max_validators = max_faulty_count; + self + } + fn build(mut self, rng: &mut R) -> Result, BuilderError> { let consensus_values = self .consensus_values .clone() @@ -707,26 +690,23 @@ impl HighwayTestHarnessBuilder { let (lower, upper) = { let (l, u) = self.weight_limits; - // `rng.gen_range(x, y)` panics if `x >= y` - // and since `safe_ftt` is calculated as: `(weight_sum - 1) / 3` - // it may panic on `x` < 7 b/c it will round down to 1. - if (l >= u) || l < 7 { + if l >= u { return Err(BuilderError::WeightLimits); } (l, u) }; let (faulty_weights, honest_weights): (Vec, Vec) = { - if (self.faulty_weight > 0 && validators_num == 1) { + if (self.faulty_weight > 33) { return Err(BuilderError::TooManyFaultyNodes( - "Network has only 1 validator, it cannot be malicious. \ - Provide more validators or set `fauluty_weight` to 0." + "Total weight of all malicious validators cannot be more than 33% of all network weight." .to_string(), )); } if (self.faulty_weight == 0) { // All validators are honest. + let validators_num = rng.gen_range(2, self.max_validators); let honest_validators: Vec = self .weight_distribution .gen_range_vec(rng, lower, upper, validators_num) @@ -736,35 +716,35 @@ impl HighwayTestHarnessBuilder { (vec![], honest_validators) } else { - // At least 2 validators with some level of faults. - let honest_num = rng.gen_range(1, validators_num); - let faulty_num = validators_num - honest_num; + // At least 2 validators total and at least one faulty. + let faulty_num = if self.max_validators == 1 { + 1 + } else { + rng.gen_range(1, self.max_validators) + }; assert!( faulty_num > 0, "Expected that at least one validator to be malicious." ); - let honest_weights = self + let faulty_weights = self .weight_distribution - .gen_range_vec(rng, lower, upper, honest_num); - - let faulty_weights: Vec = { - // Weight of all malicious validators. - let mut weight_limit: u64 = - (self.faulty_weight / 100u64) * honest_weights.iter().sum::(); - let mut validators_left = faulty_num; - let mut weights: Vec = vec![]; - // Generate weight as long as there are empty validator slots and there's a weight left. - while validators_left > 0 { - if validators_left == 1 { - weights.push(weight_limit); + .gen_range_vec(rng, lower, upper, faulty_num); + + let honest_weights = { + let faulty_sum = faulty_weights.iter().sum::(); + let mut weights_to_distribute: u64 = + faulty_sum * 100 / self.faulty_weight - faulty_sum; + let mut weights = vec![]; + while weights_to_distribute > 0 { + let weight = if weights_to_distribute < upper { + weights_to_distribute } else { - let weight: u64 = rng.gen_range(lower, weight_limit); - weight_limit -= weight; - weights.push(weight); - } - validators_left -= 1; + rng.gen_range(lower, upper) + }; + weights.push(weight); + weights_to_distribute -= weight } weights }; @@ -776,29 +756,36 @@ impl HighwayTestHarnessBuilder { } }; - let mut validator_ids = (0..validators_num).map(|i| ValidatorId(i as u64)); + let validators_num = faulty_weights.len() + honest_weights.len(); + + let mut validator_ids: Vec = + (0..validators_num).map(|i| ValidatorId(i as u64)).collect(); + + let mut secrets = validator_ids + .iter() + .map(|vid| (*vid, TestSecret(vid.0))) + .collect(); let weights_sum = faulty_weights .iter() .chain(honest_weights.iter()) .sum::(); - let faulty_validators = validator_ids - .by_ref() + let mut validator_ids_iter = validator_ids.into_iter(); + + let faulty_validators = (&mut validator_ids_iter) .take(faulty_weights.len()) .zip(faulty_weights) .collect::>(); - let honest_validators = validator_ids - .by_ref() + let honest_validators = validator_ids_iter .take(honest_weights.len()) .zip(honest_weights) .collect::>(); - // Sanity check. assert_eq!( faulty_validators.len() + honest_validators.len(), - validators_num as usize, + validators_num ); let validators: Validators = Validators::from_iter( @@ -852,7 +839,7 @@ impl HighwayTestHarnessBuilder { .map(|(vid, _)| (*vid, true)) .chain(honest_validators.iter().map(|(vid, _)| (*vid, false))) { - let (consensus, msgs) = highway_consensus((vid, &mut self.validators_secs)); + let (consensus, msgs) = highway_consensus((vid, &mut secrets)); let validator = Validator::new(vid, is_faulty, consensus); let qm: Vec> = msgs .into_iter() @@ -913,7 +900,7 @@ mod test_harness { pub(crate) struct TestContext; #[derive(Clone, Debug, Eq, PartialEq)] - pub(crate) struct TestSecret(pub(crate) u32); + pub(crate) struct TestSecret(pub(crate) u64); impl ValidatorSecret for TestSecret { type Hash = u64; @@ -974,8 +961,10 @@ mod test_harness { let mut rand = XorShiftRng::from_seed(rand::random()); let mut highway_test_harness: HighwayTestHarness = HighwayTestHarnessBuilder::new() + .max_faulty_validators(5) .consensus_values((0..10).collect()) - .weight_limits(7, 10) + .weight_limits(5, 10) + .faulty_weight(5) .build(&mut rand) .ok() .expect("Construction was successful"); From ba4ed585f28aa183187fa8e1d4319bfb1280f065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20G=C3=B3rski?= Date: Fri, 24 Jul 2020 23:48:21 +0200 Subject: [PATCH 10/17] HWY-85: Renames. --- .../consensus/highway_core/highway_testing.rs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/node/src/components/consensus/highway_core/highway_testing.rs b/node/src/components/consensus/highway_core/highway_testing.rs index fc9a6e8b40..922047e0ac 100644 --- a/node/src/components/consensus/highway_core/highway_testing.rs +++ b/node/src/components/consensus/highway_core/highway_testing.rs @@ -539,9 +539,9 @@ enum BuilderError { } struct HighwayTestHarnessBuilder { - /// Maximum number of validators in the network. + /// Maximum number of faulty validators in the network. /// Defaults to 10. - max_validators: u8, + max_faulty_validators: u8, /// Percentage of faulty validators' (i.e. equivocators) weight. /// Defaults to 0 (network is perfectly secure). faulty_weight: u64, @@ -591,7 +591,7 @@ impl DeliveryStrategy for InstantDeliveryNoDropping { impl HighwayTestHarnessBuilder { fn new() -> Self { HighwayTestHarnessBuilder { - max_validators: 10, + max_faulty_validators: 10, faulty_weight: 0, ftt: None, consensus_values: None, @@ -609,7 +609,7 @@ impl HighwayTestHarnessBuilder { impl HighwayTestHarnessBuilder { /// Sets a percentage of weight that will be assigned to malicious nodes. /// `faulty_weight` must be a value between 0 (inclusive) and 100 (inclusive). - pub(crate) fn faulty_weight(mut self, faulty_weight: u64) -> Self { + pub(crate) fn faulty_weight_perc(mut self, faulty_weight: u64) -> Self { self.faulty_weight = faulty_weight; self } @@ -628,7 +628,7 @@ impl HighwayTestHarnessBuilder { ds: DS2, ) -> HighwayTestHarnessBuilder { HighwayTestHarnessBuilder { - max_validators: self.max_validators, + max_faulty_validators: self.max_faulty_validators, faulty_weight: self.faulty_weight, ftt: self.ftt, consensus_values: self.consensus_values, @@ -673,7 +673,7 @@ impl HighwayTestHarnessBuilder { } fn max_faulty_validators(mut self, max_faulty_count: u8) -> Self { - self.max_validators = max_faulty_count; + self.max_faulty_validators = max_faulty_count; self } @@ -706,7 +706,7 @@ impl HighwayTestHarnessBuilder { if (self.faulty_weight == 0) { // All validators are honest. - let validators_num = rng.gen_range(2, self.max_validators); + let validators_num = rng.gen_range(2, self.max_faulty_validators); let honest_validators: Vec = self .weight_distribution .gen_range_vec(rng, lower, upper, validators_num) @@ -717,10 +717,10 @@ impl HighwayTestHarnessBuilder { (vec![], honest_validators) } else { // At least 2 validators total and at least one faulty. - let faulty_num = if self.max_validators == 1 { + let faulty_num = if self.max_faulty_validators == 1 { 1 } else { - rng.gen_range(1, self.max_validators) + rng.gen_range(1, self.max_faulty_validators) }; assert!( @@ -964,7 +964,7 @@ mod test_harness { .max_faulty_validators(5) .consensus_values((0..10).collect()) .weight_limits(5, 10) - .faulty_weight(5) + .faulty_weight_perc(5) .build(&mut rand) .ok() .expect("Construction was successful"); From 1971f356ff7034a2da16819476d2916d5909072e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20G=C3=B3rski?= Date: Fri, 24 Jul 2020 23:53:02 +0200 Subject: [PATCH 11/17] HWY-85: Pass in count of consensus values instead of vector. --- .../consensus/highway_core/highway_testing.rs | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/node/src/components/consensus/highway_core/highway_testing.rs b/node/src/components/consensus/highway_core/highway_testing.rs index 922047e0ac..564778556a 100644 --- a/node/src/components/consensus/highway_core/highway_testing.rs +++ b/node/src/components/consensus/highway_core/highway_testing.rs @@ -548,8 +548,10 @@ struct HighwayTestHarnessBuilder { /// FTT value for the finality detector. /// If not given, defaults to 1/3 of total validators' weight. ftt: Option, - /// Consensus values to be proposed by the nodes in the network. - consensus_values: Option::ConsensusValue>>, + /// Number of consensus values to be proposed by the nodes in the network. + /// Those will be generated by the test framework. + /// Defaults to 10. + consensus_values_count: u8, /// Distribution of message delivery (delaying, dropping) delays.. delivery_distribution: Distribution, delivery_strategy: DS, @@ -594,7 +596,7 @@ impl HighwayTestHarnessBuilder { max_faulty_validators: 10, faulty_weight: 0, ftt: None, - consensus_values: None, + consensus_values_count: 10, delivery_distribution: Distribution::Uniform, delivery_strategy: InstantDeliveryNoDropping, weight_limits: (0, 0), @@ -614,12 +616,9 @@ impl HighwayTestHarnessBuilder { self } - pub(crate) fn consensus_values( - mut self, - cv: Vec<::ConsensusValue>, - ) -> Self { - assert!(!cv.is_empty()); - self.consensus_values = Some(VecDeque::from(cv)); + pub(crate) fn consensus_values_count(mut self, count: u8) -> Self { + assert!(count > 0); + self.consensus_values_count = count; self } @@ -631,7 +630,7 @@ impl HighwayTestHarnessBuilder { max_faulty_validators: self.max_faulty_validators, faulty_weight: self.faulty_weight, ftt: self.ftt, - consensus_values: self.consensus_values, + consensus_values_count: self.consensus_values_count, delivery_distribution: self.delivery_distribution, delivery_strategy: ds, weight_limits: self.weight_limits, @@ -678,10 +677,7 @@ impl HighwayTestHarnessBuilder { } fn build(mut self, rng: &mut R) -> Result, BuilderError> { - let consensus_values = self - .consensus_values - .clone() - .ok_or_else(|| BuilderError::EmptyConsensusValues)?; + let consensus_values = (0..self.consensus_values_count as u32).collect::>(); let instance_id = 0; let seed = self.seed; @@ -941,7 +937,7 @@ mod test_harness { let mut highway_test_harness: HighwayTestHarness = HighwayTestHarnessBuilder::new() - .consensus_values(vec![1]) + .consensus_values_count(1) .weight_limits(7, 10) .build(&mut rand) .ok() @@ -962,7 +958,7 @@ mod test_harness { let mut highway_test_harness: HighwayTestHarness = HighwayTestHarnessBuilder::new() .max_faulty_validators(5) - .consensus_values((0..10).collect()) + .consensus_values_count(5) .weight_limits(5, 10) .faulty_weight_perc(5) .build(&mut rand) From a26f91b9a99dbd70630bbad3f839091d0daacb00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20G=C3=B3rski?= Date: Fri, 24 Jul 2020 23:56:04 +0200 Subject: [PATCH 12/17] HWY-85: Extract `TestContext` from `test_harness` mod. --- .../consensus/highway_core/highway_testing.rs | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/node/src/components/consensus/highway_core/highway_testing.rs b/node/src/components/consensus/highway_core/highway_testing.rs index 564778556a..fbefd9de0a 100644 --- a/node/src/components/consensus/highway_core/highway_testing.rs +++ b/node/src/components/consensus/highway_core/highway_testing.rs @@ -14,7 +14,7 @@ use crate::{ DeliverySchedule, Message, Target, TargetedMessage, Validator, ValidatorId, VirtualNet, }, tests::queue::{MessageT, QueueEntry}, - traits::{ConsensusValueT, Context}, + traits::{ConsensusValueT, Context, ValidatorSecret}, BlockContext, }, types::Timestamp, @@ -93,11 +93,11 @@ impl From> for HighwayMessage { use rand::Rng; use std::{ - collections::{HashMap, VecDeque}, + collections::{hash_map::DefaultHasher, HashMap, VecDeque}, fmt::{Debug, Display, Formatter}, + hash::Hasher, marker::PhantomData, }; -use test_harness::{TestContext, TestSecret}; impl PartialOrd for HighwayMessage { fn partial_cmp(&self, other: &Self) -> Option { @@ -610,7 +610,7 @@ impl HighwayTestHarnessBuilder { impl HighwayTestHarnessBuilder { /// Sets a percentage of weight that will be assigned to malicious nodes. - /// `faulty_weight` must be a value between 0 (inclusive) and 100 (inclusive). + /// `faulty_weight` must be a value between 0 (inclusive) and 33 (inclusive). pub(crate) fn faulty_weight_perc(mut self, faulty_weight: u64) -> Self { self.faulty_weight = faulty_weight; self @@ -873,6 +873,45 @@ impl HighwayTestHarnessBuilder { } } +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct TestContext; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) struct TestSecret(pub(crate) u64); + +impl ValidatorSecret for TestSecret { + type Hash = u64; + type Signature = u64; + + fn sign(&self, data: &Self::Hash) -> Self::Signature { + data + u64::from(self.0) + } +} + +impl Context for TestContext { + type ConsensusValue = u32; + type ValidatorId = ValidatorId; + type ValidatorSecret = TestSecret; + type Signature = u64; + type Hash = u64; + type InstanceId = u64; + + fn hash(data: &[u8]) -> Self::Hash { + let mut hasher = DefaultHasher::new(); + hasher.write(data); + hasher.finish() + } + + fn verify_signature( + hash: &Self::Hash, + public_key: &Self::ValidatorId, + signature: &::Signature, + ) -> bool { + let computed_signature = hash + public_key.0; + computed_signature == *signature + } +} + mod test_harness { use super::{ CrankOk, DeliverySchedule, DeliveryStrategy, HighwayMessage, HighwayTestHarness, @@ -892,45 +931,6 @@ mod test_harness { hash::Hasher, }; - #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] - pub(crate) struct TestContext; - - #[derive(Clone, Debug, Eq, PartialEq)] - pub(crate) struct TestSecret(pub(crate) u64); - - impl ValidatorSecret for TestSecret { - type Hash = u64; - type Signature = u64; - - fn sign(&self, data: &Self::Hash) -> Self::Signature { - data + u64::from(self.0) - } - } - - impl Context for TestContext { - type ConsensusValue = u32; - type ValidatorId = ValidatorId; - type ValidatorSecret = TestSecret; - type Signature = u64; - type Hash = u64; - type InstanceId = u64; - - fn hash(data: &[u8]) -> Self::Hash { - let mut hasher = DefaultHasher::new(); - hasher.write(data); - hasher.finish() - } - - fn verify_signature( - hash: &Self::Hash, - public_key: &Self::ValidatorId, - signature: &::Signature, - ) -> bool { - let computed_signature = hash + public_key.0; - computed_signature == *signature - } - } - #[test] fn on_empty_queue_error() { let mut rand = XorShiftRng::from_seed(rand::random()); From d596b38e002e54697e2add28bb81baa65027e581 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20G=C3=B3rski?= Date: Fri, 24 Jul 2020 23:59:05 +0200 Subject: [PATCH 13/17] HWY-85: Change defaults for weight limits. --- node/src/components/consensus/highway_core/highway_testing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/src/components/consensus/highway_core/highway_testing.rs b/node/src/components/consensus/highway_core/highway_testing.rs index fbefd9de0a..aed8f59068 100644 --- a/node/src/components/consensus/highway_core/highway_testing.rs +++ b/node/src/components/consensus/highway_core/highway_testing.rs @@ -599,7 +599,7 @@ impl HighwayTestHarnessBuilder { consensus_values_count: 10, delivery_distribution: Distribution::Uniform, delivery_strategy: InstantDeliveryNoDropping, - weight_limits: (0, 0), + weight_limits: (1, 100), start_time: Timestamp::zero(), weight_distribution: Distribution::Uniform, seed: 0, From 182d69f80d579cf5d392772bc4a51c82ae88c40d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20G=C3=B3rski?= Date: Sat, 25 Jul 2020 00:00:21 +0200 Subject: [PATCH 14/17] HWY-85: Change test params for `done_when_all_finalized`. --- node/src/components/consensus/highway_core/highway_testing.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node/src/components/consensus/highway_core/highway_testing.rs b/node/src/components/consensus/highway_core/highway_testing.rs index aed8f59068..c70d01476b 100644 --- a/node/src/components/consensus/highway_core/highway_testing.rs +++ b/node/src/components/consensus/highway_core/highway_testing.rs @@ -957,10 +957,10 @@ mod test_harness { let mut rand = XorShiftRng::from_seed(rand::random()); let mut highway_test_harness: HighwayTestHarness = HighwayTestHarnessBuilder::new() - .max_faulty_validators(5) + .max_faulty_validators(3) .consensus_values_count(5) .weight_limits(5, 10) - .faulty_weight_perc(5) + .faulty_weight_perc(20) .build(&mut rand) .ok() .expect("Construction was successful"); From 62f025f6217ae936aff2a952f4d43b7f196a6095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20G=C3=B3rski?= Date: Sat, 25 Jul 2020 00:03:15 +0200 Subject: [PATCH 15/17] HWY-85: Clippy. --- .../components/consensus/highway_core/highway_testing.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/node/src/components/consensus/highway_core/highway_testing.rs b/node/src/components/consensus/highway_core/highway_testing.rs index c70d01476b..b361fb8d7f 100644 --- a/node/src/components/consensus/highway_core/highway_testing.rs +++ b/node/src/components/consensus/highway_core/highway_testing.rs @@ -707,7 +707,7 @@ impl HighwayTestHarnessBuilder { .weight_distribution .gen_range_vec(rng, lower, upper, validators_num) .into_iter() - .map(|w| Weight(w)) + .map(Weight) .collect(); (vec![], honest_validators) @@ -805,7 +805,7 @@ impl HighwayTestHarnessBuilder { let (highway, effects) = { let highway_params: HighwayParams = HighwayParams { - instance_id: instance_id.clone(), + instance_id, validators: validators.clone(), }; @@ -884,7 +884,7 @@ impl ValidatorSecret for TestSecret { type Signature = u64; fn sign(&self, data: &Self::Hash) -> Self::Signature { - data + u64::from(self.0) + data + self.0 } } From 0e10abe11c4ac179c36982c75b929100a2084031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20G=C3=B3rski?= Date: Sun, 26 Jul 2020 11:29:49 +0200 Subject: [PATCH 16/17] HWY-85: Simplify code as per Andreas' review comments. --- .../consensus/highway_core/highway_testing.rs | 126 ++++++------------ 1 file changed, 43 insertions(+), 83 deletions(-) diff --git a/node/src/components/consensus/highway_core/highway_testing.rs b/node/src/components/consensus/highway_core/highway_testing.rs index 7671b68bc8..5192f0b978 100644 --- a/node/src/components/consensus/highway_core/highway_testing.rs +++ b/node/src/components/consensus/highway_core/highway_testing.rs @@ -30,13 +30,10 @@ struct HighwayConsensus { finality_detector: FinalityDetector, } +type ConsensusValue = u32; + impl HighwayConsensus { - fn run_finality( - &mut self, - ) -> FinalityOutcome< - ::ConsensusValue, - ::ValidatorId, - > { + fn run_finality(&mut self) -> FinalityOutcome { self.finality_detector.run(&self.highway) } @@ -206,13 +203,12 @@ struct HighwayTestHarness where DS: DeliveryStrategy, { - virtual_net: - VirtualNet<::ConsensusValue, HighwayConsensus, HighwayMessage>, + virtual_net: VirtualNet, /// The instant the network was created. start_time: Timestamp, /// Consensus values to be proposed. /// Order of values in the vector defines the order in which they will be proposed. - consensus_values: VecDeque<::ConsensusValue>, + consensus_values: VecDeque, /// Number of consensus values that the test is started with. consensus_values_num: usize, /// A strategy to pseudo randomly change the message delivery times. @@ -226,13 +222,9 @@ where DS: DeliveryStrategy, { fn new>( - virtual_net: VirtualNet< - ::ConsensusValue, - HighwayConsensus, - HighwayMessage, - >, + virtual_net: VirtualNet, start_time: T, - consensus_values: VecDeque<::ConsensusValue>, + consensus_values: VecDeque, delivery_time_distribution: Distribution, delivery_time_strategy: DS, ) -> Self { @@ -299,7 +291,7 @@ where Ok(CrankOk::Continue) } - fn next_consensus_value(&mut self) -> Option<::ConsensusValue> { + fn next_consensus_value(&mut self) -> Option { self.consensus_values.pop_front() } @@ -308,10 +300,8 @@ where fn validator_mut( &mut self, validator_id: &ValidatorId, - ) -> Result< - &mut Validator<::ConsensusValue, HighwayMessage, HighwayConsensus>, - TestRunError, - > { + ) -> Result<&mut Validator, TestRunError> + { self.virtual_net .validator_mut(&validator_id) .ok_or_else(|| TestRunError::MissingValidator(*validator_id)) @@ -551,7 +541,7 @@ struct HighwayTestHarnessBuilder { max_faulty_validators: u8, /// Percentage of faulty validators' (i.e. equivocators) weight. /// Defaults to 0 (network is perfectly secure). - faulty_weight: u64, + faulty_percent: u64, /// FTT value for the finality detector. /// If not given, defaults to 1/3 of total validators' weight. ftt: Option, @@ -601,7 +591,7 @@ impl HighwayTestHarnessBuilder { fn new() -> Self { HighwayTestHarnessBuilder { max_faulty_validators: 10, - faulty_weight: 0, + faulty_percent: 0, ftt: None, consensus_values_count: 10, delivery_distribution: Distribution::Uniform, @@ -619,7 +609,7 @@ impl HighwayTestHarnessBuilder { /// Sets a percentage of weight that will be assigned to malicious nodes. /// `faulty_weight` must be a value between 0 (inclusive) and 33 (inclusive). pub(crate) fn faulty_weight_perc(mut self, faulty_weight: u64) -> Self { - self.faulty_weight = faulty_weight; + self.faulty_percent = faulty_weight; self } @@ -635,7 +625,7 @@ impl HighwayTestHarnessBuilder { ) -> HighwayTestHarnessBuilder { HighwayTestHarnessBuilder { max_faulty_validators: self.max_faulty_validators, - faulty_weight: self.faulty_weight, + faulty_percent: self.faulty_percent, ftt: self.ftt, consensus_values_count: self.consensus_values_count, delivery_distribution: self.delivery_distribution, @@ -700,16 +690,16 @@ impl HighwayTestHarnessBuilder { }; let (faulty_weights, honest_weights): (Vec, Vec) = { - if (self.faulty_weight > 33) { + if (self.faulty_percent > 33) { return Err(BuilderError::TooManyFaultyNodes( "Total weight of all malicious validators cannot be more than 33% of all network weight." .to_string(), )); } - if (self.faulty_weight == 0) { + if (self.faulty_percent == 0) { // All validators are honest. - let validators_num = rng.gen_range(2, self.max_faulty_validators); + let validators_num = rng.gen_range(2, self.max_faulty_validators + 1); let honest_validators: Vec = self .weight_distribution .gen_range_vec(rng, lower, upper, validators_num) @@ -720,16 +710,7 @@ impl HighwayTestHarnessBuilder { (vec![], honest_validators) } else { // At least 2 validators total and at least one faulty. - let faulty_num = if self.max_faulty_validators == 1 { - 1 - } else { - rng.gen_range(1, self.max_faulty_validators) - }; - - assert!( - faulty_num > 0, - "Expected that at least one validator to be malicious." - ); + let faulty_num = rng.gen_range(1, self.max_faulty_validators + 1); let faulty_weights = self .weight_distribution @@ -738,7 +719,7 @@ impl HighwayTestHarnessBuilder { let honest_weights = { let faulty_sum = faulty_weights.iter().sum::(); let mut weights_to_distribute: u64 = - faulty_sum * 100 / self.faulty_weight - faulty_sum; + faulty_sum * 100 / self.faulty_percent - faulty_sum; let mut weights = vec![]; while weights_to_distribute > 0 { let weight = if weights_to_distribute < upper { @@ -759,45 +740,24 @@ impl HighwayTestHarnessBuilder { } }; - let validators_num = faulty_weights.len() + honest_weights.len(); - - let mut validator_ids: Vec = - (0..validators_num).map(|i| ValidatorId(i as u64)).collect(); - - let mut secrets = validator_ids - .iter() - .map(|vid| (*vid, TestSecret(vid.0))) - .collect(); - let weights_sum = faulty_weights .iter() .chain(honest_weights.iter()) .sum::(); - let mut validator_ids_iter = validator_ids.into_iter(); - - let faulty_validators = (&mut validator_ids_iter) - .take(faulty_weights.len()) - .zip(faulty_weights) - .collect::>(); - - let honest_validators = validator_ids_iter - .take(honest_weights.len()) - .zip(honest_weights) - .collect::>(); - - assert_eq!( - faulty_validators.len() + honest_validators.len(), - validators_num - ); - let validators: Validators = Validators::from_iter( - faulty_validators - .clone() - .into_iter() - .chain(honest_validators.clone().into_iter()), + faulty_weights + .iter() + .chain(honest_weights.iter()) + .enumerate() + .map(|(i, weight)| (ValidatorId(i as u64), *weight)), ); + let mut secrets = validators + .enumerate() + .map(|(_, vid)| (*vid.id(), TestSecret(vid.id().0))) + .collect(); + let ftt = self.ftt.unwrap_or_else(|| (weights_sum.0 - 1) / 3); // Local function creating an instance of `HighwayConsensus` for a single validator. @@ -833,15 +793,15 @@ impl HighwayTestHarnessBuilder { ) }; + let faulty_num = faulty_weights.len(); + let (validators, init_messages) = { - let mut validators = vec![]; + let mut validators_loc = vec![]; let mut init_messages = vec![]; - for (vid, is_faulty) in faulty_validators - .iter() - .map(|(vid, _)| (*vid, true)) - .chain(honest_validators.iter().map(|(vid, _)| (*vid, false))) - { + for (_, validator) in validators.enumerate() { + let vid = *validator.id(); + let is_faulty = vid.0 < faulty_num as u64; let (consensus, msgs) = highway_consensus((vid, &mut secrets)); let validator = Validator::new(vid, is_faulty, consensus); let qm: Vec> = msgs @@ -853,10 +813,10 @@ impl HighwayTestHarnessBuilder { }) .collect(); init_messages.extend(qm); - validators.push(validator); + validators_loc.push(validator); } - (validators, init_messages) + (validators_loc, init_messages) }; let delivery_time_strategy = self.delivery_strategy; @@ -940,20 +900,20 @@ mod test_harness { #[test] fn on_empty_queue_error() { - let mut rand = TestRng::new(); + let mut rng = TestRng::new(); let mut highway_test_harness: HighwayTestHarness = HighwayTestHarnessBuilder::new() .consensus_values_count(1) .weight_limits(7, 10) - .build(&mut rand) + .build(&mut rng) .ok() .expect("Construction was successful"); highway_test_harness.mutable_handle().clear_message_queue(); assert_eq!( - highway_test_harness.crank(&mut rand), + highway_test_harness.crank(&mut rng), Err(TestRunError::NoMessages), "Expected the test run to stop." ); @@ -961,19 +921,19 @@ mod test_harness { #[test] fn done_when_all_finalized() -> Result<(), TestRunError> { - let mut rand = TestRng::new(); + let mut rng = TestRng::new(); let mut highway_test_harness: HighwayTestHarness = HighwayTestHarnessBuilder::new() .max_faulty_validators(3) .consensus_values_count(5) .weight_limits(5, 10) .faulty_weight_perc(20) - .build(&mut rand) + .build(&mut rng) .ok() .expect("Construction was successful"); loop { - let crank_res = highway_test_harness.crank(&mut rand)?; + let crank_res = highway_test_harness.crank(&mut rng)?; match crank_res { CrankOk::Continue => continue, CrankOk::Done => break, From 42cdbd354e364a604c7ba8de4667a9118d22efb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20G=C3=B3rski?= Date: Sun, 26 Jul 2020 23:03:55 +0200 Subject: [PATCH 17/17] HWY-85: Fix honest validators weight. Make sure that malicious validators weight is never more than 34% of total weight due to rounding. --- .../src/components/consensus/highway_core/highway_testing.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/node/src/components/consensus/highway_core/highway_testing.rs b/node/src/components/consensus/highway_core/highway_testing.rs index 5192f0b978..083b0164c9 100644 --- a/node/src/components/consensus/highway_core/highway_testing.rs +++ b/node/src/components/consensus/highway_core/highway_testing.rs @@ -719,7 +719,8 @@ impl HighwayTestHarnessBuilder { let honest_weights = { let faulty_sum = faulty_weights.iter().sum::(); let mut weights_to_distribute: u64 = - faulty_sum * 100 / self.faulty_percent - faulty_sum; + (faulty_sum * 100 + self.faulty_percent - 1) / self.faulty_percent + - faulty_sum; let mut weights = vec![]; while weights_to_distribute > 0 { let weight = if weights_to_distribute < upper { @@ -924,7 +925,7 @@ mod test_harness { let mut rng = TestRng::new(); let mut highway_test_harness: HighwayTestHarness = HighwayTestHarnessBuilder::new() - .max_faulty_validators(3) + .max_faulty_validators(5) .consensus_values_count(5) .weight_limits(5, 10) .faulty_weight_perc(20)