diff --git a/finality-aleph/src/sync/forest/mod.rs b/finality-aleph/src/sync/forest/mod.rs new file mode 100644 index 0000000000..1f457fbe1b --- /dev/null +++ b/finality-aleph/src/sync/forest/mod.rs @@ -0,0 +1,721 @@ +use std::collections::{ + hash_map::{Entry, OccupiedEntry, VacantEntry}, + HashMap, HashSet, +}; + +use crate::sync::{BlockIdentifier, Header, Justification, PeerId}; + +mod vertex; + +use vertex::{JustificationAddResult, Vertex}; + +type BlockIdFor = <::Header as Header>::Identifier; + +pub struct JustificationWithParent { + pub justification: J, + pub parent: BlockIdFor, +} + +enum VertexHandle<'a, I: PeerId, J: Justification> { + HopelessFork, + BelowMinimal, + HighestFinalized, + Unknown(VacantEntry<'a, BlockIdFor, VertexWithChildren>), + Candidate(OccupiedEntry<'a, BlockIdFor, VertexWithChildren>), +} + +/// Our interest in a block referred to by a vertex, including the information about whom we expect to have the block. +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum Interest { + /// We are not interested in this block. + Uninterested, + /// We would like to have this block. + Required(HashSet), + /// We would like to have this block and its the highest on its branch. + TopRequired(HashSet), +} + +/// What can go wrong when inserting data into the forest. +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum Error { + HeaderMissingParentId, + IncorrectParentState, + IncorrectVertexState, + ParentNotImported, + TooNew, +} + +pub struct VertexWithChildren { + vertex: Vertex, + children: HashSet>, +} + +impl VertexWithChildren { + fn new() -> Self { + Self { + vertex: Vertex::new(), + children: HashSet::new(), + } + } + + fn add_child(&mut self, child: BlockIdFor) { + self.children.insert(child); + } +} + +// How deep can the forest be, vaguely based on two sessions ahead, which is the most we expect to +// ever need worst case scenario. +const MAX_DEPTH: u32 = 1800; + +pub struct Forest { + vertices: HashMap, VertexWithChildren>, + top_required: HashSet>, + root_id: BlockIdFor, + root_children: HashSet>, + compost_bin: HashSet>, +} + +impl Forest { + pub fn new(highest_justified: BlockIdFor) -> Self { + Self { + vertices: HashMap::new(), + top_required: HashSet::new(), + root_id: highest_justified, + root_children: HashSet::new(), + compost_bin: HashSet::new(), + } + } + + fn get_mut(&mut self, id: &BlockIdFor) -> VertexHandle { + use VertexHandle::*; + if id == &self.root_id { + HighestFinalized + } else if id.number() <= self.root_id.number() { + BelowMinimal + } else if self.compost_bin.contains(id) { + HopelessFork + } else { + match self.vertices.entry(id.clone()) { + Entry::Occupied(entry) => Candidate(entry), + Entry::Vacant(entry) => Unknown(entry), + } + } + } + + fn prune(&mut self, id: &BlockIdFor) { + self.top_required.remove(id); + if let Some(VertexWithChildren { children, .. }) = self.vertices.remove(id) { + self.compost_bin.insert(id.clone()); + for child in children { + self.prune(&child); + } + } + } + + fn connect_parent(&mut self, id: &BlockIdFor) { + use VertexHandle::*; + if let Candidate(mut entry) = self.get_mut(id) { + let vertex = entry.get_mut(); + let required = vertex.vertex.required(); + if let Some(parent_id) = vertex.vertex.parent().cloned() { + match self.get_mut(&parent_id) { + Unknown(entry) => { + entry + .insert(VertexWithChildren::new()) + .add_child(id.clone()); + if required { + self.set_required(&parent_id); + } + } + HighestFinalized => { + self.root_children.insert(id.clone()); + } + Candidate(mut entry) => { + entry.get_mut().add_child(id.clone()); + if required { + self.set_required(&parent_id); + } + } + HopelessFork | BelowMinimal => self.prune(id), + }; + }; + }; + } + + fn set_required(&mut self, id: &BlockIdFor) { + self.top_required.remove(id); + if let VertexHandle::Candidate(mut entry) = self.get_mut(id) { + let vertex = entry.get_mut(); + if vertex.vertex.set_required() { + if let Some(id) = vertex.vertex.parent().cloned() { + self.set_required(&id); + } + } + } + } + + fn set_top_required(&mut self, id: &BlockIdFor) -> bool { + match self.get_mut(id) { + VertexHandle::Candidate(mut entry) => match entry.get_mut().vertex.set_required() { + true => { + if let Some(parent_id) = entry.get_mut().vertex.parent().cloned() { + self.set_required(&parent_id); + } + self.top_required.insert(id.clone()); + true + } + false => false, + }, + _ => false, + } + } + + fn insert_id(&mut self, id: BlockIdFor, holder: Option) -> Result<(), Error> { + if id.number() > self.root_id.number() + MAX_DEPTH { + return Err(Error::TooNew); + } + self.vertices + .entry(id) + .or_insert_with(VertexWithChildren::new) + .vertex + .add_block_holder(holder); + Ok(()) + } + + fn process_header( + &mut self, + header: &J::Header, + ) -> Result<(BlockIdFor, BlockIdFor), Error> { + Ok(( + header.id(), + header.parent_id().ok_or(Error::HeaderMissingParentId)?, + )) + } + + /// Updates the provider block identifier, returns whether it became a new top required. + pub fn update_block_identifier( + &mut self, + id: &BlockIdFor, + holder: Option, + required: bool, + ) -> Result { + self.insert_id(id.clone(), holder)?; + match required { + true => Ok(self.set_top_required(id)), + false => Ok(false), + } + } + + /// Updates the provided header, returns whether it became a new top required. + pub fn update_header( + &mut self, + header: &J::Header, + holder: Option, + required: bool, + ) -> Result { + let (id, parent_id) = self.process_header(header)?; + self.insert_id(id.clone(), holder.clone())?; + if let VertexHandle::Candidate(mut entry) = self.get_mut(&id) { + entry.get_mut().vertex.insert_header(parent_id, holder); + self.connect_parent(&id); + } + match required { + true => Ok(self.set_top_required(&id)), + false => Ok(false), + } + } + + /// Updates the vertex related to the provided header marking it as imported. Returns whether + /// it is now finalizable, or errors when it's impossible to do consistently. + pub fn update_body(&mut self, header: &J::Header) -> Result { + use VertexHandle::*; + let (id, parent_id) = self.process_header(header)?; + self.update_header(header, None, false)?; + match self.get_mut(&parent_id) { + Candidate(entry) => { + if !entry.get().vertex.imported() { + return Err(Error::ParentNotImported); + } + } + HighestFinalized => (), + Unknown(_) | HopelessFork | BelowMinimal => return Err(Error::IncorrectParentState), + } + match self.get_mut(&id) { + Candidate(mut entry) => Ok(entry.get_mut().vertex.insert_body(parent_id.clone())), + _ => Err(Error::IncorrectVertexState), + } + } + + /// Updates the provided justification, returns whether either finalization is now possible or + /// the vertex became a new top required. + pub fn update_justification( + &mut self, + justification: J, + holder: Option, + ) -> Result { + use JustificationAddResult::*; + let (id, parent_id) = self.process_header(justification.header())?; + self.update_header(justification.header(), None, false)?; + match self.get_mut(&id) { + VertexHandle::Candidate(mut entry) => { + match entry.get_mut().vertex.insert_justification( + parent_id.clone(), + justification, + holder, + ) { + Noop => Ok(Noop), + Required => { + self.top_required.insert(id.clone()); + self.set_required(&parent_id); + Ok(Required) + } + Finalizable => { + self.top_required.remove(&id); + Ok(Finalizable) + } + } + } + _ => Ok(Noop), + } + } + + fn prune_level(&mut self, level: u32) { + let to_prune: Vec<_> = self + .vertices + .keys() + .filter(|k| k.number() <= level) + .cloned() + .collect(); + for id in to_prune.into_iter() { + self.prune(&id); + } + self.compost_bin.retain(|k| k.number() > level); + } + + /// Attempt to finalize one block, returns the correct justification if successful. + pub fn try_finalize(&mut self) -> Option { + for child_id in self.root_children.clone().into_iter() { + if let Some(VertexWithChildren { vertex, children }) = self.vertices.remove(&child_id) { + match vertex.ready() { + Ok(justification) => { + self.root_id = child_id; + self.root_children = children; + self.prune_level(self.root_id.number()); + return Some(justification); + } + Err(vertex) => { + self.vertices + .insert(child_id, VertexWithChildren { vertex, children }); + } + } + } + } + None + } + + /// How much interest we have for the block. + pub fn state(&mut self, id: &BlockIdFor) -> Interest { + match self.get_mut(id) { + VertexHandle::Candidate(entry) => { + let vertex = &entry.get().vertex; + let know_most = vertex.know_most().clone(); + match vertex.required() { + true => match self.top_required.contains(id) { + true => Interest::TopRequired(know_most), + false => Interest::Required(know_most), + }, + false => Interest::Uninterested, + } + } + _ => Interest::Uninterested, + } + } +} + +#[cfg(test)] +mod tests { + use super::{Error, Forest, Interest::*, JustificationAddResult, MAX_DEPTH}; + use crate::sync::{ + mock::{MockHeader, MockJustification, MockPeerId}, + Header, Justification, + }; + + type MockForest = Forest; + + fn setup() -> (MockHeader, MockForest) { + let header = MockHeader::random_parentless(0); + let forest = Forest::new(header.id()); + (header, forest) + } + + #[test] + fn initially_empty() { + let (initial_header, mut forest) = setup(); + assert!(forest.try_finalize().is_none()); + assert_eq!(forest.state(&initial_header.id()), Uninterested); + } + + #[test] + fn accepts_first_unimportant_id() { + let (initial_header, mut forest) = setup(); + let child = initial_header.random_child(); + let peer_id = rand::random(); + assert!(!forest + .update_block_identifier(&child.id(), Some(peer_id), false) + .expect("it's not too high")); + assert!(forest.try_finalize().is_none()); + assert_eq!(forest.state(&child.id()), Uninterested); + } + + #[test] + fn accepts_first_important_id() { + let (initial_header, mut forest) = setup(); + let child = initial_header.random_child(); + let peer_id = rand::random(); + assert!(forest + .update_block_identifier(&child.id(), Some(peer_id), true) + .expect("it's not too high")); + assert!(forest.try_finalize().is_none()); + match forest.state(&child.id()) { + TopRequired(holders) => assert!(holders.contains(&peer_id)), + other_state => panic!("Expected top required, got {:?}.", other_state), + } + assert!(!forest + .update_block_identifier(&child.id(), Some(peer_id), true) + .expect("it's not too high")); + } + + #[test] + fn rejects_too_high_id() { + let (initial_header, mut forest) = setup(); + let too_high = initial_header + .random_branch() + .nth(MAX_DEPTH as usize) + .expect("the branch is infinite"); + let peer_id = rand::random(); + assert_eq!( + forest.update_block_identifier(&too_high.id(), Some(peer_id), true), + Err(Error::TooNew) + ); + } + + #[test] + fn accepts_first_unimportant_header() { + let (initial_header, mut forest) = setup(); + let child = initial_header.random_child(); + let peer_id = rand::random(); + assert!(!forest + .update_header(&child, Some(peer_id), false) + .expect("header was correct")); + assert!(forest.try_finalize().is_none()); + assert_eq!(forest.state(&child.id()), Uninterested); + } + + #[test] + fn accepts_first_important_header() { + let (initial_header, mut forest) = setup(); + let child = initial_header.random_child(); + let peer_id = rand::random(); + assert!(forest + .update_header(&child, Some(peer_id), true) + .expect("header was correct")); + assert!(forest.try_finalize().is_none()); + match forest.state(&child.id()) { + TopRequired(holders) => assert!(holders.contains(&peer_id)), + other_state => panic!("Expected top required, got {:?}.", other_state), + } + assert!(!forest + .update_block_identifier(&child.id(), Some(peer_id), true) + .expect("it's not too high")); + } + + #[test] + fn rejects_parentless_header() { + let (_, mut forest) = setup(); + let parentless = MockHeader::random_parentless(43); + let peer_id = rand::random(); + assert!(matches!( + forest.update_header(&parentless, Some(peer_id), true), + Err(Error::HeaderMissingParentId) + )); + } + + #[test] + fn accepts_first_justification() { + let (initial_header, mut forest) = setup(); + let child = MockJustification::for_header(initial_header.random_child()); + let peer_id = rand::random(); + assert_eq!( + forest + .update_justification(child.clone(), Some(peer_id)) + .expect("header was correct"), + JustificationAddResult::Required + ); + assert!(forest.try_finalize().is_none()); + match forest.state(&child.header().id()) { + TopRequired(holders) => assert!(holders.contains(&peer_id)), + other_state => panic!("Expected top required, got {:?}.", other_state), + } + } + + #[test] + fn rejects_parentless_justification() { + let (_, mut forest) = setup(); + let parentless = MockJustification::for_header(MockHeader::random_parentless(43)); + let peer_id = rand::random(); + assert!(matches!( + forest.update_justification(parentless, Some(peer_id)), + Err(Error::HeaderMissingParentId) + )); + } + + #[test] + fn accepts_first_body() { + let (initial_header, mut forest) = setup(); + let child = initial_header.random_child(); + assert!(!forest.update_body(&child).expect("header was correct")); + assert!(forest.try_finalize().is_none()); + assert_eq!(forest.state(&child.id()), Uninterested); + } + + #[test] + fn rejects_body_when_parent_unimported() { + let (initial_header, mut forest) = setup(); + let child = initial_header.random_child(); + let grandchild = child.random_child(); + assert!(!forest + .update_header(&child, None, false) + .expect("header was correct")); + assert_eq!( + forest.update_body(&grandchild), + Err(Error::ParentNotImported) + ); + assert!(forest.try_finalize().is_none()); + assert_eq!(forest.state(&child.id()), Uninterested); + assert_eq!(forest.state(&grandchild.id()), Uninterested); + } + + #[test] + fn finalizes_first_block() { + let (initial_header, mut forest) = setup(); + let child = MockJustification::for_header(initial_header.random_child()); + let peer_id = rand::random(); + assert_eq!( + forest + .update_justification(child.clone(), Some(peer_id)) + .expect("header was correct"), + JustificationAddResult::Required + ); + assert!(forest.try_finalize().is_none()); + match forest.state(&child.header().id()) { + TopRequired(holders) => assert!(holders.contains(&peer_id)), + other_state => panic!("Expected top required, got {:?}.", other_state), + } + assert!(forest + .update_body(child.header()) + .expect("header was correct")); + assert_eq!(forest.try_finalize().expect("the block is ready"), child); + } + + #[test] + fn prunes_forks() { + let (initial_header, mut forest) = setup(); + let child = MockJustification::for_header(initial_header.random_child()); + let fork_child = initial_header.random_child(); + let peer_id = rand::random(); + let fork_peer_id = rand::random(); + assert!(forest + .update_header(&fork_child, Some(fork_peer_id), true) + .expect("header was correct")); + match forest.state(&fork_child.id()) { + TopRequired(holders) => assert!(holders.contains(&fork_peer_id)), + other_state => panic!("Expected top required, got {:?}.", other_state), + } + assert_eq!( + forest + .update_justification(child.clone(), Some(peer_id)) + .expect("header was correct"), + JustificationAddResult::Required + ); + assert!(forest + .update_body(child.header()) + .expect("header was correct")); + assert_eq!(forest.try_finalize().expect("the block is ready"), child); + assert_eq!(forest.state(&fork_child.id()), Uninterested); + assert!(!forest + .update_header(&fork_child, Some(fork_peer_id), true) + .expect("header was correct")); + } + + #[test] + fn uninterested_in_forks() { + let (initial_header, mut forest) = setup(); + let fork_branch: Vec<_> = initial_header.random_branch().take(2).collect(); + for header in &fork_branch { + let peer_id = rand::random(); + assert!(forest + .update_header(header, Some(peer_id), true) + .expect("header was correct")); + match forest.state(&header.id()) { + TopRequired(holders) => assert!(holders.contains(&peer_id)), + other_state => panic!("Expected top required, got {:?}.", other_state), + } + } + let child = MockJustification::for_header(initial_header.random_child()); + let peer_id = rand::random(); + assert_eq!( + forest + .update_justification(child.clone(), Some(peer_id)) + .expect("header was correct"), + JustificationAddResult::Required + ); + assert!(forest + .update_body(child.header()) + .expect("header was correct")); + assert_eq!(forest.try_finalize().expect("the block is ready"), child); + for header in fork_branch { + assert_eq!(forest.state(&header.id()), Uninterested); + } + } + + #[test] + fn updates_interest_on_parent_connect() { + let (initial_header, mut forest) = setup(); + let branch: Vec<_> = initial_header.random_branch().take(4).collect(); + let header = &branch[0]; + let peer_id = rand::random(); + assert!(forest + .update_header(header, Some(peer_id), true) + .expect("header was correct")); + match forest.state(&header.id()) { + TopRequired(holders) => assert!(holders.contains(&peer_id)), + other_state => panic!("Expected top required, got {:?}.", other_state), + } + let header = &branch[1]; + let peer_id = rand::random(); + assert!(!forest + .update_header(header, Some(peer_id), false) + .expect("header was correct")); + assert_eq!(forest.state(&header.id()), Uninterested); + let header = &branch[3]; + let peer_id = rand::random(); + assert!(forest + .update_header(header, Some(peer_id), true) + .expect("header was correct")); + match forest.state(&header.id()) { + TopRequired(holders) => assert!(holders.contains(&peer_id)), + other_state => panic!("Expected top required, got {:?}.", other_state), + } + let header = &branch[2]; + let peer_id = rand::random(); + assert!(!forest + .update_header(header, Some(peer_id), false) + .expect("header was correct")); + for header in branch.iter().take(3) { + assert!(matches!(forest.state(&header.id()), Required(_))); + } + assert!(matches!(forest.state(&branch[3].id()), TopRequired(_))); + } + + const HUGE_BRANCH_LENGTH: usize = MAX_DEPTH as usize; + + #[test] + fn finalizes_huge_branch() { + let (initial_header, mut forest) = setup(); + let justifications: Vec<_> = initial_header + .random_branch() + .map(MockJustification::for_header) + .take(HUGE_BRANCH_LENGTH) + .collect(); + for justification in &justifications { + let peer_id = rand::random(); + assert_eq!( + forest + .update_justification(justification.clone(), Some(peer_id)) + .expect("header was correct"), + JustificationAddResult::Required + ); + match forest.state(&justification.header().id()) { + TopRequired(holders) => assert!(holders.contains(&peer_id)), + other_state => panic!("Expected top required, got {:?}.", other_state), + } + assert!(forest + .update_body(justification.header()) + .expect("header was correct")); + } + for justification in justifications { + assert_eq!( + forest.try_finalize().expect("the block is ready"), + justification + ); + } + } + + #[test] + fn prunes_huge_branch() { + let (initial_header, mut forest) = setup(); + let fork: Vec<_> = initial_header + .random_branch() + .take(HUGE_BRANCH_LENGTH) + .collect(); + for header in &fork { + let peer_id = rand::random(); + assert!(forest + .update_header(header, Some(peer_id), true) + .expect("header was correct")); + assert!(forest.try_finalize().is_none()); + match forest.state(&header.id()) { + TopRequired(holders) => assert!(holders.contains(&peer_id)), + other_state => panic!("Expected top required, got {:?}.", other_state), + } + } + let child = MockJustification::for_header(initial_header.random_child()); + let peer_id = rand::random(); + assert_eq!( + forest + .update_justification(child.clone(), Some(peer_id)) + .expect("header was correct"), + JustificationAddResult::Required + ); + assert!(forest.try_finalize().is_none()); + match forest.state(&child.header().id()) { + TopRequired(holders) => assert!(holders.contains(&peer_id)), + other_state => panic!("Expected top required, got {:?}.", other_state), + } + assert!(forest + .update_body(child.header()) + .expect("header was correct")); + assert_eq!(forest.try_finalize().expect("the block is ready"), child); + for header in &fork { + assert_eq!(forest.state(&header.id()), Uninterested); + } + } + + #[test] + fn updates_interest_on_huge_branch() { + let (initial_header, mut forest) = setup(); + let branch: Vec<_> = initial_header + .random_branch() + .take(HUGE_BRANCH_LENGTH) + .collect(); + for header in branch.iter().take(HUGE_BRANCH_LENGTH - 1) { + let peer_id = rand::random(); + assert!(!forest + .update_header(header, Some(peer_id), false) + .expect("header was correct")); + assert_eq!(forest.state(&header.id()), Uninterested); + } + let header = &branch[HUGE_BRANCH_LENGTH - 1]; + let peer_id = rand::random(); + assert!(forest + .update_header(header, Some(peer_id), true) + .expect("header was correct")); + match forest.state(&header.id()) { + TopRequired(holders) => assert!(holders.contains(&peer_id)), + other_state => panic!("Expected top required, got {:?}.", other_state), + } + for header in branch.iter().take(HUGE_BRANCH_LENGTH - 1) { + assert!(matches!(forest.state(&header.id()), Required(_))); + } + } +} diff --git a/finality-aleph/src/sync/forest/vertex.rs b/finality-aleph/src/sync/forest/vertex.rs new file mode 100644 index 0000000000..ce4efa3b5d --- /dev/null +++ b/finality-aleph/src/sync/forest/vertex.rs @@ -0,0 +1,474 @@ +use std::collections::HashSet; + +use crate::sync::{forest::BlockIdFor, Justification, PeerId}; + +#[derive(Clone, Debug, Copy, PartialEq, Eq)] +enum HeaderImportance { + Auxiliary, + Required, + Imported, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +enum InnerVertex { + /// Empty Vertex. + Empty { required: bool }, + /// Vertex with added Header. + Header { + importance: HeaderImportance, + parent: BlockIdFor, + }, + /// Vertex with added Header and Justification. + Justification { + imported: bool, + justification: J, + parent: BlockIdFor, + }, +} + +/// The complete vertex, including metadata about peers that know most about the data it refers to. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Vertex { + inner: InnerVertex, + know_most: HashSet, +} + +/// What can happen when we add a justification. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum JustificationAddResult { + Noop, + Required, + Finalizable, +} + +impl Vertex { + /// Create a new empty vertex. + pub fn new() -> Self { + Vertex { + inner: InnerVertex::Empty { required: false }, + know_most: HashSet::new(), + } + } + + /// Whether the vertex is required. + pub fn required(&self) -> bool { + use InnerVertex::*; + matches!( + self.inner, + Empty { required: true } + | Header { + importance: HeaderImportance::Required, + .. + } + | Justification { + imported: false, + .. + } + ) + } + + /// Whether the vertex is imported. + pub fn imported(&self) -> bool { + use InnerVertex::*; + matches!( + self.inner, + Header { + importance: HeaderImportance::Imported, + .. + } | Justification { imported: true, .. } + ) + } + + /// Deconstructs the vertex into a justification if it is ready to be imported, + /// i.e. the related block has already been imported, otherwise returns it. + pub fn ready(self) -> Result { + match self.inner { + InnerVertex::Justification { + imported: true, + justification, + .. + } => Ok(justification), + _ => Err(self), + } + } + + /// The parent of the vertex, if known. + pub fn parent(&self) -> Option<&BlockIdFor> { + match &self.inner { + InnerVertex::Empty { .. } => None, + InnerVertex::Header { parent, .. } => Some(parent), + InnerVertex::Justification { parent, .. } => Some(parent), + } + } + + /// The list of peers which know most about the data this vertex refers to. + pub fn know_most(&self) -> &HashSet { + &self.know_most + } + + /// Set the vertex to be required, returns whether anything changed, i.e. the vertex was not + /// required or imported before. + pub fn set_required(&mut self) -> bool { + use InnerVertex::*; + match &self.inner { + Empty { required: false } => { + self.inner = Empty { required: true }; + true + } + Header { + importance: HeaderImportance::Auxiliary, + parent, + } => { + self.inner = Header { + importance: HeaderImportance::Required, + parent: parent.clone(), + }; + true + } + _ => false, + } + } + + /// Adds a peer that knows most about the block this vertex refers to. Does nothing if we + /// already have a justification. + pub fn add_block_holder(&mut self, holder: Option) { + if let Some(holder) = holder { + if !matches!(self.inner, InnerVertex::Justification { .. }) { + self.know_most.insert(holder); + } + } + } + + /// Adds the information the header provides to the vertex. + pub fn insert_header(&mut self, parent: BlockIdFor, holder: Option) { + self.add_block_holder(holder); + if let InnerVertex::Empty { required } = self.inner { + let importance = match required { + false => HeaderImportance::Auxiliary, + true => HeaderImportance::Required, + }; + self.inner = InnerVertex::Header { importance, parent }; + } + } + + /// Adds the information the header provides to the vertex and marks it as imported. Returns + /// whether finalization is now possible. + pub fn insert_body(&mut self, parent: BlockIdFor) -> bool { + use InnerVertex::*; + match &self.inner { + Empty { .. } | Header { .. } => { + self.inner = Header { + parent, + importance: HeaderImportance::Imported, + }; + false + } + Justification { + imported: false, + parent, + justification, + } => { + self.inner = Justification { + imported: true, + parent: parent.clone(), + justification: justification.clone(), + }; + true + } + _ => false, + } + } + + /// Adds a justification to the vertex. Returns whether either the finalization is now possible + /// or the vertex became required. + pub fn insert_justification( + &mut self, + parent: BlockIdFor, + justification: J, + holder: Option, + ) -> JustificationAddResult { + use InnerVertex::*; + match self.inner { + Justification { .. } => { + if let Some(holder) = holder { + self.know_most.insert(holder); + } + JustificationAddResult::Noop + } + Empty { required: true } + | Header { + importance: HeaderImportance::Required, + .. + } => { + self.inner = Justification { + imported: false, + parent, + justification, + }; + self.know_most = holder.into_iter().collect(); + JustificationAddResult::Noop + } + Empty { required: false } + | Header { + importance: HeaderImportance::Auxiliary, + .. + } => { + self.inner = Justification { + imported: false, + parent, + justification, + }; + self.know_most = holder.into_iter().collect(); + JustificationAddResult::Required + } + Header { + importance: HeaderImportance::Imported, + .. + } => { + self.inner = Justification { + imported: true, + parent, + justification, + }; + // No need to modify know_most, as we now know everything we need. + JustificationAddResult::Finalizable + } + } + } +} + +#[cfg(test)] +mod tests { + use super::{JustificationAddResult, Vertex}; + use crate::sync::{ + mock::{MockHeader, MockIdentifier, MockJustification, MockPeerId}, + Header, + }; + + type MockVertex = Vertex; + + #[test] + fn initially_empty() { + let vertex = MockVertex::new(); + assert!(!vertex.required()); + assert!(!vertex.imported()); + assert!(vertex.parent().is_none()); + assert!(vertex.know_most().is_empty()); + assert_eq!(vertex.clone().ready(), Err(vertex)); + } + + #[test] + fn empty_remembers_block_holders() { + let mut vertex = MockVertex::new(); + let peer_id = rand::random(); + vertex.add_block_holder(Some(peer_id)); + assert!(vertex.know_most().contains(&peer_id)); + } + + #[test] + fn empty_set_required() { + let mut vertex = MockVertex::new(); + assert!(vertex.set_required()); + assert!(vertex.required()); + assert!(!vertex.set_required()); + assert!(vertex.required()); + } + + #[test] + fn empty_to_header() { + let mut vertex = MockVertex::new(); + let peer_id = rand::random(); + let parent = MockIdentifier::new_random(43); + vertex.insert_header(parent.clone(), Some(peer_id)); + assert!(!vertex.required()); + assert!(!vertex.imported()); + assert_eq!(vertex.parent(), Some(&parent)); + assert!(vertex.know_most().contains(&peer_id)); + assert_eq!(vertex.clone().ready(), Err(vertex)); + } + + #[test] + fn header_remembers_block_holders() { + let mut vertex = MockVertex::new(); + let peer_id = rand::random(); + let parent = MockIdentifier::new_random(43); + vertex.insert_header(parent, Some(peer_id)); + let other_peer_id = rand::random(); + vertex.add_block_holder(Some(other_peer_id)); + assert!(vertex.know_most().contains(&peer_id)); + assert!(vertex.know_most().contains(&other_peer_id)); + } + + #[test] + fn header_set_required() { + let mut vertex = MockVertex::new(); + let peer_id = rand::random(); + let parent = MockIdentifier::new_random(43); + vertex.insert_header(parent, Some(peer_id)); + assert!(vertex.set_required()); + assert!(vertex.required()); + assert!(!vertex.set_required()); + assert!(vertex.required()); + } + + #[test] + fn header_still_required() { + let mut vertex = MockVertex::new(); + assert!(vertex.set_required()); + let peer_id = rand::random(); + let parent = MockIdentifier::new_random(43); + vertex.insert_header(parent, Some(peer_id)); + assert!(vertex.required()); + assert!(!vertex.set_required()); + assert!(vertex.required()); + } + + #[test] + fn empty_to_body() { + let mut vertex = MockVertex::new(); + let parent = MockIdentifier::new_random(43); + assert!(!vertex.insert_body(parent.clone())); + assert!(!vertex.required()); + assert!(vertex.imported()); + assert_eq!(vertex.parent(), Some(&parent)); + assert_eq!(vertex.clone().ready(), Err(vertex)); + } + + #[test] + fn header_to_body() { + let mut vertex = MockVertex::new(); + let peer_id = rand::random(); + let parent = MockIdentifier::new_random(43); + vertex.insert_header(parent.clone(), Some(peer_id)); + assert!(!vertex.insert_body(parent.clone())); + assert!(!vertex.required()); + assert!(vertex.imported()); + assert_eq!(vertex.parent(), Some(&parent)); + assert_eq!(vertex.clone().ready(), Err(vertex)); + } + + #[test] + fn body_set_required() { + let mut vertex = MockVertex::new(); + let parent = MockIdentifier::new_random(43); + assert!(!vertex.insert_body(parent)); + assert!(!vertex.set_required()); + assert!(!vertex.required()); + } + + #[test] + fn body_no_longer_required() { + let mut vertex = MockVertex::new(); + assert!(vertex.set_required()); + let parent = MockIdentifier::new_random(43); + assert!(!vertex.insert_body(parent)); + assert!(!vertex.required()); + } + + #[test] + fn empty_to_justification() { + let mut vertex = MockVertex::new(); + let parent_header = MockHeader::random_parentless(0); + let header = parent_header.random_child(); + let parent = header.parent_id().expect("born of a parent"); + let justification = MockJustification::for_header(header); + let peer_id = rand::random(); + assert_eq!( + vertex.insert_justification(parent.clone(), justification, Some(peer_id)), + JustificationAddResult::Required + ); + assert!(vertex.required()); + assert!(!vertex.imported()); + assert_eq!(vertex.parent(), Some(&parent)); + assert!(vertex.know_most().contains(&peer_id)); + assert_eq!(vertex.clone().ready(), Err(vertex)); + } + + #[test] + fn header_to_justification() { + let mut vertex = MockVertex::new(); + let parent_header = MockHeader::random_parentless(0); + let header = parent_header.random_child(); + let parent = header.parent_id().expect("born of a parent"); + let justification = MockJustification::for_header(header); + let peer_id = rand::random(); + vertex.insert_header(parent.clone(), Some(peer_id)); + assert_eq!( + vertex.insert_justification(parent.clone(), justification, None), + JustificationAddResult::Required + ); + assert!(vertex.required()); + assert!(!vertex.imported()); + assert_eq!(vertex.parent(), Some(&parent)); + assert!(vertex.know_most().is_empty()); + assert_eq!(vertex.clone().ready(), Err(vertex)); + } + + #[test] + fn body_to_justification() { + let mut vertex = MockVertex::new(); + let parent_header = MockHeader::random_parentless(0); + let header = parent_header.random_child(); + let parent = header.parent_id().expect("born of a parent"); + let justification = MockJustification::for_header(header); + assert!(!vertex.insert_body(parent.clone())); + assert_eq!( + vertex.insert_justification(parent.clone(), justification.clone(), None), + JustificationAddResult::Finalizable + ); + assert!(!vertex.required()); + assert!(vertex.imported()); + assert_eq!(vertex.parent(), Some(&parent)); + assert_eq!(vertex.ready(), Ok(justification)); + } + + #[test] + fn justification_set_required() { + let mut vertex = MockVertex::new(); + let parent_header = MockHeader::random_parentless(0); + let header = parent_header.random_child(); + let parent = header.parent_id().expect("born of a parent"); + let justification = MockJustification::for_header(header); + let peer_id = rand::random(); + assert_eq!( + vertex.insert_justification(parent, justification, Some(peer_id)), + JustificationAddResult::Required + ); + assert!(!vertex.set_required()); + assert!(vertex.required()); + } + + #[test] + fn justification_still_required() { + let mut vertex = MockVertex::new(); + let parent_header = MockHeader::random_parentless(0); + let header = parent_header.random_child(); + let parent = header.parent_id().expect("born of a parent"); + let justification = MockJustification::for_header(header); + let peer_id = rand::random(); + assert!(vertex.set_required()); + assert_eq!( + vertex.insert_justification(parent, justification, Some(peer_id)), + JustificationAddResult::Noop + ); + assert!(vertex.required()); + } + + #[test] + fn my_body_is_ready() { + let mut vertex = MockVertex::new(); + let parent_header = MockHeader::random_parentless(0); + let header = parent_header.random_child(); + let parent = header.parent_id().expect("born of a parent"); + let justification = MockJustification::for_header(header); + assert_eq!( + vertex.insert_justification(parent.clone(), justification.clone(), None), + JustificationAddResult::Required + ); + assert!(vertex.insert_body(parent.clone())); + assert!(!vertex.required()); + assert!(vertex.imported()); + assert_eq!(vertex.parent(), Some(&parent)); + assert_eq!(vertex.ready(), Ok(justification)); + } +} diff --git a/finality-aleph/src/sync/mock.rs b/finality-aleph/src/sync/mock.rs new file mode 100644 index 0000000000..2cdc630cf3 --- /dev/null +++ b/finality-aleph/src/sync/mock.rs @@ -0,0 +1,106 @@ +use codec::{Decode, Encode}; + +use crate::sync::{BlockIdentifier, Header, Justification}; + +pub type MockPeerId = u32; + +#[derive(Clone, Hash, Debug, PartialEq, Eq, Encode, Decode)] +pub struct MockIdentifier { + number: u32, + hash: u32, +} + +impl MockIdentifier { + fn new(number: u32, hash: u32) -> Self { + MockIdentifier { number, hash } + } + + pub fn new_random(number: u32) -> Self { + MockIdentifier::new(number, rand::random()) + } +} + +impl BlockIdentifier for MockIdentifier { + fn number(&self) -> u32 { + self.number + } +} + +#[derive(Clone, Hash, Debug, PartialEq, Eq, Encode, Decode)] +pub struct MockHeader { + id: MockIdentifier, + parent: Option, +} + +impl MockHeader { + fn new(id: MockIdentifier, parent: Option) -> Self { + MockHeader { id, parent } + } + + pub fn random_parentless(number: u32) -> Self { + let id = MockIdentifier::new_random(number); + MockHeader { id, parent: None } + } + + pub fn random_child(&self) -> Self { + let id = MockIdentifier::new_random(self.id.number() + 1); + let parent = Some(self.id.clone()); + MockHeader { id, parent } + } + + pub fn random_branch(&self) -> impl Iterator { + RandomBranch { + parent: self.clone(), + } + } +} + +struct RandomBranch { + parent: MockHeader, +} + +impl Iterator for RandomBranch { + type Item = MockHeader; + + fn next(&mut self) -> Option { + let result = self.parent.random_child(); + self.parent = result.clone(); + Some(result) + } +} + +impl Header for MockHeader { + type Identifier = MockIdentifier; + + fn id(&self) -> Self::Identifier { + self.id.clone() + } + + fn parent_id(&self) -> Option { + self.parent.clone() + } +} + +#[derive(Clone, Hash, Debug, PartialEq, Eq, Encode, Decode)] +pub struct MockJustification { + header: MockHeader, +} + +impl MockJustification { + pub fn for_header(header: MockHeader) -> Self { + MockJustification { header } + } +} + +impl Justification for MockJustification { + type Header = MockHeader; + type Unverified = Self; + + fn header(&self) -> &Self::Header { + &self.header + } + + fn into_unverified(self) -> Self::Unverified { + self + } +} diff --git a/finality-aleph/src/sync/mod.rs b/finality-aleph/src/sync/mod.rs index 1baf8f9c42..611a510de2 100644 --- a/finality-aleph/src/sync/mod.rs +++ b/finality-aleph/src/sync/mod.rs @@ -6,6 +6,9 @@ use std::{ use codec::Codec; mod data; +mod forest; +#[cfg(test)] +mod mock; mod substrate; mod task_queue; mod ticker; @@ -14,6 +17,11 @@ pub use substrate::SessionVerifier; const LOG_TARGET: &str = "aleph-block-sync"; +/// The identifier of a connected peer. +pub trait PeerId: Clone + Hash + Eq {} + +impl PeerId for T {} + /// The identifier of a block, the least amount of knowledge we can have about a block. pub trait BlockIdentifier: Clone + Hash + Debug + Eq { /// The block number, useful when reasoning about hopeless forks.