diff --git a/.gitignore b/.gitignore index 8ff0eb369..4b1aa93d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target .idea Cargo.lock +grove.db \ No newline at end of file diff --git a/grovedb/src/lib.rs b/grovedb/src/lib.rs index ccae4ec83..649183a39 100644 --- a/grovedb/src/lib.rs +++ b/grovedb/src/lib.rs @@ -30,6 +30,8 @@ pub enum Error { InvalidPath(&'static str), #[error("unable to decode")] EdError(#[from] ed::Error), + #[error("cyclic reference path")] + CyclicReferencePath, } impl From for Error { diff --git a/grovedb/src/subtree.rs b/grovedb/src/subtree.rs index d4237b49b..bc6521a9d 100644 --- a/grovedb/src/subtree.rs +++ b/grovedb/src/subtree.rs @@ -30,16 +30,32 @@ impl Element { /// Recursively follow `Element::Reference` fn follow_reference(self, merk: &Merk) -> Result { - if let Element::Reference(reference_merk_key) = self { - let element = Element::decode( - merk.get(reference_merk_key.as_slice())? - .ok_or(Error::InvalidPath("key not found in Merk"))? - .as_slice(), - )?; - element.follow_reference(merk) - } else { - Ok(self) + fn follow_reference_with_path( + element: Element, + merk: &Merk, + paths: &mut Vec>, + ) -> Result { + if let Element::Reference(reference_merk_key) = element { + // Check if the reference merk key has been visited before + // if it has then we have a cycle + if paths.contains(&reference_merk_key) { + return Err(Error::CyclicReferencePath); + } + let element = Element::decode( + merk.get(reference_merk_key.as_slice())? + .ok_or(Error::InvalidPath("key not found in Merk"))? + .as_slice(), + )?; + + paths.push(reference_merk_key); + follow_reference_with_path(element, merk, paths) + } else { + Ok(element) + } } + + let mut reference_paths: Vec> = Vec::new(); + follow_reference_with_path(self, merk, &mut reference_paths) } /// A helper method to build Merk keys (and RocksDB as well) out of path + @@ -72,6 +88,7 @@ impl Element { .ok_or(Error::InvalidPath("key not found in Merk"))? .as_slice(), )?; + element.follow_reference(&merk) } @@ -141,4 +158,24 @@ mod tests { Element::Item(b"value".to_vec()), ); } + + #[test] + fn test_circular_references() { + let tmp_dir = TempDir::new("db").unwrap(); + let mut merk = Merk::open(tmp_dir.path()).unwrap(); + + Element::Tree + .insert(&mut merk, &[], b"tree-key") + .expect("expected successful insertion"); + + // r1 points to r2 and r2 points to r1 (cycle!) + Element::new_reference(&[b"tree-key"], b"reference-2") + .insert(&mut merk, &[b"tree-key"], b"reference-1") + .expect("expected successful reference insertion"); + Element::new_reference(&[b"tree-key"], b"reference-1") + .insert(&mut merk, &[b"tree-key"], b"reference-2") + .expect("expected successful reference insertion"); + + assert!(Element::get(&merk, &[b"tree-key"], b"reference-1").is_err()); + } }