From 9aa7dfe661b078da6e86fb944fb179d16e2207c0 Mon Sep 17 00:00:00 2001 From: Kirill Ivanov Date: Mon, 13 Mar 2023 18:20:42 +0300 Subject: [PATCH] Revert "remove smart sync" This reverts commit 4ed9f7d6e2dc48748dd717dfe3130e78e3852800. --- src/algorithm/datastructure/mod.rs | 56 +- .../peer_index/fork_tracking/mod.rs | 773 ++++++++++++++++++ .../peer_index/fork_tracking/utils.rs | 458 +++++++++++ src/algorithm/datastructure/peer_index/mod.rs | 172 ++-- src/algorithm/datastructure/sync/jobs.rs | 242 ++++++ src/algorithm/datastructure/sync/mod.rs | 15 + src/algorithm/datastructure/sync/state.rs | 462 +++++++++++ 7 files changed, 2107 insertions(+), 71 deletions(-) create mode 100644 src/algorithm/datastructure/peer_index/fork_tracking/utils.rs create mode 100644 src/algorithm/datastructure/sync/jobs.rs create mode 100644 src/algorithm/datastructure/sync/state.rs diff --git a/src/algorithm/datastructure/mod.rs b/src/algorithm/datastructure/mod.rs index 0f21446..2bd6717 100644 --- a/src/algorithm/datastructure/mod.rs +++ b/src/algorithm/datastructure/mod.rs @@ -6,7 +6,7 @@ use tracing::{debug, error, instrument, trace, warn}; use std::collections::{HashMap, HashSet, VecDeque}; use self::ordering::OrderedEvents; -use self::peer_index::{PeerIndex, PeerIndexEntry}; +use self::peer_index::{EventIndex, PeerIndex, PeerIndexEntry}; use self::slice::SliceIterator; use super::event::{self, Event, Parents}; use super::{EventKind, PushError, RoundNum}; @@ -106,8 +106,6 @@ pub enum OrderedEventsError { UndecidedRound, } -pub type EventIndex = HashMap; - pub struct Graph { all_events: EventIndex>, peer_index: PeerIndex, @@ -210,7 +208,10 @@ where return Err(PushError::GenesisAlreadyExists); } debug!("The event is valid, updating state to include it"); - let new_peer_index = PeerIndexEntry::new(new_event.hash().clone()); + let new_peer_index = PeerIndexEntry::new( + new_event.hash().clone(), + std::num::NonZeroU8::new(10u8).unwrap(), + ); self.peer_index.insert(author, new_peer_index); } event::Kind::Regular(parents) => { @@ -258,8 +259,11 @@ where .children .self_child .add_child(new_event.hash().clone()); - if let Err(e) = author_index.add_event(self_parent_event, new_event.hash().clone()) - { + if let Err(e) = author_index.add_event( + self_parent_event.hash().clone(), + new_event.hash().clone(), + |h| self.all_events.get(h), + ) { warn!("Peer index insertion error: {}", e); } let other_parent_event = self @@ -329,7 +333,37 @@ where } /// Synchronization-related stuff. -impl Graph {} +/// details in [sync] +impl Graph { + /// 1st sync step - (at reciever) generate compressed state. + pub fn graph_known_state( + &self, + ) -> Result { + let res = sync::state::CompressedKnownState::try_from(&self.peer_index); + match &res { + Ok(_state) => debug!("Successfully constructed compressed graph state"), + Err(sync::state::SubmultiplierMismatch) => error!( + "Fork index is inconsistent (submultipliers vary). Very strange, should not happen" + ), + }; + res + } + + /// 2nd sync step - (at sender) find which updates the reciever must perform to get to the same + /// (or higher) level of graph knowledge as we have. + pub fn generate_sync_jobs( + &self, + peer_state: sync::state::CompressedKnownState, + ) -> sync::jobs::Jobs { + let events_unknown_by_receiver = {}; + todo!() + } + + /// 3rd sync step - (at reciever) try to apply the sync steps provided by the sender. + pub fn try_apply_sync(&mut self, jobs: sync::jobs::Jobs) -> Result<(), ()> { + todo!() + } +} impl Graph where @@ -518,7 +552,7 @@ where let mut extension = vec![]; for (peer_id, index) in &self.peer_index { if !peers_hit.contains(&peer_id) { - extension.push(index.latest_event()); + extension.push(index.latest_events()[0]); } } trace!( @@ -582,7 +616,11 @@ impl Graph { // for navigating the graph state externally pub fn peer_latest_event(&self, peer: &PeerId) -> Option<&event::Hash> { - self.peer_index.get(peer).map(|e| e.latest_event()) + self.peer_index.get(peer).map(|e| { + *e.latest_events() + .get(0) + .expect("At least genesis is present") + }) } // for navigating the graph state externally diff --git a/src/algorithm/datastructure/peer_index/fork_tracking/mod.rs b/src/algorithm/datastructure/peer_index/fork_tracking/mod.rs index 8b13789..e43d8c6 100644 --- a/src/algorithm/datastructure/peer_index/fork_tracking/mod.rs +++ b/src/algorithm/datastructure/peer_index/fork_tracking/mod.rs @@ -1 +1,774 @@ +use std::{collections::HashMap, num::NonZeroU8}; +use derive_getters::Getters; +use thiserror::Error; +use tracing::{instrument, trace, warn}; + +pub use self::utils::Extension; +use self::utils::SplitError; +use crate::algorithm::event; + +#[cfg(test)] +pub use self::utils::test_utils; + +mod utils; + +#[derive(Debug, Error, PartialEq)] +#[error("Provided fork identifier is unknown")] +pub struct InvalidForkIdentifier; + +#[derive(Debug, Error, PartialEq)] +pub enum LookupIndexInconsistency { + #[error(transparent)] + InvalidIdentifier(#[from] InvalidForkIdentifier), + #[error("Splitting extension failed")] + SplitError(#[from] SplitError), +} + +#[derive(Debug, Error, PartialEq)] +pub enum LeafPush { + #[error("Self parent is not known to be the latest event of some leaf")] + InvalidSelfParent, + #[error("Inconsistent fork tracking index")] + InconsistentState, +} + +/// Sequence of events without any forks at the end +#[derive(Debug, Clone)] +pub struct Leaf(Extension); + +impl Leaf { + /// Insert a new leaf within this leaf, creating new fork. + /// + /// Returns `(parent_fork, first_child_leaf, new_child_leaf)` and + /// `None` if `fork_parent_height` is incorrect (the parent or its child + /// are out of bounds) + fn attach_leaf( + self, + fork_parent: event::Hash, + fork_parent_height: usize, + firstborn: event::Hash, + newborn: event::Hash, + ) -> Result<(Fork, Leaf, Leaf), SplitError> { + let this_submul = self.0.submultiple(); + let (parent_ext, firstborn_ext) = + self.0 + .split_at(fork_parent, fork_parent_height, firstborn.clone())?; + let parent_fork = Fork { + events: parent_ext, + forks: vec![firstborn, newborn.clone()], + }; + let firstborn_fork = Leaf(firstborn_ext); + let newborn_leaf = Leaf( + Extension::from_event_with_submultiple(newborn, fork_parent_height + 1, this_submul) + .expect("Submultiple was correct before, so it must be here as well"), + ); + Ok((parent_fork, firstborn_fork, newborn_leaf)) + } + + pub fn events(&self) -> &Extension { + &self.0 + } +} + +/// Sequence of events with branching at the end +#[derive(Debug, Clone, Getters)] +pub struct Fork { + events: Extension, + /// First elements of each child + forks: Vec, +} + +impl Fork { + /// Insert a new leaf within this fork, creating new fork. + /// + /// Returns `(parent_fork, first_child_fork, new_child_leaf)` and + /// `None` if `fork_parent_height` is incorrect (the parent or its child + /// are out of bounds) + fn attach_leaf( + self, + fork_parent: event::Hash, + fork_parent_height: usize, + firstborn: event::Hash, + newborn: event::Hash, + ) -> Result<(Fork, Fork, Leaf), SplitError> { + let this_submul = self.events.submultiple(); + let (parent_ext, firstborn_ext) = + self.events + .split_at(fork_parent, fork_parent_height, firstborn.clone())?; + let parent_fork = Fork { + events: parent_ext, + forks: vec![firstborn, newborn.clone()], + }; + let firstborn_fork = Fork { + events: firstborn_ext, + forks: self.forks, + }; + let newborn_leaf = Leaf( + Extension::from_event_with_submultiple(newborn, fork_parent_height + 1, this_submul) + .expect("Submultiple was correct before, so it must be here as well"), + ); + Ok((parent_fork, firstborn_fork, newborn_leaf)) + } + + pub fn subsequent_forks(&self) -> &Vec { + &self.forks + } +} + +/// Tracks events created by a single peer with respect to self child/self parent +/// relation. Since by definition an event can only have a single self parent, +/// we can represent it as tree. +#[derive(Getters)] +pub struct ForkIndex { + /// Sections of the peer graph that do not end with a fork. Indexed by their + /// first/starting element + leafs: HashMap, + /// Index of the first element of a leaf by the last/ending element (for convenient + /// push) + leaf_starts: HashMap, + forks: HashMap, + /// Index of the first element of a fork by the last/ending element (for convenient + /// sync)??? Todo: remove??? + fork_starts: HashMap, + origin: event::Hash, + // Should be consistent with actual submultiples inside since they're propagated + // on creation without changes + // + /// Submultiple for tracking intermediate events in [`Extension`]s + submultiple: NonZeroU8, +} + +impl ForkIndex { + /// Create new fork index with origin event `genesis` and + /// `submultiple` - base for multipliers of tracking long + /// extensions. + pub fn new(genesis: event::Hash, submultiple: NonZeroU8) -> Self { + let mut new_index = Self { + leafs: HashMap::new(), + leaf_starts: HashMap::new(), + forks: HashMap::new(), + fork_starts: HashMap::new(), + origin: genesis.clone(), + submultiple, + }; + new_index.insert_leaf(Leaf( + Extension::from_event_with_submultiple(genesis, 0, submultiple.into()) + .expect("Nonzero static type"), + )); + new_index + } + + /// Add event to the end of a corresponding leaf. + #[instrument(level = "trace", skip_all)] + pub fn push_event( + &mut self, + event: event::Hash, + self_parent: &event::Hash, + ) -> Result<(), LeafPush> { + let leaf_start = self + .leaf_starts + .get(self_parent) + .ok_or(LeafPush::InvalidSelfParent)? + .clone(); + let leaf = self + .leafs + .get_mut(&leaf_start) + .ok_or(LeafPush::InconsistentState)?; + if leaf.0.last_event() != self_parent { + return Err(LeafPush::InconsistentState); + } + leaf.0.push_event(event.clone()); + self.leaf_starts.remove(self_parent); + self.leaf_starts.insert(event, leaf_start); + Ok(()) + } + + fn insert_fork(&mut self, fork: Fork) { + self.fork_starts.insert( + fork.events.last_event().clone(), + fork.events.first_event().clone(), + ); + self.forks.insert(fork.events.first_event().clone(), fork); + } + + fn remove_fork(&mut self, fork_identifier: &event::Hash) -> Option { + // Separate block to ensure the borrow is dropped + let fork = self.forks.remove(&fork_identifier)?; + let end_identifier = &fork.events.last_event(); + self.fork_starts.remove(end_identifier); + Some(fork) + } + + fn insert_leaf(&mut self, leaf: Leaf) { + let first = leaf.0.first_event().clone(); + let last = leaf.0.last_event().clone(); + self.leaf_starts.insert(last, first.clone()); + self.leafs.insert(first, leaf); + } + + fn remove_leaf(&mut self, leaf_identifier: &event::Hash) -> Option { + // Separate block to ensure the borrow is dropped + let leaf = self.leafs.remove(&leaf_identifier)?; + let end_identifier = &leaf.0.last_event(); + self.leaf_starts.remove(&end_identifier); + Some(leaf) + } + + /// Add a new branch that starts with event `new_self_child` into the middle + /// of [`Extension`] (thus creating a new fork). + /// + /// ## `previous_fork_identifier` + /// Needs `previous_fork_identifier`; it is an event that starts the branch + /// we insert the fork into + /// + /// In other words, it is a first self ancestor whose parent has multiple + /// self children (or genesis event) + /// + /// ### Examples + /// Let's say, we want to add new fork with event I after D: + /// + /// ```text + /// F + /// / + /// C-D-E + /// / \ + /// A-B G + /// \ + /// H + /// ``` + /// + /// We use this procedure this way (assuming A has height 0 and thus C - 2) + /// ```rust,ignore + /// add_new_fork( + /// previous_fork_identifier: "C", + /// forking_parent: "D", + /// forking_parent_height: 3, + /// new_self_child: "I", + /// other_self_child: "E", + /// ) + /// ``` + /// It adds new leaf that consisting of single event "I" after event "D", which + /// has another child - "E". Also, "D" belongs to [extension](Extension) identified + /// by "C" at the height = 3. + /// + /// result: + /// ```text + /// F + /// / + /// C-D-E + /// / \ \ + /// A-B I G + /// \ + /// H + /// ``` + #[instrument(level = "trace", skip_all)] + pub fn add_new_fork( + &mut self, + extension_identifier: &event::Hash, + forking_parent: event::Hash, + forking_parent_height: usize, + new_self_child: event::Hash, + other_self_child: event::Hash, + ) -> Result<(), LookupIndexInconsistency> { + // Not `remove` because we don't want to arrive at + // inconsistent state in case of error + if let Some(fork) = self.forks.get(extension_identifier).cloned() { + trace!("Found forking extension by ident, creating new fork within it"); + let (parent_fork, firstborn_fork, newborn_leaf) = fork.clone().attach_leaf( + forking_parent, + forking_parent_height, + other_self_child, + new_self_child, + )?; + // Now no errors can happen, so we're safe to remove + self.remove_fork(extension_identifier); + self.insert_fork(parent_fork); + self.insert_fork(firstborn_fork); + self.insert_leaf(newborn_leaf); + Ok(()) + } else if let Some(leaf) = self.leafs.get(extension_identifier).cloned() { + trace!("Found leaf extension by ident, creating new fork within it"); + let (parent_fork, firstborn_leaf, newborn_leaf) = leaf.attach_leaf( + forking_parent, + forking_parent_height, + other_self_child, + new_self_child, + )?; + // Now no errors can happen, so we're safe to remove + self.remove_leaf(extension_identifier); + self.insert_fork(parent_fork); + self.insert_leaf(firstborn_leaf); + self.insert_leaf(newborn_leaf); + Ok(()) + } else { + Err(LookupIndexInconsistency::InvalidIdentifier( + InvalidForkIdentifier, + )) + } + } + + /// Add a new branch that starts with event `new_self_child` to an existing + /// fork OR at the leafs. + /// + /// To insert new fork use [`Self::add_new_fork()`]. + /// + /// ## `previous_fork_identifier` + /// Needs `previous_fork_identifier`; it is an event that starts the branch + /// which resulted in the fork of interest. + /// + /// In other words, it is a first self ancestor whose parent has multiple + /// self children (or genesis event) + /// + /// ### Examples + /// Let's say, we want to add new forking event I in such fork tree: + /// + /// ```text + /// F + /// / + /// C-D-E + /// / \ + /// A-B G + /// \ + /// H + /// ``` + /// + /// We use this procedure to add new forks after events B or E by calling + /// #### B + /// ```rust,ignore + /// add_branch_to_fork("A", "I") + /// ``` + /// result: + /// ```text + /// F + /// / + /// C-D-E + /// / \ + /// A-B-I G + /// \ + /// H + /// ``` + /// #### E + /// ```rust,ignore + /// add_branch_to_fork("C", "I") + /// ``` + /// result: + /// ```text + /// F + /// / + /// C-D-E-I + /// / \ + /// A-B G + /// \ + /// H + /// ``` + #[instrument(level = "trace", skip_all)] + pub fn add_branch_to_fork( + &mut self, + previous_fork_identifier: &event::Hash, + new_self_child: event::Hash, + ) -> Result<(), InvalidForkIdentifier> { + let parent_fork = self + .forks + .get_mut(&previous_fork_identifier) + .ok_or(InvalidForkIdentifier)?; + // No need to add new `Fork`s + parent_fork.forks.push(new_self_child.clone()); + let new_self_child_height = parent_fork.events.first_height() + parent_fork.events.length(); + let parent_submul = parent_fork.events.submultiple(); + self.insert_leaf(Leaf( + Extension::from_event_with_submultiple( + new_self_child, + new_self_child_height, + parent_submul, + ) + .expect("Submultiple was correct before, so it must be here as well"), + )); + Ok(()) + } + + pub fn iter(&self) -> ForkIndexIter { + ForkIndexIter { + index: self, + queued_entries: vec![&self.origin], + } + } + + pub fn leaf_events(&self) -> Vec<&event::Hash> { + self.leaf_starts.keys().collect() + } + + // pub fn submultiple(&self) -> NonZeroU8 { + // self.submultiple + // } + + /// Get fork either by its start or end. + fn find_fork(&self, event: &event::Hash) -> Option<&Fork> { + self.forks + .get(event) + .or_else(|| self.forks.get(self.fork_starts.get(event)?)) + } + + /// Get fork either by its start or end. + fn find_leaf(&self, event: &event::Hash) -> Option<&Leaf> { + self.leafs + .get(event) + .or_else(|| self.leafs.get(self.leaf_starts.get(event)?)) + } + + // /// Find extension by its start or end. + // pub fn find_extension(&self, event: &event::Hash) -> Option<&Extension> { + // self.find_leaf(event) + // .map(|leaf| leaf.events()) + // .or_else(|| self.find_fork(event).map(|fork| fork.events())) + // } + + /// Find extension by its start. + pub fn find_extension(&self, start: &event::Hash) -> Option<&Extension> { + self.leafs + .get(start) + .map(|leaf| leaf.events()) + .or_else(|| self.forks.get(start).map(|fork| fork.events())) + } +} + +pub enum ForkIndexEntry<'a> { + Fork(&'a Fork), + Leaf(&'a Leaf), +} + +impl<'a> ForkIndexEntry<'a> { + pub fn extension(&self) -> &Extension { + match self { + ForkIndexEntry::Fork(f) => &f.events, + ForkIndexEntry::Leaf(l) => &l.0, + } + } +} + +pub struct ForkIndexIter<'a> { + index: &'a ForkIndex, + queued_entries: Vec<&'a event::Hash>, +} + +/// Iterate over all forks/leafs. For a - pair +/// in the index tree, never traverses child before parent +/// (basically DFS or BFS). +impl<'a> Iterator for ForkIndexIter<'a> { + type Item = ForkIndexEntry<'a>; + + fn next(&mut self) -> Option { + let next_entry = self.queued_entries.pop()?; + if let Some(fork) = self.index.forks.get(next_entry) { + let mut new_entries = fork.forks.iter().by_ref().clone().collect(); + self.queued_entries.append(&mut new_entries); + Some(ForkIndexEntry::Fork(fork)) + } else if let Some(leaf) = self.index.leafs.get(next_entry) { + Some(ForkIndexEntry::Leaf(leaf)) + } else { + warn!( + "Couldn't find fork {} in the index, likely the index is malformed", + next_entry + ); + None + } + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashSet; + + use super::*; + use utils::test_utils::*; + + // For debugging + #[allow(unused)] + fn print_entry(e: ForkIndexEntry, name_lookup: F) + where + F: Fn(&event::Hash) -> &str, + { + match e { + ForkIndexEntry::Fork(_) => println!("Fork: "), + ForkIndexEntry::Leaf(_) => println!("Leaf: "), + } + let ext = e.extension(); + print!( + "events {}(at {})-({} events)->{}; then ", + name_lookup(ext.first_event()), + ext.first_height(), + ext.length(), + name_lookup(&ext.last_event()) + ); + match e { + ForkIndexEntry::Fork(f) => { + println!( + "forks to: {:?}", + f.forks + .iter() + .map(name_lookup) + .collect::>() + .as_slice() + ); + } + ForkIndexEntry::Leaf(_) => println!("nothing."), + } + } + + // For debugging + #[allow(unused)] + fn print_fork_index(index: &ForkIndex, name_lookup: F) + where + F: Fn(&event::Hash) -> &str, + { + for (_, fork) in &index.forks { + let entry = ForkIndexEntry::Fork(&fork); + print_entry(entry, &name_lookup); + } + for (_, leaf) in &index.leafs { + let entry = ForkIndexEntry::Leaf(&leaf); + print_entry(entry, &name_lookup); + } + } + + #[test] + fn test_fork_index_constructs() { + // Resulting layout; events are added in alphabetical order + // F E + // / / + // A-B-C + // \ + // D + + // A + let mut index = ForkIndex::new(TEST_HASH_A, NonZeroU8::new(3u8).unwrap()); + // B + index.push_event(TEST_HASH_B, &TEST_HASH_A).unwrap(); + // C + index.push_event(TEST_HASH_C, &TEST_HASH_B).unwrap(); + // D + index + .add_new_fork(&TEST_HASH_A, TEST_HASH_B, 1, TEST_HASH_D, TEST_HASH_C) + .unwrap(); + // E + index.add_branch_to_fork(&TEST_HASH_A, TEST_HASH_E).unwrap(); + // F + index + .add_new_fork(&TEST_HASH_A, TEST_HASH_A, 0, TEST_HASH_F, TEST_HASH_B) + .unwrap(); + } + + #[test] + fn test_fork_index_gives_errors() { + // Resulting layout; events are added in alphabetical order + // F E + // / / + // A-B-C + // \ + // D + + // A + let mut index = ForkIndex::new(TEST_HASH_A, NonZeroU8::new(3u8).unwrap()); + // B + assert_eq!( + index.push_event(TEST_HASH_B, &TEST_HASH_B), + Err(LeafPush::InvalidSelfParent) + ); + index.push_event(TEST_HASH_B, &TEST_HASH_A).unwrap(); + // C + index.push_event(TEST_HASH_C, &TEST_HASH_B).unwrap(); + // D + assert_eq!( + index.add_new_fork(&TEST_HASH_A, TEST_HASH_B, 2, TEST_HASH_D, TEST_HASH_C), + Err(LookupIndexInconsistency::SplitError( + SplitError::HeightOutOfBounds + )) + ); + assert_eq!( + index.add_new_fork(&TEST_HASH_A, TEST_HASH_B, 1, TEST_HASH_D, TEST_HASH_A), + Err(LookupIndexInconsistency::SplitError( + SplitError::ChildEndMismatch + )) + ); + assert_eq!( + index.add_new_fork(&TEST_HASH_D, TEST_HASH_B, 1, TEST_HASH_D, TEST_HASH_C), + Err(LookupIndexInconsistency::InvalidIdentifier( + InvalidForkIdentifier + )) + ); + // No need to test `forking_parent` as the index has no way of knowing if it is + // correct in this case. + // `new_self_child` as well, since it's just a new event. + index + .add_new_fork(&TEST_HASH_A, TEST_HASH_B, 1, TEST_HASH_D, TEST_HASH_C) + .unwrap(); + // E + assert_eq!( + index.add_branch_to_fork(&TEST_HASH_B, TEST_HASH_E), + Err(InvalidForkIdentifier) + ); + index.add_branch_to_fork(&TEST_HASH_A, TEST_HASH_E).unwrap(); + // F + assert_eq!( + index.add_new_fork(&TEST_HASH_A, TEST_HASH_B, 0, TEST_HASH_F, TEST_HASH_B), + Err(LookupIndexInconsistency::SplitError( + SplitError::ParentStartMismatch + )) + ); + index + .add_new_fork(&TEST_HASH_A, TEST_HASH_A, 0, TEST_HASH_F, TEST_HASH_B) + .unwrap(); + } + + #[test] + fn test_fork_index_iterates_correctly() { + // Resulting layout; events are added in alphabetical order + // F E + // / / + // A-B-C + // \ + // D + + // We want to make sure we visit all events and don't visit + // children before their parents. + + // Let's use these helpers to track it: + + enum ForkIndexEntryOwned { + Fork(Fork), + Leaf(Leaf), + } + + impl ForkIndexEntryOwned { + fn extension(&self) -> &Extension { + match self { + ForkIndexEntryOwned::Fork(f) => &f.events, + ForkIndexEntryOwned::Leaf(l) => &l.0, + } + } + } + + // key - identifier + let mut entries_to_visit = HashMap::from([ + ( + TEST_HASH_A, + ForkIndexEntryOwned::Fork(Fork { + events: Extension::from_event_with_submultiple(TEST_HASH_A, 0, 3u8) + .expect("nonzero"), + forks: vec![TEST_HASH_B, TEST_HASH_F], + }), + ), + ( + TEST_HASH_F, + ForkIndexEntryOwned::Leaf(Leaf( + Extension::from_event_with_submultiple(TEST_HASH_F, 1, 3u8).expect("nonzero"), + )), + ), + ( + TEST_HASH_B, + ForkIndexEntryOwned::Fork(Fork { + events: Extension::from_event_with_submultiple(TEST_HASH_B, 1, 3u8) + .expect("nonzero"), + forks: vec![TEST_HASH_E, TEST_HASH_C, TEST_HASH_D], + }), + ), + ( + TEST_HASH_E, + ForkIndexEntryOwned::Leaf(Leaf( + Extension::from_event_with_submultiple(TEST_HASH_E, 2, 3u8).expect("nonzero"), + )), + ), + ( + TEST_HASH_C, + ForkIndexEntryOwned::Leaf(Leaf( + Extension::from_event_with_submultiple(TEST_HASH_C, 2, 3u8).expect("nonzero"), + )), + ), + ( + TEST_HASH_D, + ForkIndexEntryOwned::Leaf(Leaf( + Extension::from_event_with_submultiple(TEST_HASH_D, 2, 3u8).expect("nonzero"), + )), + ), + ]); + let parent = HashMap::from([ + (TEST_HASH_B, TEST_HASH_A), + (TEST_HASH_F, TEST_HASH_A), + (TEST_HASH_C, TEST_HASH_B), + (TEST_HASH_D, TEST_HASH_B), + (TEST_HASH_E, TEST_HASH_B), + ]); + // let names = HashMap::from(NAMES); + + // A + let mut index = ForkIndex::new(TEST_HASH_A, NonZeroU8::new(3u8).unwrap()); + // println!("\nInserted A; state:"); + // print_fork_index(&index, |hash| names.get(hash).unwrap().clone()); + + // B + index.push_event(TEST_HASH_B, &TEST_HASH_A).unwrap(); + // println!("\nInserted B; state:"); + // print_fork_index(&index, |hash| names.get(hash).unwrap().clone()); + + // C + index.push_event(TEST_HASH_C, &TEST_HASH_B).unwrap(); + // println!("\nInserted C; state:"); + // print_fork_index(&index, |hash| names.get(hash).unwrap().clone()); + + // D + index + .add_new_fork(&TEST_HASH_A, TEST_HASH_B, 1, TEST_HASH_D, TEST_HASH_C) + .unwrap(); + // println!("\nInserted D; state:"); + // print_fork_index(&index, |hash| names.get(hash).unwrap().clone()); + + // E + index.add_branch_to_fork(&TEST_HASH_A, TEST_HASH_E).unwrap(); + // println!("\nInserted E; state:"); + // print_fork_index(&index, |hash| names.get(hash).unwrap().clone()); + + // F + index + .add_new_fork(&TEST_HASH_A, TEST_HASH_A, 0, TEST_HASH_F, TEST_HASH_B) + .unwrap(); + // println!("\nInserted F; state:"); + // print_fork_index(&index, |hash| names.get(hash).unwrap().clone()); + + fn entries_equal(expected: ForkIndexEntryOwned, got: ForkIndexEntry) -> Result<(), String> { + let ext_expected = expected.extension().clone(); + let ext_got = got.extension().clone(); + match (expected, got) { + (ForkIndexEntryOwned::Fork(_), ForkIndexEntry::Leaf(_)) + | (ForkIndexEntryOwned::Leaf(_), ForkIndexEntry::Fork(_)) => { + return Err("Types mismatch".to_owned()) + } + (ForkIndexEntryOwned::Fork(expected), ForkIndexEntry::Fork(got)) => { + let forks_expected = HashSet::<_>::from_iter(expected.forks); + let forks_got = HashSet::<_>::from_iter(got.forks.clone()); + if forks_expected != forks_got { + return Err("Sets of forks are different".to_owned()); + } + } + (ForkIndexEntryOwned::Leaf(_), ForkIndexEntry::Leaf(_)) => (), + }; + if ext_expected != ext_got { + return Err("Extensions are not equal".to_owned()); + } + return Ok(()); + } + + for entry in index.iter() { + let id = entry.extension().first_event(); + if id != &TEST_HASH_A { + let parent_id = parent.get(id).unwrap(); + + if entries_to_visit.contains_key(parent_id) { + panic!("Visited child before parent ({} before {})", id, parent_id); + } + } + let expected_entry = entries_to_visit.remove(id).unwrap(); + assert_eq!(Ok(()), entries_equal(expected_entry, entry)); + } + assert!(entries_to_visit.is_empty()); + } +} diff --git a/src/algorithm/datastructure/peer_index/fork_tracking/utils.rs b/src/algorithm/datastructure/peer_index/fork_tracking/utils.rs new file mode 100644 index 0000000..e0b60b1 --- /dev/null +++ b/src/algorithm/datastructure/peer_index/fork_tracking/utils.rs @@ -0,0 +1,458 @@ +use thiserror::Error; + +use self::multiples::Multiples; +use crate::algorithm::event; + +// To not accidentally use private stuff +mod multiples { + use std::collections::BTreeMap; + + use thiserror::Error; + + #[derive(Debug, Error, PartialEq)] + #[error("The index is not a multiple of specified submultiple")] + pub struct NotMultiple; + + /// Stores items with each nth index (number/height/etc.). + /// + /// Intended to store subsequent elements starting not from the + /// beginning. For example, for `submultiple` 11 we may want to + /// store elements with indices 33, 44, 55, 66, 77, 88. + /// + /// Essentially [BTreeMap] but restricted to numeric indexes + /// that are multiples of the submultiple. + #[derive(Debug, Clone, PartialEq)] + pub struct Multiples { + submultiple: TMul, + items: BTreeMap, + } + + impl Multiples + where + TMul: Into + PartialEq + From + Copy, + TIndex: std::ops::Rem + Ord + Copy, + ::Output: PartialEq + From, + TItem: Clone, + { + // `None` if `submultiple` is 0 + pub fn new(submultiple: TMul) -> Option { + if submultiple == 0.into() { + return None; + } + Some(Self { + submultiple, + items: BTreeMap::new(), + }) + } + + pub fn try_insert(&mut self, index: TIndex, element: &TItem) -> Result<(), NotMultiple> { + if index % self.submultiple.into() != 0.into() { + return Err(NotMultiple); + } + self.items.insert(index, element.clone()); + Ok(()) + } + + /// Splits the collection into two at the given key. Returns everything after the given key, including the key. + pub fn split_off(&mut self, index: TIndex) -> Self { + let new_items = self.items.split_off(&index); + Self { + submultiple: self.submultiple, + items: new_items, + } + } + + /// All entries, in order by their index + pub fn entries(&self) -> std::collections::btree_map::Iter { + self.items.iter() + } + + pub fn submultiple(&self) -> TMul { + self.submultiple + } + } + + impl Multiples + where + TMul: Into + Copy, + TItem: Clone, + { + /// Returns `i`th multiple of `submultiple` starting from the first tracked + /// (`i` starts at 0). + /// Also returns its height, so the pair is `(height, event_hash)` + /// + /// If no such value, returns `None` + /// + /// # Example + /// If `submultiple=10` and the first known multiple is 20, then + /// `get(0)` will yield to `20`, and `get(3) = 50`. + pub fn get_ith(&self, i: usize) -> Option<(&usize, &TItem)> { + // It must already be a multiple of `submultiple` since + // we don't add items here if they're not + let first_height = self.items.first_key_value()?.0; + let i_height = first_height.saturating_add(i.saturating_mul(self.submultiple.into())); + self.items.get_key_value(&i_height) + // .map(|(index, item)| (index.clone(), item.clone())) + } + + pub fn get_by_height(&self, height: usize) -> Option<&TItem> { + self.items.get(&height) + } + } +} + +#[derive(Debug, Error, PartialEq)] +pub enum SplitError { + // Also note implies that single-event extension is not splittable + #[error( + "Parent height is out of bounds for splitting the extension. \ + Note that parent cannot be the last event of the extension" + )] + HeightOutOfBounds, + #[error( + "Split occurs at the start of the extension, thus split \ + parent must be equal to the extension start." + )] + ParentStartMismatch, + #[error( + "Split occurs at the end of the extension, thus split \ + child must be equal to the extension end." + )] + ChildEndMismatch, +} + +/// Node that represents a sequence of events without branching/forking +#[derive(Debug, Clone, PartialEq)] +pub struct Extension { + first: event::Hash, + first_height: usize, + last: event::Hash, + length: usize, + multiples: Multiples, +} + +impl Extension { + /// Construct extension consisting of a single event. + pub fn from_event_with_submultiple( + event: event::Hash, + height: usize, + submultiple: u8, + ) -> Option { + let mut multiples = Multiples::new(submultiple)?; + // it knows better whether to insert + let _ = multiples.try_insert(height, &event); + Some(Self { + first: event.clone(), + first_height: height, + last: event, + length: 1, + multiples, + }) + } + /// Add event to the end of extension. + pub fn push_event(&mut self, event: event::Hash) { + // it knows if insert + let _ = self + .multiples + .try_insert(self.first_height + self.length, &event); + self.last = event; + self.length += 1; + } + + /// Split the extension [A, D] into two: [A, B] and [C, D], where B is `parent` and C is `child`. + /// + /// Returns two extensions: `(before the split, after)` + pub fn split_at( + mut self, + parent: event::Hash, + parent_height: usize, + child: event::Hash, + ) -> Result<(Self, Self), SplitError> { + if parent_height < self.first_height || parent_height >= self.first_height + self.length - 1 + { + return Err(SplitError::HeightOutOfBounds); + } + if parent_height == self.first_height && parent != self.first { + return Err(SplitError::ParentStartMismatch); + } + // `self.first_height < parent_heigth < self.first_height + self.length` + // is true from the first condition. These are all integers, so + // length is at least 2. It means `length-2` won't panic + if parent_height == self.first_height + (self.length - 2) && child != self.last { + return Err(SplitError::ChildEndMismatch); + } + let first_part_length = parent_height - self.first_height + 1; + let second_multiples = self.multiples.split_off(parent_height + 1); + let first_part = Extension { + first: self.first, + first_height: self.first_height, + last: parent, + length: first_part_length, + multiples: self.multiples, + }; + let second_part = Extension { + first: child, + first_height: parent_height + 1, + last: self.last, + length: self + .length + .checked_sub(first_part_length) + .expect("Incorrect source length or some height"), + multiples: second_multiples, + }; + Ok((first_part, second_part)) + } + + pub fn first_event(&self) -> &event::Hash { + &self.first + } + + pub fn first_height(&self) -> &usize { + &self.first_height + } + + pub fn last_event(&self) -> &event::Hash { + &self.last + } + + pub fn length(&self) -> &usize { + &self.length + } + + pub fn multiples(&self) -> &Multiples { + &self.multiples + } + + pub fn submultiple(&self) -> u8 { + self.multiples.submultiple() + } +} + +#[cfg(test)] +pub mod test_utils { + use hex_literal::hex; + + use crate::algorithm::event; + + use super::Extension; + + pub const TEST_HASH_A: event::Hash = event::Hash::from_array( + hex![ + "1c09ecaba3131425e5f04afb9e6ea029c363cdfbb17a04aff4946847d20bd85be6dbd9529a9b5bea3d63c967645ce28891e9994844fc6e0fdd0468d60fdf0300" + ] + ); + pub const TEST_HASH_B: event::Hash = event::Hash::from_array( + hex![ + "66b4d625d5729f5a36fd918fbbda2cd38f636743708d489f9a35d0a62e7ca319b9db7939fbd129d0e8a3b4e00586acc88439e2bb7f9ba2beada06f1c34a6c065" + ] + ); + pub const TEST_HASH_C: event::Hash = event::Hash::from_array( + hex![ + "65a3247180d90327a35f3662920336feb5e9487630294cdeb08ca25720998633ff95108c950200453ccb2ace1a4c774f4ae4887203900506576b38dd7fe93fd3" + ] + ); + pub const TEST_HASH_D: event::Hash = event::Hash::from_array( + hex![ + "f537c5c9cf69000588d0bb69b835b7f3d062540e981acb82d748eeb102ad2a3b82cc228dba870fd0da5a9e7c5bf2669d3cb852520838599ecb52230ed15be1f4" + ] + ); + pub const TEST_HASH_E: event::Hash = event::Hash::from_array( + hex![ + "f660614747149b2b9d324b503891d923bf96626f7cc8e0a5c2bc90e2803105d68e2710cd986b356626d067ef15b52af4caf29085e3ee8925104c3982020eb991" + ] + ); + pub const TEST_HASH_F: event::Hash = event::Hash::from_array( + hex![ + "45d854c1bb52aa932940c6d80662961301f96f46d7f7fc9b5fc0a17d12d073fdc581dab54ee1e414a562ce354c74b2994935e4a8a843040336122add8e0a7086" + ] + ); + // For debugging + #[allow(unused)] + pub const NAMES: [(event::Hash, &str); 6] = [ + (TEST_HASH_A, "A"), + (TEST_HASH_B, "B"), + (TEST_HASH_C, "C"), + (TEST_HASH_D, "D"), + (TEST_HASH_E, "E"), + (TEST_HASH_F, "F"), + ]; + + pub fn sample_extension() -> Extension { + let mut ext = Extension::from_event_with_submultiple(TEST_HASH_A, 0, 3).unwrap(); + ext.push_event(TEST_HASH_B); + ext.push_event(TEST_HASH_C); + ext.push_event(TEST_HASH_D); + ext.push_event(TEST_HASH_E); + ext.push_event(TEST_HASH_F); + ext + } +} + +#[cfg(test)] +mod tests { + use itertools::Itertools; + use multiples::NotMultiple; + + use super::*; + use test_utils::*; + + #[test] + fn multiples_construct() { + let mut m = multiples::Multiples::<&str>::new(3).unwrap(); + m.try_insert(0, &"a").unwrap(); + m.try_insert(3, &"b").unwrap(); + m.try_insert(6, &"c").unwrap(); + // should work in any order + m.try_insert(12, &"e").unwrap(); + m.try_insert(9, &"d").unwrap(); + assert_eq!(m.try_insert(1, &"fail"), Err(NotMultiple)); + assert_eq!( + m.entries().collect_vec(), + vec![(&0, &"a"), (&3, &"b"), (&6, &"c"), (&9, &"d"), (&12, &"e"),] + ); + } + + #[test] + fn multiples_splits_off() { + fn construct_mul() -> Multiples<&'static str> { + let mut m = multiples::Multiples::<&str>::new(3).unwrap(); + let entries = [(0, &"a"), (3, &"b"), (6, &"c"), (9, &"d"), (12, &"e")]; + for (index, item) in entries { + m.try_insert(index, item) + .expect(&format!("Failed to insert {index}, \"{item}\"")); + } + m + } + let mut m = construct_mul(); + let m2 = m.split_off(9); + assert_eq!( + m.entries().collect_vec(), + vec![(&0, &"a"), (&3, &"b"), (&6, &"c"),] + ); + assert_eq!(m2.entries().collect_vec(), vec![(&9, &"d"), (&12, &"e"),]); + + let mut m = construct_mul(); + let m2 = m.split_off(7); + assert_eq!( + m.entries().collect_vec(), + vec![(&0, &"a"), (&3, &"b"), (&6, &"c"),] + ); + assert_eq!(m2.entries().collect_vec(), vec![(&9, &"d"), (&12, &"e"),]); + } + + #[test] + fn extension_constructs() { + let ext = sample_extension(); + + assert_eq!( + ext.multiples().entries().collect_vec(), + vec![(&0, &TEST_HASH_A), (&3, &TEST_HASH_D),] + ); + assert_eq!(ext.first_event(), &TEST_HASH_A); + assert_eq!(ext.last_event(), &TEST_HASH_F); + assert_eq!(ext.first_height(), &0); + assert_eq!(ext.length(), &6); + } + + #[test] + fn extension_splits_off() { + fn validate_ext( + ext: &Extension, + multiples_entries: Vec<(&usize, &event::Hash)>, + first: &event::Hash, + last: &event::Hash, + first_height: usize, + length: usize, + ) { + assert_eq!(ext.multiples().entries().collect_vec(), multiples_entries); + assert_eq!(ext.first_event(), first); + assert_eq!(ext.last_event(), last); + assert_eq!(ext.first_height(), &first_height); + assert_eq!(ext.length(), &length); + } + + let ext = sample_extension(); + + // split at B-C + let (ext_before, ext_after) = ext.clone().split_at(TEST_HASH_B, 1, TEST_HASH_C).unwrap(); + validate_ext( + &ext_before, + vec![(&0, &TEST_HASH_A)], + &TEST_HASH_A, + &TEST_HASH_B, + 0, + 2, + ); + validate_ext( + &ext_after, + vec![(&3, &TEST_HASH_D)], + &TEST_HASH_C, + &TEST_HASH_F, + 2, + 4, + ); + + // split at C-D + let (ext_before, ext_after) = ext.clone().split_at(TEST_HASH_C, 2, TEST_HASH_D).unwrap(); + validate_ext( + &ext_before, + vec![(&0, &TEST_HASH_A)], + &TEST_HASH_A, + &TEST_HASH_C, + 0, + 3, + ); + validate_ext( + &ext_after, + vec![(&3, &TEST_HASH_D)], + &TEST_HASH_D, + &TEST_HASH_F, + 3, + 3, + ); + + // split at D-E + let (ext_before, ext_after) = ext.clone().split_at(TEST_HASH_D, 3, TEST_HASH_E).unwrap(); + validate_ext( + &ext_before, + vec![(&0, &TEST_HASH_A), (&3, &TEST_HASH_D)], + &TEST_HASH_A, + &TEST_HASH_D, + 0, + 4, + ); + validate_ext(&ext_after, vec![], &TEST_HASH_E, &TEST_HASH_F, 4, 2); + } + + #[test] + fn extension_errors_returned() { + let ext = sample_extension(); + + fn check_state(ext: &Extension) { + assert_eq!( + ext.multiples().entries().collect_vec(), + vec![(&0, &TEST_HASH_A), (&3, &TEST_HASH_D),] + ); + assert_eq!(ext.first_event(), &TEST_HASH_A); + assert_eq!(ext.last_event(), &TEST_HASH_F); + assert_eq!(ext.first_height(), &0); + assert_eq!(ext.length(), &6); + } + + // Check state just in case + check_state(&ext); + + assert_eq!( + ext.clone().split_at(TEST_HASH_B, 0, TEST_HASH_C), + Err(SplitError::ParentStartMismatch) + ); + assert_eq!( + ext.clone().split_at(TEST_HASH_E, 4, TEST_HASH_D), + Err(SplitError::ChildEndMismatch) + ); + assert_eq!( + ext.clone().split_at(TEST_HASH_E, 4, TEST_HASH_D), + Err(SplitError::ChildEndMismatch) + ); + } +} diff --git a/src/algorithm/datastructure/peer_index/mod.rs b/src/algorithm/datastructure/peer_index/mod.rs index 440bb54..bc43d42 100644 --- a/src/algorithm/datastructure/peer_index/mod.rs +++ b/src/algorithm/datastructure/peer_index/mod.rs @@ -1,28 +1,35 @@ -use std::collections::{HashMap, HashSet}; +use std::{collections::HashMap, num::NonZeroU8}; use derive_getters::Getters; use thiserror::Error; use crate::PeerId; -use self::fork_tracking::ForkIndex; -use super::{event, EventIndex}; +use self::fork_tracking::{ForkIndex, ForkIndexIter, LeafPush, LookupIndexInconsistency}; +use super::event; pub mod fork_tracking; +pub type EventIndex = HashMap; + pub type PeerIndex = HashMap; #[derive(Getters)] pub struct PeerIndexEntry { origin: event::Hash, - authored_events: EventIndex<()>, + /// Use `add_latest` for insertion. + /// + /// Value is height of the event from genesis if considering self parent/self + /// child relationship. Genesis (should) have height 0. + /// + /// In other words, height is a distance from genesis through `self_parent`s + authored_events: EventIndex, /// Forks authored by the peer that we've observed. Forks are events /// that have the same `self_parent`. /// /// Represented by event hashes that have multiple self children /// (children authored by the same peer) fork_index: ForkIndex, - latest_event: event::Hash, } #[derive(Debug, Error, PartialEq)] @@ -38,23 +45,31 @@ pub enum Error { EventAlreadyKnown, #[error("Events found via the lookup contain reference unknown events")] InconsistentLookup, + #[error("State of the index and lookup state are inconsistent")] + LookupIndexInconsistency(#[from] LookupIndexInconsistency), + #[error("Pushing leaf (non-forking) event failed")] + LeafPush(#[from] LeafPush), } impl PeerIndexEntry { - pub fn new(genesis: event::Hash) -> Self { + pub fn new(genesis: event::Hash, fork_index_submultiple: NonZeroU8) -> Self { Self { origin: genesis.clone(), - authored_events: HashMap::from([(genesis.clone(), ())]), - fork_index: ForkIndex::new(), - latest_event: genesis.clone(), + authored_events: HashMap::from([(genesis.clone(), 0)]), + fork_index: ForkIndex::new(genesis, fork_index_submultiple), } } - pub fn add_event( + pub fn add_event<'a, F, TPayload>( &mut self, - self_parent: &event::Event, + self_parent: event::Hash, event: event::Hash, - ) -> Result<(), Error> { + event_lookup: F, + ) -> Result<(), Error> + where + F: Fn(&event::Hash) -> Option<&'a event::Event>, + TPayload: 'a, + { // First do all checks, only then apply changes, to keep the state consistent // TODO: represent it in some way in the code. E.g. by creating 2 functions @@ -65,62 +80,99 @@ impl PeerIndexEntry { if self.authored_events.contains_key(&event) { return Err(Error::EventAlreadyKnown); } + let parent_event = event_lookup(&self_parent).ok_or(Error::UnknownParent)?; + let parent_height = self + .authored_events + .get(&self_parent) + .ok_or(Error::UnknownParent)?; // Consider self children without the newly added event (just in case) - let parent_self_children: event::SelfChild = self_parent + let parent_self_children: event::SelfChild = parent_event .children .self_child .clone() .with_child_removed(&event); match parent_self_children { - event::SelfChild::HonestParent(None) => {} - event::SelfChild::HonestParent(Some(_)) | event::SelfChild::ForkingParent(_) => { - self.fork_index.track_fork(self_parent, event); + event::SelfChild::HonestParent(None) => { + // It is a "leaf" in terms of forking, so we just add it to the index + // Makes the final check and starts updating the state + self.fork_index.push_event(event.clone(), &self_parent)?; + } + event::SelfChild::HonestParent(Some(firstborn)) => { + // It is the second self child, so we creating a new fork + let identifier = Self::find_fork_identifier(&self_parent, event_lookup)?; + // completing the checks and starting to update the state + self.fork_index.add_new_fork( + &identifier, + self_parent, + *parent_height, + event.clone(), + firstborn, + )?; + } + event::SelfChild::ForkingParent(_) => { + // Its parent already has forks, so we just add another one + let identifier = Self::find_fork_identifier(&self_parent, event_lookup)?; + // completing the checks and starting to update the state + self.fork_index + .add_branch_to_fork(&identifier, event.clone()) + .map_err(|e| ::from(e))?; } } - self.authored_events.insert(event, ()); - self.latest_event = event; + self.authored_events.insert(event, parent_height + 1); Ok(()) } -} -#[cfg(test)] -mod tests { - use std::time::Duration; - - // Returns the index and all_events tracker - fn construct_peer_index( - events: &[(event::Event)], - ) -> (PeerIndexEntry, HashMap>) { - let events = events.into_iter(); - let mut all_events = HashMap::new(); - let genesis = events.next().expect("event list must be nonempty"); - all_events.insert(genesis.hash().clone(), *genesis.clone()); - let mut peer_index = PeerIndexEntry::new(genesis.hash().clone()); - for event in events { - all_events.insert(event.hash().clone(), *event.clone()); - let self_parent = if let event::Kind::Regular(p) = event.parents() { - p.self_parent - } else { - panic!("2 geneses, can't add to the index"); - }; - all_events - .get_mut(&self_parent) - .unwrap() + fn find_fork_identifier<'a, F, TPayload>( + target: &event::Hash, + event_lookup: F, + ) -> Result + where + F: Fn(&event::Hash) -> Option<&'a event::Event>, + TPayload: 'a, + { + let mut this_event = event_lookup(target).ok_or(Error::InconsistentLookup)?; + let mut prev_event = match this_event.parents() { + event::Kind::Genesis => return Ok(target.clone()), + event::Kind::Regular(parents) => { + event_lookup(&parents.self_parent).ok_or(Error::InconsistentLookup)? + } + }; + while let event::Kind::Regular(parents) = prev_event.parents() { + let prev_self_children: event::SelfChild = prev_event .children .self_child - .add_child(event.hash().clone()); - peer_index - .add_event( - all_events - .get(&self_parent) - .expect("unknown event listed as peer"), - event.hash().clone(), - ) - .unwrap(); + .clone() + .with_child_removed(this_event.hash()); + if let event::SelfChild::ForkingParent(_) = prev_self_children { + // `prev_event` has forking children, thus the child is the identifier we want + return Ok(this_event.hash().clone()); + } + this_event = prev_event; + prev_event = event_lookup(&parents.self_parent).ok_or(Error::InconsistentLookup)? } - (peer_index, all_events) + // `prev_event` is genesis and thus it's the identifier + Ok(prev_event.hash().clone()) } + /// Latest events across all known forks of the peer. + /// If peer is honest, it will contain a single event + pub fn latest_events(&self) -> Vec<&event::Hash> { + self.fork_index.leaf_events() + } + + pub fn forks<'a>(&'a self) -> ForkIndexIter<'a> { + self.fork_index.iter() + } + + pub fn forks_submultiple(&self) -> NonZeroU8 { + *self.fork_index.submultiple() + } +} + +#[cfg(test)] +mod tests { + use std::time::Duration; + use super::*; #[test] fn test_peer_index_constructs() { @@ -140,7 +192,7 @@ mod tests { event::Event::new((), event::Kind::Genesis, peer, start_time.as_secs().into()).unwrap(); let a_hash = event_a.hash().clone(); all_events.insert(a_hash.clone(), event_a); - let mut index = PeerIndexEntry::new(a_hash.clone()); + let mut index = PeerIndexEntry::new(a_hash.clone(), NonZeroU8::new(3u8).unwrap()); let event_b = event::Event::new( (), @@ -162,7 +214,7 @@ mod tests { .self_child .add_child(b_hash.clone()); index - .add_event(all_events.get(&a_hash).expect("aboba"), b_hash.clone()) + .add_event(a_hash.clone(), b_hash.clone(), |h| all_events.get(h)) .unwrap(); let event_c = event::Event::new( @@ -185,7 +237,7 @@ mod tests { .self_child .add_child(c_hash.clone()); index - .add_event(all_events.get(&b_hash).expect("akeke"), c_hash.clone()) + .add_event(b_hash.clone(), c_hash.clone(), |h| all_events.get(h)) .unwrap(); let event_d = event::Event::new( @@ -208,7 +260,7 @@ mod tests { .self_child .add_child(d_hash.clone()); index - .add_event(all_events.get(&b_hash).expect("akeke"), d_hash.clone()) + .add_event(b_hash.clone(), d_hash.clone(), |h| all_events.get(h)) .unwrap(); let event_e = event::Event::new( @@ -231,7 +283,7 @@ mod tests { .self_child .add_child(e_hash.clone()); index - .add_event(all_events.get(&b_hash).expect("akeke"), e_hash.clone()) + .add_event(b_hash.clone(), e_hash.clone(), |h| all_events.get(h)) .unwrap(); let event_f = event::Event::new( @@ -254,11 +306,7 @@ mod tests { .self_child .add_child(f_hash.clone()); index - .add_event(all_events.get(&a_hash).expect("akeke"), f_hash.clone()) + .add_event(a_hash.clone(), f_hash.clone(), |h| all_events.get(h)) .unwrap(); - - // check test for correctness - let events = vec![event_a, event_b, event_c, event_d, event_e, event_f]; - let (index_2, all_events_2) = construct_peer_index(&events); } } diff --git a/src/algorithm/datastructure/sync/jobs.rs b/src/algorithm/datastructure/sync/jobs.rs new file mode 100644 index 0000000..207a52c --- /dev/null +++ b/src/algorithm/datastructure/sync/jobs.rs @@ -0,0 +1,242 @@ +use std::collections::VecDeque; + +use tracing::warn; + +use crate::{ + algorithm::{ + datastructure::peer_index::{PeerIndex, PeerIndexEntry}, + event, EventKind, Signature, + }, + common::Graph, + PeerId, Timestamp, +}; + +use super::state::{CompressedKnownState, CompressedPeerState}; + +pub struct AddEvent { + pub signature: Signature, + pub payload: TPayload, + pub event_type: EventKind, + pub author: PeerId, + pub time_created: Timestamp, +} + +/// Sync jobs that need to be applied in order to achieve (at least) +/// the same knowledge as sender. +/// +/// "at least" - because the receiver might know some more data (mostly +/// technicality) +pub struct Jobs { + additions: Vec>, +} + +impl Jobs { + /// the result is topologically sorted, as we + /// require all parents to be known at the time + /// of event addition + pub fn generate( + self_graph: &crate::algorithm::datastructure::Graph, + reciever_state: &CompressedKnownState, + ) -> Self { + // Since what we need is topsort, we can construct a graph of all missing + // events (in reciever), with all relevant edges present (both for "parent" and + // "other parent" relations). + // + // This graph would be DAG, because it's a subgraph of another DAG. + // + // Then we can unleash all power of topological ordering at the graph. + + // 1-2. Get a graph of (potentially) events that are potentially missing on reciever + let missing_events = Self::missing_events_graph( + self_graph, + &self_graph.peer_index, + reciever_state, + |hash| self_graph.all_events.get(hash), + ); + + // 3. topologically sort them, send as jobs to perform + + todo!() + } + + fn missing_events_graph<'a, 'b, TGraphIn, TLookup>( + self_state: &'a TGraphIn, + self_index: &PeerIndex, + reciever_state: &CompressedKnownState, + event_lookup: TLookup, + ) -> Subgraph<'a, TGraphIn> + where + TGraphIn: Graph, + TLookup: Fn(&event::Hash) -> Option<&'b event::Event>, + TPayload: 'b, + { + // 1. find first unknown events by the reciever (first events right after latest common events). + let starting_slice = Self::first_unknown_events(self_index, reciever_state, event_lookup); + // 2. construct a sub graph that starts from events found in 1. + Subgraph::new(self_state, starting_slice.into()) + } + + /// Earliest events that might be unknown by the reciever + fn first_unknown_events<'a, TLookup>( + self_index: &PeerIndex, + reciever_known_state: &CompressedKnownState, + event_lookup: TLookup, + ) -> Vec + where + TLookup: Fn(&event::Hash) -> Option<&'a event::Event>, + TPayload: 'a, + { + let mut result = Vec::with_capacity(self_index.capacity()); + for (id_known_by_reciever, compressed_state) in reciever_known_state.as_vec() { + let Some(peer_index) = self_index.get(id_known_by_reciever) else { + continue; + }; + result.extend(Self::first_unknown_peer_events( + peer_index, + compressed_state, + &&event_lookup, + )); + } + result + } + + /// Earliest events created by a certain peer that might be unknown by the reciever. + /// Might be a singleton set of origin event. + fn first_unknown_peer_events<'a, TLookup>( + self_index: &PeerIndexEntry, + reciever_state: &CompressedPeerState, + event_lookup: TLookup, + ) -> Vec + where + TLookup: Fn(&event::Hash) -> Option<&'a event::Event>, + TPayload: 'a, + { + let origin = self_index.fork_index().origin(); + if self_index.fork_index().origin() != reciever_state.origin() { + return vec![origin.clone()]; + } + // Now BFS/DFS but it stops when an event is not known by the reciever + // (and store it) + Self::first_unknown_search( + self_index, + reciever_state, + event_lookup, + vec![origin.clone()], + ) + } + + /// Find first unknown (to reciever) events + fn first_unknown_search<'a, TLookup>( + self_index: &PeerIndexEntry, + reciever_state: &CompressedPeerState, + event_lookup: TLookup, + start_events: Vec, + ) -> Vec + where + TLookup: Fn(&event::Hash) -> Option<&'a event::Event>, + TPayload: 'a, + { + fn next_events_after<'a, TLookup, TPayload>( + event: &event::Hash, + event_lookup: TLookup, + ) -> Vec + where + TLookup: Fn(&event::Hash) -> Option<&'a event::Event>, + TPayload: 'a, + { + warn!("Recieved submultiple does not equal to ours. This leads to inefficient synchronization."); + event_lookup(&event) + .expect("Could not find events tracked in fork index. It is inconsistent with general graph state.") + .children + .self_child + .clone() + .into() + } + + let mut to_visit = start_events; + let mut first_unknown = vec![]; + while let Some(next) = to_visit.pop() { + // let branching_events = self_index + // .fork_index() + // .forks() + // .get(&next_identifier) + // .map(|fork| fork.forks().clone()) + // .unwrap_or(vec![]); + let self_extension = self_index + .fork_index() + .find_extension(&next) + .expect("Inconsistent fork_index state (or bfs is broken)"); + let Some(reciever_entry) = reciever_state.entries().get(&next) else { + // Reciever doesn't know the extension start. + // + // Since the event is in `to_visit`, its self-parent is known + // to the receiver. Thus it's one of the first unknowns + first_unknown.push(next); + // no need to go further here + continue; + }; + // at this point we checked that the reciever knows `next` + let reciever_knows_extension_end = reciever_state + .entries_starts() + .contains_key(self_extension.last_event()); + if !reciever_knows_extension_end { + if reciever_entry.section().submultiple() == self_extension.submultiple().into() { + // find latest known event in the extension + let mut latest_known = &next; + for (height, reciever_intermediate) in reciever_entry.section().intermediates() + { + if let Some(self_intermediate) = + self_extension.multiples().get_by_height(*height) + { + if reciever_intermediate == self_intermediate { + latest_known = reciever_intermediate; + continue; + } + } + break; + } + // add its child(ren?); should be only one child btw + let mut after_known = next_events_after(&latest_known, &event_lookup); + first_unknown.append(&mut after_known); + } else { + let mut next_next = next_events_after(&next, &event_lookup); + first_unknown.append(&mut next_next); + } + continue; + } + // events in the extension are fully present in the peer + if let Some(next_extension_forks) = self_index.fork_index().forks().get(&next) { + // there are more events after this extension, need to visit them + to_visit.extend_from_slice(next_extension_forks.forks()) + } + // if it was the leaf, just move on + } + first_unknown + } +} + +pub struct Subgraph<'a, G: Graph> { + super_graph: &'a G, + to_visit: VecDeque, +} + +impl<'a, G: Graph> Subgraph<'a, G> { + pub fn new(super_graph: &'a G, starting_nodes: VecDeque) -> Self { + Self { + super_graph, + to_visit: starting_nodes, + } + } +} + +impl<'a, G> Graph for Subgraph<'a, G> +where + G: Graph, +{ + type NodeIdentifier = G::NodeIdentifier; + type NodeIdentifiers = G::NodeIdentifiers; + + fn neighbors(&self, node: &Self::NodeIdentifier) -> Self::NodeIdentifiers { + self.super_graph.neighbors(node) + } +} diff --git a/src/algorithm/datastructure/sync/mod.rs b/src/algorithm/datastructure/sync/mod.rs index 8b13789..f7ebc4d 100644 --- a/src/algorithm/datastructure/sync/mod.rs +++ b/src/algorithm/datastructure/sync/mod.rs @@ -1 +1,16 @@ +//! Synchronization with another peer. +//! +//! We call a "reciever" a peer that recieves the graph updates +//! (and updates its state accordingly) and a "sender" - a peer +//! that shares its state to reciever. +//! +//! The sync is done in 3 steps: +//! 1. reciever sends compressed version of its state +//! 2. sender figures out what is missing and sends the missing +//! stuff to reciever +//! 3. reciever recieves (lol), verifies, and applies needed changes. +//! +// isn't it the same as https://github.com/bcpierce00/unison ? +pub mod jobs; +pub mod state; diff --git a/src/algorithm/datastructure/sync/state.rs b/src/algorithm/datastructure/sync/state.rs new file mode 100644 index 0000000..97e0b10 --- /dev/null +++ b/src/algorithm/datastructure/sync/state.rs @@ -0,0 +1,462 @@ +//! Generation of data that allows more efficient sync. +//! +//! In particular, generates compressed version of known state. +//! It means state with only some event hashes present. In best case of +//! honest peers (no forking) it includes only log(N) of event hashes, +//! where N is number of events created by the peer. +use std::collections::HashMap; + +use derive_getters::Getters; +use itertools::unfold; +use thiserror::Error; + +use crate::{algorithm::event, PeerId}; + +use crate::algorithm::datastructure::peer_index::{ + fork_tracking::{Extension, ForkIndexEntry}, + PeerIndex, PeerIndexEntry, +}; + +#[derive(Debug, Error, PartialEq)] +#[error("Submultipliers within fork index differ")] +pub struct SubmultiplierMismatch; + +pub struct CompressedKnownState { + peer_states: Vec<(PeerId, CompressedPeerState)>, +} + +impl TryFrom<&PeerIndex> for CompressedKnownState { + type Error = SubmultiplierMismatch; + + fn try_from(value: &PeerIndex) -> Result { + let peer_states: Result, _> = value + .into_iter() + .map(|(id, entry)| CompressedPeerState::try_from(entry).map(|s| (*id, s))) + .collect(); + let peer_states = peer_states?; + Ok(Self { peer_states }) + } +} + +impl CompressedKnownState { + pub fn as_vec(&self) -> &Vec<(PeerId, CompressedPeerState)> { + &self.peer_states + } +} + +#[derive(Getters)] +pub struct CompressedPeerState { + origin: event::Hash, + // Indexed by starting event + entries: HashMap, + // Start of entries in `data` by ends of corresponding + // sections + entries_starts: HashMap, +} + +impl TryFrom<&PeerIndexEntry> for CompressedPeerState { + type Error = SubmultiplierMismatch; + + fn try_from(value: &PeerIndexEntry) -> Result { + let submul = value.forks_submultiple(); + let data_res: Result, _> = value + .forks() + .map(|entry| { + let section = ::from(entry.extension()); + if section.submultiple != u8::from(submul).into() { + return Err(SubmultiplierMismatch); + } + let index = section.first.clone(); + let new_entry = match entry { + ForkIndexEntry::Fork(fork) => CompressedStateEntry::Fork { + events: section, + forks: fork.subsequent_forks().clone(), + }, + ForkIndexEntry::Leaf(_) => CompressedStateEntry::Leaf(section), + }; + Ok((index, new_entry)) + }) + .collect(); + let data = data_res?; + let data_starts: HashMap<_, _> = data + .iter() + .map(|(start, entry)| (entry.section().last.clone(), start.clone())) + .collect(); + Ok(Self { + origin: value.origin().clone(), + entries: data, + entries_starts: data_starts, + }) + } +} + +pub enum CompressedStateEntry { + /// Sequence of events with fork at the end (in case of dishonest author) + Fork { + events: EventsSection, + /// First elements of each child + forks: Vec, + }, + /// Just a sequence of events + Leaf(EventsSection), +} + +impl CompressedStateEntry { + pub fn section(&self) -> &EventsSection { + match self { + CompressedStateEntry::Fork { events, .. } => events, + CompressedStateEntry::Leaf(events) => events, + } + } +} + +/// Helps to roughly figure out how many events are known in a chain (and to send) +/// without transferring all of the events. +#[derive(Debug, Clone, PartialEq)] +pub struct EventsSection { + first: event::Hash, + first_height: usize, + last: event::Hash, + length: usize, + intermediate_threshold: u32, + submultiple: u32, + /// `(index, hash)` + /// + /// Empty if `length` < `intermediate_threshold` + /// + /// if `length` >= `intermediate_threshold`, the first element is the first + /// whose height `H` is a multiple of `submultiple` (`S`), the next one is with height + /// `H+S`, then `H+2S, H+4S, H+8S, ...` until the end of the section is reached + // TODO: reverse this, I wanted H-S, H-2S, H-4S, H-8S... until the start of the section + intermediates: Vec<(usize, event::Hash)>, +} + +impl EventsSection { + // temp to update tests later?? + pub fn intermediates(&self) -> &Vec<(usize, event::Hash)> { + &self.intermediates + } + + pub fn submultiple(&self) -> u32 { + self.submultiple + } +} + +impl From<&Extension> for EventsSection { + fn from(value: &Extension) -> Self { + let submul = value.submultiple(); + let threshold = submul.saturating_div(2).saturating_add(submul); + // Take multiples at positions + // 0, 1, 2, 4, 8, 16, 32, ... until none left + let intermediates = if value.length() >= &(threshold as usize) { + let powers_of_2 = unfold(1, |x| { + let next = *x; + *x *= 2; + Some(next) + }); + // also 0 at the start since we index from zero + let indexes = [0].into_iter().chain(powers_of_2); + indexes + .map(|i| value.multiples().get_ith(i)) + .take_while(|x| x.is_some()) + .filter_map(|x| x) + .collect() + } else { + vec![] + }; + let intermediates = intermediates + .into_iter() + .map(|(a, b)| (a.clone(), b.clone())) + .collect(); + Self { + first: value.first_event().clone(), + first_height: value.first_height().clone(), + last: value.last_event().clone(), + length: value.length().clone(), + intermediate_threshold: threshold.into(), + submultiple: submul.into(), + intermediates, + } + } +} + +#[cfg(test)] +mod tests { + use std::{collections::HashSet, num::NonZeroU8, time::Duration}; + + use hex_literal::hex; + + use super::*; + + const FAKE_HASHES: [[u8; 64]; 15] = [ + hex!["411f1010e94a282a038a3fa1d18a6956361d9918be2b827308042764299ea995ccfdcb6f2d49187c869b9ed87560e136dc7b6671a37909617432aa8595106a8b"], + hex!["0aca064a733c3d97be52cb47fb8a336823da4ad7dff114352f17f78d1a16c6b0fa67260a84b4913fbcad55357599fc3d943172b400b6551692857004bda4ff87"], + hex!["ec10b0e7a15b1beb12e87ebe1c461b2ee276f5e151545d8992e1daaa8f61e1d46c913baa90854a34e755bd2545631ff3f300bffe541bff6adb240ab43d0ffbb7"], + hex!["43632b90d55f060ee55943350d324dce6d3c40d826f9279ef5a5a688380a3227660ce295b4c6c3e1d50c918b7e2eab18613c7ed5cd521c172b7cc20db5a9e847"], + hex!["e3ba6dbad32d3ca4c186aa40cbe294181c4cdb77ba95204ac97d7d79635a139a967ef7f09e72737eb10bdd0776784726efcd07e0b7143966990c22d5d97083e0"], + hex!["fba6f9b0a0ea6785abc7a368227604c668a7e3ca2dca6c95998729b867489509247a27786a26c7ab33b13ef1e020eb4caa66780d8caa03e47e2238f9f0eff69d"], + hex!["051207ac1bdb6fca1f0f784609ffd213c7a95730ee9ef209fb7828d94eb6898d09c99c0a0c2e90fc23f19554ad9c5f2fa00aedc430fc54b8aa7e16fd7cc47830"], + hex!["62c8b3617313924ff319a39706524797eb8bfe70232b10f21aee8b1a5ca62d120de06b09d0facb4ad2ebc0462342e2e8497514ffc51345b50d26558a2dde53f9"], + hex!["819f8d3a5781660b111982f33cab86c3384148c3887654181b0fbe4fc25b59f005b5c8142d3c476954745ade577b34dd68c194c696ff6adad8aaded9341fdc44"], + hex!["2d28a3f1a77c924fe860f481f9b1388cb3dcc20486b0177299371c7463495e869d1f27721753f84ad59371cfa592860c7a75bae69c28dae7e49a5927b9135440"], + hex!["4a75eb97c22ec64b3659eb65f1f6350d39edafdb59a7966cc8c5553972ee7e61ba7d124e78b9cbacf3a76e151349352bec36e214ebc17df4221c4a3a01a09e4d"], + hex!["1530b07b6edf74fc9dea6929d2c801b3e9b197e365bd349db4ec7eec08927e0e537179e8028281851eeb8632203a42e86138aabe1710d9d16f6807bb09c1b000"], + hex!["55571800a2bcadebeba16b2cdd39f6e390482ee1cfb4550a4b21818173f875697ae841ad78ed8b94e3363a793f3e7a1f4b4f5b3d821b67f81869413b306d2a2d"], + hex!["75305afe9bf50e38c97e53d01ed69e6b596815dee321c26d1f12f5ec51e9c3009e303bdafa88e2f5887290c0f15f69e29888a70145632b6df100f3cabbbc23a6"], + hex!["e88814dfb0055827c5159f2b16d57f72c74294d1128dc47827ea613c48cabb44cd03d9d4443b384c4b675771a20e4a25e522a235cd36b6398268b09d3836e7c4"], + ]; + + #[test] + fn section_constructs_correctly() { + let fake_event_hashes_init = FAKE_HASHES.iter().map(|h| event::Hash::from_array(*h)); + // For 15 events and submultiple 3, the section should save hashes of + // events at heights 0, 3, 6, 12. + let section_1 = { + let mut fake_event_hashes = fake_event_hashes_init.clone(); + let mut ext = Extension::from_event_with_submultiple( + fake_event_hashes.next().unwrap().clone(), + 0, + 3, + ) + .unwrap(); + for h in fake_event_hashes { + ext.push_event(h); + } + EventsSection::from(&ext) + }; + // test for 13 events (at height 12 is the last one) + let section_2 = { + let mut fake_event_hashes = fake_event_hashes_init.clone().take(13); + let mut ext = Extension::from_event_with_submultiple( + fake_event_hashes.next().unwrap().clone(), + 0, + 3, + ) + .unwrap(); + for h in fake_event_hashes { + ext.push_event(h); + } + EventsSection::from(&ext) + }; + + let mut fake_event_hashes = fake_event_hashes_init.clone(); + let expected = vec![ + (0, fake_event_hashes.nth(0).unwrap()), + (3, fake_event_hashes.nth(2).unwrap()), + (6, fake_event_hashes.nth(2).unwrap()), + (12, fake_event_hashes.nth(5).unwrap()), + ]; + + assert_eq!(section_1.intermediates(), &expected); + + assert_eq!(section_2.intermediates(), &expected); + + // Start height is not necessary 0 + let section_3 = { + let mut fake_event_hashes = fake_event_hashes_init.clone().take(15); + let mut ext = Extension::from_event_with_submultiple( + fake_event_hashes.next().unwrap().clone(), + 11, + 3, + ) + .unwrap(); + for h in fake_event_hashes { + ext.push_event(h); + } + EventsSection::from(&ext) + }; + + let mut fake_event_hashes = fake_event_hashes_init.clone(); + let expected = vec![ + (12, fake_event_hashes.nth(1).unwrap()), + (15, fake_event_hashes.nth(2).unwrap()), + (18, fake_event_hashes.nth(2).unwrap()), + (24, fake_event_hashes.nth(5).unwrap()), + ]; + assert_eq!(section_3.intermediates(), &expected); + } + + #[test] + fn peer_state_constructs_correctly() { + // Let's do something like + // 1-2-3-4-5-6-7-8-9-10-11-12-13-14-15 + // / + // 0 + // \ + // 16 + + // arbitrary values + let peer = 3u64; + let start_time = Duration::from_secs(1674549572); + let mut all_events = HashMap::new(); + + // Since we work with actual events, we can't use our sample hashes. + + // init + let genesis_event = + event::Event::new((), event::Kind::Genesis, peer, start_time.as_secs().into()).unwrap(); + let genesis_hash = genesis_event.hash().clone(); + all_events.insert(genesis_hash.clone(), genesis_event.clone()); + let mut index = PeerIndexEntry::new(genesis_hash.clone(), NonZeroU8::new(3u8).unwrap()); + let mut tracked_events = vec![(0, genesis_hash.clone())]; + + // events 1..=13 + let mut prev_hash = genesis_hash.clone(); + for i in 1usize..=15 { + let event = event::Event::new( + (), + event::Kind::Regular(event::Parents { + self_parent: prev_hash.clone(), + // doesn't matter what to put here, we don't test it at all + other_parent: prev_hash.clone(), + }), + peer, + (start_time + Duration::from_secs(i.try_into().unwrap())) + .as_secs() + .into(), + ) + .unwrap(); + all_events + .get_mut(&prev_hash) + .unwrap() + .children + .self_child + .add_child(event.hash().clone()); + index + .add_event(prev_hash, event.hash().clone(), |h| all_events.get(h)) + .unwrap(); + prev_hash = event.hash().clone(); + if i % 3 == 0 { + tracked_events.push((i, event.hash().clone())); + } + all_events.insert(event.hash().clone(), event); + } + + // at this point it should track 0, 3, 6, 12 + // (as in `section_constructs_correctly`) + + let peer_state = CompressedPeerState::try_from(&index).unwrap(); + assert_eq!(peer_state.origin, genesis_hash); + let mut entries = peer_state.entries.iter(); + + let Some((start_hash, entry)) = entries.next() + else { + panic!("expected at least one entry to be produced"); + }; + assert_eq!(start_hash, &genesis_hash); + + let CompressedStateEntry::Leaf(ext) = entry + else { + panic!("no forks were inserted yet"); + }; + assert_eq!( + ext.intermediates(), + &vec![ + tracked_events[0].clone(), + tracked_events[1].clone(), + tracked_events[2].clone(), + tracked_events[4].clone() + ] + ); + + // the last 16'th event + + let event_16 = event::Event::new( + (), + event::Kind::Regular(event::Parents { + self_parent: genesis_hash.clone(), + // doesn't matter what to put here, we don't test it at all + other_parent: genesis_hash.clone(), + }), + peer, + (start_time + Duration::from_secs(16)).as_secs().into(), + ) + .unwrap(); + all_events + .get_mut(&genesis_hash) + .unwrap() + .children + .self_child + .add_child(event_16.hash().clone()); + index + .add_event(genesis_hash.clone(), event_16.hash().clone(), |h| { + all_events.get(h) + }) + .unwrap(); + all_events.insert(event_16.hash().clone(), event_16.clone()); + + // Now the largest section should track only 3, 6, and 12 + // (since 0 is in another section now) + + let peer_state = CompressedPeerState::try_from(&index).unwrap(); + assert_eq!(peer_state.origin, genesis_hash); + + let Some(entry) = peer_state.entries.get(&genesis_hash) + else { + panic!("should produce corresponding structure"); + }; + + let CompressedStateEntry::Fork { events: ext, forks } = entry + else { + panic!("we inserted a fork, so it should be tracked"); + }; + // less than 15, so none are tracked + assert_eq!(&ext.intermediates()[..], &vec![]); + + // Test the smaller leaf first (with event 16) + let mut forks = HashSet::<_>::from_iter(forks.into_iter()); + assert!(forks.remove(event_16.hash())); + + let Some(entry) = peer_state.entries.get(&event_16.hash()) + else { + panic!("should produce corresponding structure"); + }; + + let CompressedStateEntry::Leaf(ext) = entry + else { + panic!("no more forks on the branch"); + }; + assert_eq!(ext.intermediates(), &vec![]); + + // Now the larger one + let mut forks = forks.into_iter(); + let last_fork_hash = forks + .next() + .expect("forks must branch to at least 2 sections"); + assert!(matches!(forks.next(), None)); + let gen_children_without_16: Vec<_> = all_events + .get(&genesis_hash) + .unwrap() + .children + .self_child + .clone() + .with_child_removed(event_16.hash()) + .into(); + let mut gen_children_without_16 = gen_children_without_16.into_iter(); + let Some(event_1_hash) = gen_children_without_16.next() + else { + panic!("only inserted 2 children to genesis"); + }; + assert_eq!(gen_children_without_16.next(), None); + assert_eq!(last_fork_hash, &event_1_hash); + + let Some(entry) = peer_state.entries.get(&last_fork_hash) + else { + panic!("should produce corresponding structure"); + }; + + let CompressedStateEntry::Leaf(ext) = entry + else { + panic!("no more forks on the branch"); + }; + assert_eq!( + &ext.intermediates()[..], + &vec![ + tracked_events[1].clone(), + tracked_events[2].clone(), + tracked_events[3].clone(), + tracked_events[5].clone() + ] + ); + } +}