Skip to main content

ossa_core/store/
ecg.rs

1use daggy::petgraph::visit::{
2    Bfs, EdgeRef, IntoEdgeReferences, IntoNodeReferences, NodeRef, Reversed,
3};
4use daggy::stable_dag::StableDag;
5use daggy::Walker;
6use ossa_crdt::CRDT;
7use std::cmp::{self, Reverse};
8use std::collections::{BTreeMap, BTreeSet, VecDeque};
9use std::fmt::Debug;
10use std::marker::PhantomData;
11use tracing::{debug, error};
12
13pub mod v0;
14
15/// Trait that ECG headers (nodes?) must implement.
16pub trait ECGHeader {
17    type HeaderId: Ord + Copy + Debug;
18
19    // /// Type identifying operations that implements CausalOrder so that it can be used as CRDT::Time.
20    // type OperationId;
21
22    // /// Type associated with this header that implements ECGBody.
23    // type Body;
24
25    /// Return the parents ids of a node. If an empty slice is returned, the root node is the
26    /// parent.
27    fn get_parent_ids(&self) -> &[Self::HeaderId];
28
29    /// Computes the identifier of the header.
30    fn get_header_id(&self) -> Self::HeaderId;
31
32    fn validate_header(&self, header_id: Self::HeaderId) -> bool;
33
34    // // TODO: Can we return the following instead? impl Iterator<(T::Time, Item = T::Time)>
35    // fn zip_operations_with_time<T>(&self, body: Self::Body) -> Vec<(T::Time, T::Op)>
36    // where
37    //     T: CRDT + Sized,
38    //     <Self as ECGHeader>::Body: ECGBody<T>;
39
40    // /// Retrieve the times for each operation in this ECG header and body.
41    // // TODO: Can we return the following instead? impl Iterator<Item = T::Time>
42    // fn get_operation_times<T>(&self, body: &Self::Body) -> Vec<T::Time> where T: CRDT;
43}
44
45pub trait ECGBody<Op, SerializedOp> {
46    /// Header type associated with this body.
47    type Header: ECGHeader;
48
49    /// Create a new body from a vector of operations.
50    // fn new_body(operations: Vec<T::Op<CausalTime<T::Time>>>) -> Self;
51    fn new_body(operations: Vec<SerializedOp>) -> Self;
52
53    /// The operations in this body.
54    fn operations(
55        self,
56        header_id: <Self::Header as ECGHeader>::HeaderId,
57    ) -> impl Iterator<Item = Op>;
58
59    /// The number of operations in this body.
60    fn operations_count(&self) -> u8;
61
62    // fn new_header(&self, parents: BTreeSet<<Self::Header as ECGHeader>::HeaderId>) -> Self::Header
63    fn new_header(&self, parents: BTreeSet<<Self::Header as ECGHeader>::HeaderId>) -> Self::Header;
64    // fn new_header<HeaderId>(&self, parents: BTreeSet<HeaderId>) -> Self::Header
65    // where
66    //     // Self::Header: ECGHeader;
67    //     Self::Header: ECGHeader<HeaderId = HeaderId>;
68
69    // // TODO: Can we return the following instead? impl Iterator<(T::Time, Item = T::Time)>
70    // fn zip_operations_with_time(self, header: &Self::Header) -> Vec<(T::Time, T::Op<T::Time>)>;
71    // // where
72    // // T: CRDT + Sized,
73    // // <Self as ECGHeader>::Body: ECGBody<T>;
74
75    // /// Retrieve the times for each operation in this ECG header and body.
76    // // TODO: Can we return the following instead? impl Iterator<Item = T::Time>
77    // fn get_operation_times(&self, header: &Self::Header) -> Vec<T::Time>;
78}
79
80// Serialized ECG body
81pub(crate) type RawECGBody = Vec<u8>;
82
83#[derive(Clone, Debug)]
84pub(crate) struct NodeInfo<Header> {
85    /// The index of this node in the dependency graph.
86    graph_index: daggy::NodeIndex,
87    /// The (minimum) depth of this node in the dependency graph.
88    depth: u64,
89    /// The header this node is storing.
90    header: Header,
91    /// Raw serialized and potentially encrypted operations.
92    operations: RawECGBody,
93}
94
95impl<Header> NodeInfo<Header> {
96    pub(crate) fn header(&self) -> &Header {
97        &self.header
98    }
99
100    pub(crate) fn operations(&self) -> &Vec<u8> {
101        &self.operations
102    }
103}
104
105#[derive(Clone, Debug)]
106pub struct UntypedState<HeaderId, Header> {
107    dependency_graph: StableDag<HeaderId, ()>, // JP: Hold the operations? Depth? Do we need StableDag?
108
109    /// Nodes at the top of the DAG that depend on the initial state.
110    root_nodes: BTreeSet<HeaderId>,
111
112    /// Mapping from header ids to node indices.
113    node_info_map: BTreeMap<HeaderId, NodeInfo<Header>>,
114
115    /// Tips of the ECG (hashes of their headers).
116    /// Invariant: All of these headers are in `node_info_map`.
117    tips: BTreeSet<HeaderId>,
118}
119
120impl<HeaderId, Header> UntypedState<HeaderId, Header> {
121    pub fn tips(&self) -> &BTreeSet<HeaderId> {
122        &self.tips
123    }
124
125    pub fn contains(&self, h: &HeaderId) -> bool
126    where
127        HeaderId: Ord,
128    {
129        if let Some(_node_info) = self.node_info_map.get(h) {
130            true
131        } else {
132            false
133        }
134    }
135
136    pub fn get_parents(&self, h: &HeaderId) -> Option<Vec<HeaderId>>
137    where
138        HeaderId: Ord + Copy,
139    {
140        let node_info = self.node_info_map.get(h)?;
141        self.dependency_graph
142            .parents(node_info.graph_index)
143            .iter(&self.dependency_graph)
144            .map(|(_, parent_idx)| self.dependency_graph.node_weight(parent_idx).map(|i| *i))
145            .try_collect()
146    }
147
148    pub fn get_parents_with_depth(&self, h: &HeaderId) -> Option<Vec<(u64, HeaderId)>>
149    where
150        HeaderId: Ord + Copy,
151    {
152        let node_info = self.node_info_map.get(h)?;
153        self.dependency_graph
154            .parents(node_info.graph_index)
155            .iter(&self.dependency_graph)
156            .map(|(_, parent_idx)| {
157                self.dependency_graph
158                    .node_weight(parent_idx)
159                    .and_then(|parent_id| {
160                        self.node_info_map
161                            .get(parent_id)
162                            .map(|i| (i.depth, *parent_id))
163                    })
164            })
165            .try_collect()
166    }
167
168    /// Returns the children of the given node (with their depths) if it exists. If the returned array is
169    /// empty, the node is a leaf node.
170    pub fn get_children_with_depth(&self, h: &HeaderId) -> Option<Vec<(Reverse<u64>, HeaderId)>>
171    where
172        HeaderId: Ord + Copy,
173    {
174        let node_info = self.node_info_map.get(h)?;
175        self.dependency_graph
176            .children(node_info.graph_index)
177            .iter(&self.dependency_graph)
178            .map(|(_, child_idx)| {
179                self.dependency_graph
180                    .node_weight(child_idx)
181                    .and_then(|child_id| {
182                        self.node_info_map
183                            .get(child_id)
184                            .map(|i| (Reverse(i.depth), *child_id))
185                    })
186            })
187            .try_collect()
188    }
189
190    pub(crate) fn get_header_depth(&self, n: &HeaderId) -> Option<u64>
191    where
192        HeaderId: Ord,
193    {
194        self.node_info_map.get(n).map(|i| i.depth)
195    }
196
197    pub(crate) fn get_header(&self, n: &HeaderId) -> Option<&Header>
198    where
199        HeaderId: Ord,
200    {
201        self.node_info_map.get(n).map(|i| &i.header)
202    }
203
204    pub(crate) fn get_node(&self, n: &HeaderId) -> Option<&NodeInfo<Header>>
205    where
206        HeaderId: Ord,
207    {
208        self.node_info_map.get(n)
209    }
210
211    pub(crate) fn is_root_node(&self, h: &HeaderId) -> bool
212    where
213        HeaderId: Ord,
214    {
215        self.root_nodes.contains(h)
216    }
217
218    pub fn get_root_nodes_with_depth<'a>(
219        &'a self,
220    ) -> impl Iterator<Item = (Reverse<u64>, HeaderId)> + 'a
221    where
222        HeaderId: Copy,
223    {
224        // All root nodes have depth 1.
225        self.root_nodes.iter().map(|h| (Reverse(1), *h))
226    }
227}
228
229#[derive(Debug)]
230pub struct State<Header: ECGHeader, T> {
231    pub(crate) state: UntypedState<Header::HeaderId, Header>,
232
233    phantom: PhantomData<fn(T)>, // TODO: Delete T?
234}
235
236impl<Header: ECGHeader + Clone, T: CRDT> Clone for State<Header, T> {
237    fn clone(&self) -> Self {
238        let state = self.state.clone();
239        State {
240            state,
241            phantom: PhantomData,
242        }
243    }
244}
245
246impl<Header: ECGHeader, T: CRDT> State<Header, T> {
247    pub fn new() -> State<Header, T> {
248        let state = UntypedState {
249            dependency_graph: StableDag::new(),
250            root_nodes: BTreeSet::new(),
251            node_info_map: BTreeMap::new(),
252            tips: BTreeSet::new(),
253        };
254        State {
255            state,
256            phantom: PhantomData,
257        }
258    }
259
260    pub fn tips(&self) -> &BTreeSet<Header::HeaderId> {
261        &self.state.tips
262    }
263
264    pub fn is_root_node(&self, h: &Header::HeaderId) -> bool {
265        self.state.is_root_node(h)
266    }
267
268    /// Returns the parents of the given node (with their depths) if it exists. If the returned array is
269    /// empty, the node is a root node.
270    pub fn get_parents_with_depth(
271        &self,
272        h: &Header::HeaderId,
273    ) -> Option<Vec<(u64, Header::HeaderId)>> {
274        self.state.get_parents_with_depth(h)
275    }
276
277    /// Returns the parents of the given node if it exists. If the returned array is
278    /// empty, the node is a root node.
279    pub fn get_parents(&self, h: &Header::HeaderId) -> Option<Vec<Header::HeaderId>> {
280        self.state.get_parents(h)
281    }
282
283    /// Returns the children of the given node (with their depths) if it exists. If the returned array is
284    /// empty, the node is a leaf node.
285    pub fn get_children_with_depth(
286        &self,
287        h: &Header::HeaderId,
288    ) -> Option<Vec<(Reverse<u64>, Header::HeaderId)>> {
289        self.state.get_children_with_depth(h)
290    }
291
292    // pub fn get_children(&self, n:&HeaderId) -> Vec<HeaderId> {
293    //     unimplemented!{}
294    // }
295
296    pub fn contains(&self, h: &Header::HeaderId) -> bool {
297        self.state.contains(h)
298    }
299
300    pub fn get_header(&self, n: &Header::HeaderId) -> Option<&Header> {
301        self.state.get_header(n)
302    }
303
304    pub fn get_header_depth(&self, n: &Header::HeaderId) -> Option<u64> {
305        self.state.get_header_depth(n)
306    }
307
308    pub fn insert_header(&mut self, header: Header, operations: RawECGBody) -> bool {
309        let header_id = header.get_header_id();
310
311        // Validate header.
312        if !header.validate_header(header_id) {
313            debug!("Invalid header: {header_id:?}");
314            return false;
315        }
316
317        // Check that the header is not already in the dependency_graph.
318        if self.state.node_info_map.contains_key(&header_id) {
319            debug!("Already have header: {header_id:?}");
320            return false;
321        }
322
323        let parents = header.get_parent_ids();
324        let (parent_idxs, depth) = if parents.is_empty() {
325            let is_new_insert = self.state.root_nodes.insert(header_id);
326            // Check if it already existed.
327            if !is_new_insert {
328                // TODO: Log that the state is corrupt. Invariant violated that
329                // `root_nodes` is a subset of `node_idx_map.keys()`.
330                error!("Invariant violated: Header already existed in root_nodes but not in node_info_map: {header_id:?}");
331                return false;
332            }
333
334            // Update tips since the new node is a leaf.
335            self.state.tips.insert(header_id);
336
337            (vec![], 1)
338        } else {
339            let mut depth = u64::MAX;
340            if let Some(parent_idxs) = parents
341                .iter()
342                .map(|parent_id| {
343                    self.state.node_info_map.get(&parent_id).map(|i| {
344                        depth = cmp::min(depth, i.depth);
345                        i.graph_index
346                    })
347                })
348                .try_collect::<Vec<daggy::NodeIndex>>()
349            {
350                // If any parents were previously a tip, remove from tips.
351                parents.iter().for_each(|parent_id| {
352                    self.state.tips.remove(parent_id);
353                });
354                // Insert as tip since received headers must (currently) be a leaf.
355                self.state.tips.insert(header_id);
356
357                (parent_idxs, depth + 1)
358            } else {
359                // They sent us a header when we don't know the parent.
360                error!("They sent us a header but we don't know its parents: {header_id:?}");
361                return false;
362            }
363        };
364
365        // Insert node and store its index in `node_idx_map`.
366        // JP: We really want an `add_child` function that takes multiple parents.
367        let graph_index = self.state.dependency_graph.add_node(header_id);
368        let node_info = NodeInfo {
369            graph_index: graph_index.clone(),
370            depth,
371            header,
372            operations,
373        };
374        if let Err(_) = self.state.node_info_map.try_insert(header_id, node_info) {
375            // TODO: Should be unreachable. Log this.
376            error!("Unreachable: We already checked that it doesn't exist in node_info_map: {header_id:?}");
377            return false;
378        }
379
380        // Insert edges.
381        if let Err(_) = self.state.dependency_graph.add_edges(
382            parent_idxs
383                .into_iter()
384                .map(|parent_idx| (parent_idx, graph_index, ())),
385        ) {
386            // TODO: Unreachable? Log this.
387            error!("Invariant violated: Header already existed in dependency_graph but not in node_info_map: {header_id:?}");
388            return false;
389        }
390
391        true
392    }
393
394    /// Perform a BFS to check if `ancestor` is an ancestor of `descendent`. Returns `None` if
395    /// either header id is not in the graph.
396    fn is_ancestor_of(
397        &self,
398        ancestor: &Header::HeaderId,
399        descendent: &Header::HeaderId,
400    ) -> Option<bool> {
401        let anid = self.state.node_info_map.get(ancestor)?.graph_index;
402        let dnid = self.state.node_info_map.get(descendent)?.graph_index;
403
404        let mut queue = VecDeque::from([dnid]);
405        let mut visited = BTreeSet::from([dnid]);
406
407        while let Some(nid) = queue.pop_front() {
408            if nid == anid {
409                return Some(true);
410            }
411
412            for (_, pid) in self
413                .state
414                .dependency_graph
415                .parents(nid)
416                .iter(&self.state.dependency_graph)
417            {
418                if !visited.contains(&pid) {
419                    visited.insert(pid);
420                    queue.push_back(pid);
421                }
422            }
423        }
424
425        Some(false)
426    }
427
428    pub fn state(&self) -> &UntypedState<Header::HeaderId, Header> {
429        &self.state
430    }
431}
432
433/// Tests whether two ecg states have the same DAG.
434#[cfg(test)]
435pub(crate) fn equal_dags<Header: ECGHeader, T>(l: &State<Header, T>, r: &State<Header, T>) -> bool
436where
437    Header::HeaderId: Copy,
438{
439    let edges = |g: &StableDag<Header::HeaderId, ()>| {
440        g.edge_references()
441            .map(|e| {
442                let n1 = g.node_weight(e.source()).unwrap();
443                let n2 = g.node_weight(e.target()).unwrap();
444                (*n1, *n2)
445            })
446            .collect()
447    };
448    let nodes =
449        |g: &StableDag<Header::HeaderId, ()>| g.node_references().map(|n| *n.weight()).collect();
450
451    let node_set_left: BTreeSet<_> = nodes(&l.state.dependency_graph);
452    let node_set_right = nodes(&r.state.dependency_graph);
453    let edge_set_left: BTreeSet<_> = edges(&l.state.dependency_graph);
454    let edge_set_right = edges(&r.state.dependency_graph);
455
456    l.state.root_nodes == r.state.root_nodes
457        && l.state.tips == r.state.tips
458        && edge_set_left == edge_set_right
459        && node_set_left == node_set_right
460}
461
462#[cfg(test)]
463pub(crate) fn print_dag<Header: ECGHeader, T>(s: &State<Header, T>) {
464    use petgraph::dot::{Config, Dot};
465    use petgraph::stable_graph::StableDiGraph;
466
467    let mut g = s
468        .state
469        .dependency_graph
470        .map(|_i, n| format!("{:?}", n), |_i, e| e);
471
472    // Add root node.
473    let root = g.add_node("".to_string());
474    for n in &s.state.root_nodes {
475        g.add_edge(root, s.state.node_info_map[n].graph_index, &());
476    }
477
478    let g: StableDiGraph<_, _> = g.into();
479    let d = Dot::with_config(&g, &[Config::EdgeNoLabel]);
480    println!("{:?}", d);
481}