Skip to content

Commit

Permalink
Added tests in control hash module
Browse files Browse the repository at this point in the history
  • Loading branch information
Marcin-Radecki committed Jan 14, 2025
1 parent 9e7d2ce commit c60d1d7
Showing 1 changed file with 166 additions and 2 deletions.
168 changes: 166 additions & 2 deletions consensus/src/units/control_hash.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
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<H: Hasher> {
pub(crate) parents: NodeMap<Round>,
pub(crate) combined_hash: H::Hash,
}

impl<H: Hasher> ControlHash<H> {
/// 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() {
Expand All @@ -26,23 +35,78 @@ impl<H: Hasher> ControlHash<H> {
parent_map.using_encoded(H::hash)
}

/// Iterator over non-empty parents - returns [`UnitCoord`]s
pub(crate) fn parents(&self) -> impl Iterator<Item = UnitCoord> + '_ {
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;
Expand Down Expand Up @@ -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::<Hasher64>::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::<Hasher64>::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::<Hasher64>::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::<Hasher64>::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::<Hasher64>::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::<Hasher64>::new(&parent_map);
assert_eq!(
ch.validate(UnitCoord::new(3, NodeIndex(1)))
.expect_err("validate() should return error, returned Ok(()) instead"),
Error::ParentsHigherThanRound(2)
);
}
}

0 comments on commit c60d1d7

Please sign in to comment.