From 3f2ddf308b8bfaaf69e70efbc38e516fc4141a06 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Thu, 4 Aug 2022 19:24:29 -0400 Subject: [PATCH] Node comparison within a given DAG. --- src/lib.rs | 58 +++++++++++++++++++++++++++++++++++++--- src/proptest.rs | 1 - src/test.rs | 70 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 13d7abe..baa75ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,12 +19,18 @@ use node::Node; mod hash; mod node; +#[derive(PartialEq, Debug)] +pub enum NodeCompare { + After, + Before, + Equivalent, + Uncomparable, +} + #[derive(Debug)] pub enum EdgeError { NoSuchDependents, } -// TODO(jwall): In order to avoid copies it is probably smart to have some concept of -// a node pool. /// A Merkle-DAG implementation. This is a modification on the standard Merkle Tree data structure /// but instead of a tree it is a DAG and as a result can have multiple roots. A merkle-dag specifies @@ -98,7 +104,53 @@ where &self.nodes } - // TODO(jwall): How to specify a partial ordering for nodes in a graph? + /// Compare two nodes by id in the graph. If the left id is an ancestor of the right node + /// then `returns `NodeCompare::Before`. If the right id is an ancestor of the left node + /// then returns `NodeCompare::After`. If both id's are equal then the returns + /// `NodeCompare::Equivalent`. If neither id are parts of the same subgraph then returns + /// `NodeCompare::Uncomparable`. + pub fn compare(&self, left: &[u8; HASH_LEN], right: &[u8; HASH_LEN]) -> NodeCompare { + if left == right { + NodeCompare::Equivalent + } else { + // Is left node an ancestor of right node? + if self.search_graph(right, left) { + NodeCompare::Before + // is right node an ancestor of left node? + } else if self.search_graph(left, right) { + NodeCompare::After + } else { + NodeCompare::Uncomparable + } + } + } + + fn search_graph(&self, root_id: &[u8; HASH_LEN], search_id: &[u8; HASH_LEN]) -> bool { + if root_id == search_id { + return true; + } + let root_node = match self.get_node_by_id(root_id) { + Some(n) => n, + None => { + return false; + } + }; + let mut stack = vec![root_node]; + while !stack.is_empty() { + let node = stack.pop().unwrap(); + let deps = node.dependency_ids(); + for dep in deps { + if search_id == dep { + return true; + } + stack.push(match self.get_node_by_id(dep) { + Some(n) => n, + None => panic!("Invalid DAG STATE encountered"), + }) + } + } + return false; + } } impl Default for DAG diff --git a/src/proptest.rs b/src/proptest.rs index 00222c4..7efa9b6 100644 --- a/src/proptest.rs +++ b/src/proptest.rs @@ -19,7 +19,6 @@ use crate::DAG; fn edge_strategy(nodes_count: usize) -> impl Strategy, BTreeSet)> { prop::collection::vec(".*", 4..nodes_count).prop_flat_map(|payloads| { let nodes_len = payloads.len(); - // TODO(jwall): Generate valid DAGs // select a random set of payloads to be roots. // select a random set of non root payloads to be dependencies ( diff --git a/src/test.rs b/src/test.rs index e41349c..ef01c6c 100644 --- a/src/test.rs +++ b/src/test.rs @@ -83,3 +83,73 @@ fn test_adding_nodes_is_idempotent_regardless_of_dep_order() { assert_eq!(root_size, dag.get_roots().len()); assert_eq!(nodes_size, dag.get_nodes().len()); } + +#[test] +fn test_node_comparison_equivalent() { + let mut dag = DAG::<&str, DefaultHasher, 8>::new(); + let quake_node_id = dag.add_node("quake", BTreeSet::new()).unwrap(); + assert_eq!( + dag.compare(&quake_node_id, &quake_node_id), + NodeCompare::Equivalent + ); +} + +#[test] +fn test_node_comparison_before() { + let mut dag = DAG::<&str, DefaultHasher, 8>::new(); + let quake_node_id = dag.add_node("quake", BTreeSet::new()).unwrap(); + let qualm_node_id = dag + .add_node("qualm", BTreeSet::from([quake_node_id.clone()])) + .unwrap(); + let quell_node_id = dag + .add_node("quell", BTreeSet::from([qualm_node_id.clone()])) + .unwrap(); + assert_eq!( + dag.compare(&quake_node_id, &qualm_node_id), + NodeCompare::Before + ); + assert_eq!( + dag.compare(&quake_node_id, &quell_node_id), + NodeCompare::Before + ); +} + +#[test] +fn test_node_comparison_after() { + let mut dag = DAG::<&str, DefaultHasher, 8>::new(); + let quake_node_id = dag.add_node("quake", BTreeSet::new()).unwrap(); + let qualm_node_id = dag + .add_node("qualm", BTreeSet::from([quake_node_id.clone()])) + .unwrap(); + let quell_node_id = dag + .add_node("quell", BTreeSet::from([qualm_node_id.clone()])) + .unwrap(); + assert_eq!( + dag.compare(&qualm_node_id, &quake_node_id), + NodeCompare::After + ); + assert_eq!( + dag.compare(&quell_node_id, &quake_node_id), + NodeCompare::After + ); +} + +#[test] +fn test_node_comparison_no_shared_graph() { + let mut dag = DAG::<&str, DefaultHasher, 8>::new(); + let quake_node_id = dag.add_node("quake", BTreeSet::new()).unwrap(); + let qualm_node_id = dag.add_node("qualm", BTreeSet::new()).unwrap(); + let quell_node_id = dag.add_node("quell", BTreeSet::new()).unwrap(); + assert_eq!( + dag.compare(&qualm_node_id, &quake_node_id), + NodeCompare::Uncomparable + ); + assert_eq!( + dag.compare(&quell_node_id, &quake_node_id), + NodeCompare::Uncomparable + ); + assert_eq!( + dag.compare(&quell_node_id, &qualm_node_id), + NodeCompare::Uncomparable + ); +}