From c60d1d74125b00fe58635ef5b1079d85da934243 Mon Sep 17 00:00:00 2001 From: Marcin Date: Tue, 14 Jan 2025 13:31:04 +0100 Subject: [PATCH] Added tests in control hash module --- consensus/src/units/control_hash.rs | 168 +++++++++++++++++++++++++++- 1 file changed, 166 insertions(+), 2 deletions(-) diff --git a/consensus/src/units/control_hash.rs b/consensus/src/units/control_hash.rs index cc721ead..c156ac4a 100644 --- a/consensus/src/units/control_hash.rs +++ b/consensus/src/units/control_hash.rs @@ -1,8 +1,16 @@ -use crate::{units::UnitCoord, Hasher, NodeCount, NodeMap, Round}; +use crate::{units::UnitCoord, Hasher, NodeCount, NodeIndex, NodeMap, Round}; use codec::{Decode, Encode}; +#[derive(Debug, PartialEq)] +pub enum Error { + NotDescendantOfPreviousUnit(NodeIndex), + DescendantOfPreviousUnitButWrongRound(Round), + NotEnoughParentsForRound(Round), + ParentsHigherThanRound(Round), +} + /// Combined hashes of the parents of a unit together with the set of indices of creators of the -/// parents +/// parents. By parent here we mean a parent hash and its round. #[derive(Clone, Eq, PartialEq, Hash, Debug, Decode, Encode)] pub struct ControlHash { pub(crate) parents: NodeMap, @@ -10,6 +18,7 @@ pub struct ControlHash { } impl ControlHash { + /// Creates new control hash from parents hashes and rounds pub(crate) fn new(parents_with_rounds_and_hashes: &NodeMap<(H::Hash, Round)>) -> Self { let mut parents_with_rounds = NodeMap::with_size(parents_with_rounds_and_hashes.size()); for (parent_index, (_, parent_round)) in parents_with_rounds_and_hashes.iter() { @@ -26,23 +35,78 @@ impl ControlHash { parent_map.using_encoded(H::hash) } + /// Iterator over non-empty parents - returns [`UnitCoord`]s pub(crate) fn parents(&self) -> impl Iterator + '_ { self.parents .iter() .map(|(node_index, &round)| UnitCoord::new(round, node_index)) } + /// Returns number of non-empty parents pub(crate) fn n_parents(&self) -> NodeCount { NodeCount(self.parents().count()) } + /// Returns number of all members in abft consensus pub(crate) fn n_members(&self) -> NodeCount { self.parents.size() } + + /// Validate + pub fn validate(&self, unit_coord: UnitCoord) -> Result<(), Error> { + assert!(unit_coord.round > 0, "Round must be greater than 0"); + + self.unit_creator_is_descendant_of_previous_unit(unit_coord)?; + self.previous_round_have_enough_parents(unit_coord)?; + self.check_if_parents_greater_than_previous_round(unit_coord)?; + + Ok(()) + } + + fn check_if_parents_greater_than_previous_round( + &self, + unit_coord: UnitCoord, + ) -> Result<(), Error> { + let parents_greater_than_previous_round = self + .parents() + .filter(|&parent| parent.round > unit_coord.round - 1) + .count(); + if parents_greater_than_previous_round > 0 { + return Err(Error::ParentsHigherThanRound(unit_coord.round - 1)); + } + Ok(()) + } + + fn previous_round_have_enough_parents(&self, unit_coord: UnitCoord) -> Result<(), Error> { + let previous_round_parents = self + .parents() + .filter(|&parent| parent.round == unit_coord.round - 1) + .count(); + if previous_round_parents < self.n_members().consensus_threshold().0 { + return Err(Error::NotEnoughParentsForRound(unit_coord.round - 1)); + } + Ok(()) + } + + fn unit_creator_is_descendant_of_previous_unit( + &self, + unit_coord: UnitCoord, + ) -> Result<(), Error> { + match self.parents.get(unit_coord.creator) { + None => return Err(Error::NotDescendantOfPreviousUnit(unit_coord.creator)), + Some(&parent_round) => { + if unit_coord.round - 1 != parent_round { + return Err(Error::DescendantOfPreviousUnitButWrongRound(parent_round)); + } + } + } + Ok(()) + } } #[cfg(test)] pub mod tests { + use crate::units::control_hash::Error; use crate::units::UnitCoord; use crate::{units::ControlHash, NodeCount, NodeIndex}; use aleph_bft_mock::Hasher64; @@ -90,4 +154,104 @@ pub mod tests { ]; assert_eq!(parents, expected_parents); } + + #[test] + fn given_control_hash_when_validate_with_correct_unit_coord_then_validate_is_ok() { + let parent_map = vec![ + Some(([0; 8], 2)), + None, + Some(([2; 8], 2)), + Some(([3; 8], 2)), + Some(([4; 8], 2)), + Some(([5; 8], 2)), + Some(([5; 8], 1)), + ] + .into(); + let ch = ControlHash::::new(&parent_map); + assert!(ch.validate(UnitCoord::new(3, NodeIndex(2))).is_ok()); + } + + #[test] + #[should_panic(expected = "Round must be greater than 0")] + fn given_control_hash_when_validate_called_for_initial_unit_then_validate_panics() { + let parent_map = vec![ + Some(([0; 8], 2)), + None, + Some(([2; 8], 2)), + Some(([3; 8], 2)), + ] + .into(); + let ch = ControlHash::::new(&parent_map); + let _ = ch.validate(UnitCoord::new(0, NodeIndex(1))); + } + #[test] + fn given_control_hash_when_creator_parent_does_not_exist_then_err_is_returned_from_validate() { + let parent_map = vec![ + Some(([0; 8], 2)), + None, + Some(([2; 8], 2)), + Some(([3; 8], 2)), + ] + .into(); + let ch = ControlHash::::new(&parent_map); + assert_eq!( + ch.validate(UnitCoord::new(3, NodeIndex(1))) + .expect_err("validate() should return error, returned Ok(()) instead"), + Error::NotDescendantOfPreviousUnit(NodeIndex(1)) + ); + } + + #[test] + fn given_control_hash_when_creator_parent_exists_but_has_wrong_round_then_err_is_returned_from_validate( + ) { + let parent_map = vec![ + Some(([0; 8], 2)), + Some(([1; 8], 1)), + Some(([2; 8], 2)), + Some(([3; 8], 2)), + ] + .into(); + let ch = ControlHash::::new(&parent_map); + assert_eq!( + ch.validate(UnitCoord::new(3, NodeIndex(1))) + .expect_err("validate() should return error, returned Ok(()) instead"), + Error::DescendantOfPreviousUnitButWrongRound(1) + ); + } + + #[test] + fn given_control_hash_when_there_are_not_enough_previous_round_parents_then_err_is_returned_from_validate( + ) { + let parent_map = vec![ + None, + Some(([1; 8], 2)), + Some(([2; 8], 2)), + Some(([3; 8], 1)), + ] + .into(); + let ch = ControlHash::::new(&parent_map); + assert_eq!( + ch.validate(UnitCoord::new(3, NodeIndex(1))) + .expect_err("validate() should return error, returned Ok(()) instead"), + Error::NotEnoughParentsForRound(2) + ); + } + + #[test] + fn given_control_hash_when_there_are_parents_from_greater_rounds_then_err_is_returned_from_validate( + ) { + let parent_map = vec![ + Some(([0; 8], 2)), + Some(([1; 8], 2)), + Some(([2; 8], 2)), + Some(([3; 8], 3)), + ] + .into(); + let ch = ControlHash::::new(&parent_map); + assert_eq!( + ch.validate(UnitCoord::new(3, NodeIndex(1))) + .expect_err("validate() should return error, returned Ok(()) instead"), + Error::ParentsHigherThanRound(2) + ); + } }