Skip to content

Commit

Permalink
Create units with ancient parents
Browse files Browse the repository at this point in the history
  • Loading branch information
timorleph committed Jan 22, 2025
1 parent 1a35839 commit c032bb4
Show file tree
Hide file tree
Showing 3 changed files with 418 additions and 139 deletions.
342 changes: 342 additions & 0 deletions consensus/src/creation/collector.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,342 @@
use crate::{units::Unit, Hasher, NodeCount, NodeIndex, NodeMap, Round};
use anyhow::Result;
use thiserror::Error;

#[derive(Eq, Error, Debug, PartialEq)]
pub enum ConstraintError {
#[error("Not enough parents.")]
NotEnoughParents,
#[error("Missing own parent.")]
MissingOwnParent,
}

#[derive(Clone)]
pub struct UnitsCollector<H: Hasher> {
candidates: NodeMap<(H::Hash, Round)>,
for_round: Round,
direct_parents: NodeCount,
}

impl<H: Hasher> UnitsCollector<H> {
pub fn new_initial(n_members: NodeCount) -> Self {
UnitsCollector {
candidates: NodeMap::with_size(n_members),
for_round: 1,
direct_parents: NodeCount(0),
}
}

pub fn from_previous(previous: &UnitsCollector<H>) -> Self {
UnitsCollector {
candidates: previous.candidates.clone(),
for_round: previous.for_round + 1,
direct_parents: NodeCount(0),
}
}

pub fn add_unit<U: Unit<Hasher = H>>(&mut self, unit: &U) {
let node_id = unit.creator();
let hash = unit.hash();
let round = unit.round();

if round >= self.for_round {
return;
}

let to_insert = match self.candidates.get(node_id) {
None => Some((hash, round)),
Some((_, r)) if *r < round => Some((hash, round)),
_ => None,
};

if let Some(data) = to_insert {
self.candidates.insert(node_id, data);
if round == self.for_round - 1 {
self.direct_parents += NodeCount(1);
}
}
}

pub fn prospective_parents(
&self,
node_id: NodeIndex,
) -> Result<&NodeMap<(H::Hash, Round)>, ConstraintError> {
if self.direct_parents < self.candidates.size().consensus_threshold() {
return Err(ConstraintError::NotEnoughParents);
}
match self.candidates.get(node_id) {
Some((_, r)) if *r == self.for_round - 1 => Ok(&self.candidates),
_ => Err(ConstraintError::MissingOwnParent),
}
}
}

#[cfg(test)]
mod tests {
use crate::{
creation::collector::{ConstraintError, UnitsCollector},
units::{random_full_parent_units_up_to, Unit},
NodeCount, NodeIndex,
};
use aleph_bft_mock::Hasher64;

#[test]
fn initial_fails_without_parents() {
let n_members = NodeCount(4);
let units_collector = UnitsCollector::<Hasher64>::new_initial(n_members);

let err = units_collector
.prospective_parents(NodeIndex(0))
.expect_err("should fail without parents");
assert_eq!(err, ConstraintError::NotEnoughParents);
}

#[test]
fn initial_fails_with_too_few_parents() {
let n_members = NodeCount(4);
let mut units_collector = UnitsCollector::new_initial(n_members);
let units = random_full_parent_units_up_to(0, n_members, 43);
units_collector.add_unit(&units[0][0]);

let err = units_collector
.prospective_parents(NodeIndex(0))
.expect_err("should fail without parents");
assert_eq!(err, ConstraintError::NotEnoughParents);
}

#[test]
fn initial_fails_without_own_parent() {
let n_members = NodeCount(4);
let mut units_collector = UnitsCollector::new_initial(n_members);
let units = random_full_parent_units_up_to(0, n_members, 43);
for unit in units[0].iter().skip(1) {
units_collector.add_unit(unit);
}

let err = units_collector
.prospective_parents(NodeIndex(0))
.expect_err("should fail without parents");
assert_eq!(err, ConstraintError::MissingOwnParent);
}

#[test]
fn initial_successfully_computes_minimal_parents() {
let n_members = NodeCount(4);
let mut units_collector = UnitsCollector::new_initial(n_members);
let units = random_full_parent_units_up_to(0, n_members, 43);
for unit in units[0].iter().take(3) {
units_collector.add_unit(unit);
}

let parents = units_collector
.prospective_parents(NodeIndex(0))
.expect("we should be able to retrieve parents");
assert_eq!(parents.item_count(), 3);

let new_units: Vec<_> = units[0]
.iter()
.take(3)
.map(|unit| (unit.hash(), unit.round()))
.collect();
let selected_parents: Vec<_> = parents.values().cloned().collect();
assert_eq!(new_units, selected_parents);
}

#[test]
fn initial_successfully_computes_full_parents() {
let n_members = NodeCount(4);
let mut units_collector = UnitsCollector::new_initial(n_members);
let units = random_full_parent_units_up_to(0, n_members, 43);
for unit in &units[0] {
units_collector.add_unit(unit);
}

let parents = units_collector
.prospective_parents(NodeIndex(0))
.expect("we should be able to retrieve parents");
assert_eq!(parents.item_count(), 4);

let new_units: Vec<_> = units[0]
.iter()
.map(|unit| (unit.hash(), unit.round()))
.collect();
let selected_parents: Vec<_> = parents.values().cloned().collect();
assert_eq!(new_units, selected_parents);
}

#[test]
fn following_fails_without_parents() {
let n_members = NodeCount(4);
let initial_units_collector = UnitsCollector::<Hasher64>::new_initial(n_members);
let units_collector = UnitsCollector::from_previous(&initial_units_collector);

let err = units_collector
.prospective_parents(NodeIndex(0))
.expect_err("should fail without parents");
assert_eq!(err, ConstraintError::NotEnoughParents);
}

#[test]
fn following_fails_with_too_few_parents() {
let n_members = NodeCount(4);
let initial_units_collector = UnitsCollector::<Hasher64>::new_initial(n_members);
let mut units_collector = UnitsCollector::from_previous(&initial_units_collector);
let units = random_full_parent_units_up_to(1, n_members, 43);
units_collector.add_unit(&units[1][0]);

let err = units_collector
.prospective_parents(NodeIndex(0))
.expect_err("should fail without parents");
assert_eq!(err, ConstraintError::NotEnoughParents);
}

#[test]
fn following_fails_with_too_old_parents() {
let n_members = NodeCount(4);
let initial_units_collector = UnitsCollector::<Hasher64>::new_initial(n_members);
let mut units_collector = UnitsCollector::from_previous(&initial_units_collector);
let units = random_full_parent_units_up_to(0, n_members, 43);
for unit in &units[0] {
units_collector.add_unit(unit);
}

let err = units_collector
.prospective_parents(NodeIndex(0))
.expect_err("should fail without parents");
assert_eq!(err, ConstraintError::NotEnoughParents);
}

#[test]
fn following_fails_without_own_parent() {
let n_members = NodeCount(4);
let initial_units_collector = UnitsCollector::<Hasher64>::new_initial(n_members);
let mut units_collector = UnitsCollector::from_previous(&initial_units_collector);
let units = random_full_parent_units_up_to(1, n_members, 43);
for unit in units[1].iter().skip(1) {
units_collector.add_unit(unit);
}

let err = units_collector
.prospective_parents(NodeIndex(0))
.expect_err("should fail without parents");
assert_eq!(err, ConstraintError::MissingOwnParent);
}

#[test]
fn following_fails_with_too_old_own_parent() {
let n_members = NodeCount(4);
let initial_units_collector = UnitsCollector::<Hasher64>::new_initial(n_members);
let mut units_collector = UnitsCollector::from_previous(&initial_units_collector);
let units = random_full_parent_units_up_to(1, n_members, 43);
for unit in units[1].iter().skip(1) {
units_collector.add_unit(unit);
}
units_collector.add_unit(&units[0][0]);

let err = units_collector
.prospective_parents(NodeIndex(0))
.expect_err("should fail without parents");
assert_eq!(err, ConstraintError::MissingOwnParent);
}

#[test]
fn following_successfully_computes_minimal_parents() {
let n_members = NodeCount(4);
let initial_units_collector = UnitsCollector::<Hasher64>::new_initial(n_members);
let mut units_collector = UnitsCollector::from_previous(&initial_units_collector);
let units = random_full_parent_units_up_to(1, n_members, 43);
for unit in units[1].iter().take(3) {
units_collector.add_unit(unit);
}

let parents = units_collector
.prospective_parents(NodeIndex(0))
.expect("we should be able to retrieve parents");
assert_eq!(parents.item_count(), 3);

let new_units: Vec<_> = units[1]
.iter()
.take(3)
.map(|unit| (unit.hash(), unit.round()))
.collect();
let selected_parents: Vec<_> = parents.values().cloned().collect();
assert_eq!(new_units, selected_parents);
}

#[test]
fn following_successfully_computes_minimal_parents_with_ancient() {
let n_members = NodeCount(4);
let initial_units_collector = UnitsCollector::<Hasher64>::new_initial(n_members);
let mut units_collector = UnitsCollector::from_previous(&initial_units_collector);
let units = random_full_parent_units_up_to(1, n_members, 43);
for unit in units[1].iter().take(3) {
units_collector.add_unit(unit);
}
units_collector.add_unit(&units[0][3]);

let parents = units_collector
.prospective_parents(NodeIndex(0))
.expect("we should be able to retrieve parents");
assert_eq!(parents.item_count(), 4);

let mut new_units: Vec<_> = units[1]
.iter()
.take(3)
.map(|unit| (unit.hash(), unit.round()))
.collect();
new_units.push((units[0][3].hash(), units[0][3].round()));
let selected_parents: Vec<_> = parents.values().cloned().collect();
assert_eq!(new_units, selected_parents);
}

#[test]
fn following_successfully_computes_full_parents() {
let n_members = NodeCount(4);
let initial_units_collector = UnitsCollector::<Hasher64>::new_initial(n_members);
let mut units_collector = UnitsCollector::from_previous(&initial_units_collector);
let units = random_full_parent_units_up_to(1, n_members, 43);
for unit in &units[1] {
units_collector.add_unit(unit);
}

let parents = units_collector
.prospective_parents(NodeIndex(0))
.expect("we should be able to retrieve parents");
assert_eq!(parents.item_count(), 4);

let new_units: Vec<_> = units[1]
.iter()
.map(|unit| (unit.hash(), unit.round()))
.collect();
let selected_parents: Vec<_> = parents.values().cloned().collect();
assert_eq!(new_units, selected_parents);
}

#[test]
fn following_inherits_units() {
let n_members = NodeCount(4);
let mut initial_units_collector = UnitsCollector::<Hasher64>::new_initial(n_members);
let units = random_full_parent_units_up_to(1, n_members, 43);
for unit in &units[0] {
initial_units_collector.add_unit(unit);
}
let mut units_collector = UnitsCollector::from_previous(&initial_units_collector);
for unit in units[1].iter().take(3) {
units_collector.add_unit(unit);
}

let parents = units_collector
.prospective_parents(NodeIndex(0))
.expect("we should be able to retrieve parents");
assert_eq!(parents.item_count(), 4);

let mut new_units: Vec<_> = units[1]
.iter()
.take(3)
.map(|unit| (unit.hash(), unit.round()))
.collect();
new_units.push((units[0][3].hash(), units[0][3].round()));
let selected_parents: Vec<_> = parents.values().cloned().collect();
assert_eq!(new_units, selected_parents);
}
}
Loading

0 comments on commit c032bb4

Please sign in to comment.