diff --git a/grovedb/src/element/mod.rs b/grovedb/src/element/mod.rs index ad2540e3..b1667ffb 100644 --- a/grovedb/src/element/mod.rs +++ b/grovedb/src/element/mod.rs @@ -34,6 +34,8 @@ use grovedb_visualize::visualize_to_vec; use crate::operations::proof::util::hex_to_ascii; #[cfg(any(feature = "full", feature = "verify"))] use crate::reference_path::ReferencePathType; +#[cfg(feature = "full")] +use crate::OperationCost; #[cfg(any(feature = "full", feature = "verify"))] /// Optional meta-data to be stored per element @@ -153,6 +155,15 @@ impl Element { Element::SumTree(..) => "sum tree", } } + + #[cfg(feature = "full")] + pub(crate) fn value_hash( + &self, + grove_version: &grovedb_version::version::GroveVersion, + ) -> grovedb_costs::CostResult { + let bytes = grovedb_costs::cost_return_on_error_default!(self.serialize(grove_version)); + crate::value_hash(&bytes).map(Result::Ok) + } } #[cfg(any(feature = "full", feature = "visualize"))] diff --git a/grovedb/src/lib.rs b/grovedb/src/lib.rs index 0d28e0a7..96c2d0de 100644 --- a/grovedb/src/lib.rs +++ b/grovedb/src/lib.rs @@ -1063,46 +1063,26 @@ impl GroveDb { "expected merk to contain value at key".to_string(), ))?; - // Absolute path to the referenced merk and the key in that merk: - let (referenced_path, referenced_key) = { + let referenced_value_hash = { let mut full_path = path_from_reference_path_type( reference_path.clone(), &path.to_vec(), Some(&key), )?; - let key = full_path.pop().ok_or_else(|| { - Error::MissingReference( - "can't resolve reference path to absolute path".to_owned(), + let item = self + .follow_reference( + (full_path.as_slice()).into(), + true, + None, + grove_version, ) - })?; - (full_path, key) + .unwrap()?; + item.value_hash(grove_version).unwrap()? }; - // Open another subtree, one that is referenced: - let referenced_merk = self - .open_non_transactional_merk_at_path( - (referenced_path.as_slice()).into(), - None, - grove_version, - ) - .unwrap()?; - - // Get value hash of the referenced item - let (_, referenced_value_hash) = referenced_merk - .get_value_and_value_hash( - &referenced_key, - true, - None::<&fn(&[u8], &GroveVersion) -> Option>, - grove_version, - ) - .unwrap() - .map_err(MerkError)? - .ok_or(Error::CorruptedData( - "expected merk to contain value at key".to_string(), - ))?; - // Take the current item (reference) hash and combine it with referenced value's // hash + let self_actual_value_hash = value_hash(&kv_value).unwrap(); let combined_value_hash = combine_hash(&self_actual_value_hash, &referenced_value_hash).unwrap(); @@ -1223,45 +1203,23 @@ impl GroveDb { "expected merk to contain value at key".to_string(), ))?; - // Absolute path to the referenced merk and the key in that merk: - let (referenced_path, referenced_key) = { + let referenced_value_hash = { let mut full_path = path_from_reference_path_type( reference_path.clone(), &path.to_vec(), Some(&key), )?; - let key = full_path.pop().ok_or_else(|| { - Error::MissingReference( - "can't resolve reference path to absolute path".to_owned(), + let item = self + .follow_reference( + (full_path.as_slice()).into(), + true, + Some(transaction), + grove_version, ) - })?; - (full_path, key) + .unwrap()?; + item.value_hash(grove_version).unwrap()? }; - // Open another subtree, one that is referenced: - let referenced_merk = self - .open_transactional_merk_at_path( - (referenced_path.as_slice()).into(), - transaction, - None, - grove_version, - ) - .unwrap()?; - - // Get value hash of the referenced item - let (_, referenced_value_hash) = referenced_merk - .get_value_and_value_hash( - &referenced_key, - true, - None::<&fn(&[u8], &GroveVersion) -> Option>, - grove_version, - ) - .unwrap() - .map_err(MerkError)? - .ok_or(Error::CorruptedData( - "expected merk to contain value at key".to_string(), - ))?; - // Take the current item (reference) hash and combine it with referenced value's // hash let self_actual_value_hash = value_hash(&kv_value).unwrap(); diff --git a/grovedb/src/operations/insert/mod.rs b/grovedb/src/operations/insert/mod.rs index b7e00fc1..87f0f97b 100644 --- a/grovedb/src/operations/insert/mod.rs +++ b/grovedb/src/operations/insert/mod.rs @@ -7,6 +7,7 @@ use std::{collections::HashMap, option::Option::None}; use grovedb_costs::{ cost_return_on_error, cost_return_on_error_no_add, CostResult, CostsExt, OperationCost, }; +use grovedb_merk::tree::value_hash; #[cfg(feature = "full")] use grovedb_merk::{tree::NULL_HASH, Merk, MerkOptions}; use grovedb_path::SubtreePath; @@ -293,44 +294,18 @@ impl GroveDb { .wrap_with_cost(OperationCost::default()) ); - let (referenced_key, referenced_path) = reference_path.split_last().unwrap(); - let subtree_for_reference = cost_return_on_error!( + let referenced_item = cost_return_on_error!( &mut cost, - self.open_transactional_merk_at_path( - referenced_path.into(), - transaction, - Some(batch), - grove_version, - ) - ); - - let referenced_element_value_hash_opt = cost_return_on_error!( - &mut cost, - Element::get_value_hash( - &subtree_for_reference, - referenced_key, - true, + self.follow_reference( + reference_path.as_slice().into(), + false, + Some(transaction), grove_version ) ); - let referenced_element_value_hash = cost_return_on_error!( - &mut cost, - referenced_element_value_hash_opt - .ok_or({ - let reference_string = reference_path - .iter() - .map(hex::encode) - .collect::>() - .join("/"); - Error::MissingReference(format!( - "reference {}/{} can not be found", - reference_string, - hex::encode(key) - )) - }) - .wrap_with_cost(OperationCost::default()) - ); + let referenced_element_value_hash = + cost_return_on_error!(&mut cost, referenced_item.value_hash(grove_version)); cost_return_on_error!( &mut cost, @@ -452,46 +427,18 @@ impl GroveDb { path_from_reference_path_type(reference_path.clone(), path, Some(key)) .wrap_with_cost(OperationCost::default()) ); - - // TODO unwrap? - let (referenced_key, referenced_path) = reference_path.split_last().unwrap(); - let subtree_for_reference = cost_return_on_error!( + let referenced_item = cost_return_on_error!( &mut cost, - self.open_non_transactional_merk_at_path( - referenced_path.into(), - Some(batch), - grove_version - ) - ); - - // when there is no transaction, we don't want to use caching - let referenced_element_value_hash_opt = cost_return_on_error!( - &mut cost, - Element::get_value_hash( - &subtree_for_reference, - referenced_key, + self.follow_reference( + reference_path.as_slice().into(), false, + None, grove_version ) ); - let referenced_element_value_hash = cost_return_on_error!( - &mut cost, - referenced_element_value_hash_opt - .ok_or({ - let reference_string = reference_path - .iter() - .map(hex::encode) - .collect::>() - .join("/"); - Error::MissingReference(format!( - "reference {}/{} can not be found", - reference_string, - hex::encode(key) - )) - }) - .wrap_with_cost(OperationCost::default()) - ); + let referenced_element_value_hash = + cost_return_on_error!(&mut cost, referenced_item.value_hash(grove_version)); cost_return_on_error!( &mut cost, diff --git a/grovedb/src/tests/mod.rs b/grovedb/src/tests/mod.rs index f98fcb64..f6b7b6d4 100644 --- a/grovedb/src/tests/mod.rs +++ b/grovedb/src/tests/mod.rs @@ -758,6 +758,7 @@ pub fn make_deep_tree_with_sum_trees(grove_version: &GroveVersion) -> TempGroveD } mod tests { + use batch::GroveDbOp; use grovedb_merk::proofs::query::SubqueryBranch; use super::*; @@ -1189,7 +1190,12 @@ mod tests { ) .unwrap(); - assert!(matches!(result, Err(Error::MissingReference(_)))); + dbg!(&result); + + assert!(matches!( + result, + Err(Error::CorruptedReferencePathKeyNotFound(_)) + )); } #[test] @@ -1228,24 +1234,15 @@ mod tests { } // Add one more reference - db.insert( - [TEST_LEAF].as_ref(), - &keygen(MAX_REFERENCE_HOPS + 1), - Element::new_reference(ReferencePathType::AbsolutePathReference(vec![ - TEST_LEAF.to_vec(), - keygen(MAX_REFERENCE_HOPS), - ])), - None, - None, - grove_version, - ) - .unwrap() - .expect("expected insert"); - let result = db - .get( + .insert( [TEST_LEAF].as_ref(), &keygen(MAX_REFERENCE_HOPS + 1), + Element::new_reference(ReferencePathType::AbsolutePathReference(vec![ + TEST_LEAF.to_vec(), + keygen(MAX_REFERENCE_HOPS), + ])), + None, None, grove_version, ) @@ -3995,4 +3992,131 @@ mod tests { // `verify_grovedb` must identify issues assert!(db.verify_grovedb(None, true, grove_version).unwrap().len() > 0); } + + #[test] + fn test_verify_corrupted_long_reference() { + let grove_version = GroveVersion::latest(); + let db = make_test_grovedb(grove_version); + + db.insert( + &[TEST_LEAF], + b"value", + Element::new_item(b"hello".to_vec()), + None, + None, + grove_version, + ) + .unwrap() + .unwrap(); + + db.insert( + &[TEST_LEAF], + b"refc", + Element::new_reference(ReferencePathType::SiblingReference(b"value".to_vec())), + None, + None, + grove_version, + ) + .unwrap() + .unwrap(); + + db.insert( + &[TEST_LEAF], + b"refb", + Element::new_reference(ReferencePathType::SiblingReference(b"refc".to_vec())), + None, + None, + grove_version, + ) + .unwrap() + .unwrap(); + + db.insert( + &[TEST_LEAF], + b"refa", + Element::new_reference(ReferencePathType::SiblingReference(b"refb".to_vec())), + None, + None, + grove_version, + ) + .unwrap() + .unwrap(); + + assert!(db + .verify_grovedb(None, true, grove_version) + .unwrap() + .is_empty()); + + // Breaking things there: + db.insert( + &[TEST_LEAF], + b"value", + Element::new_item(b"not hello >:(".to_vec()), + None, + None, + grove_version, + ) + .unwrap() + .unwrap(); + + assert!(!db + .verify_grovedb(None, true, grove_version) + .unwrap() + .is_empty()); + } + + #[test] + fn test_verify_corrupted_long_reference_batch() { + let grove_version = GroveVersion::latest(); + let db = make_test_grovedb(grove_version); + + let ops = vec![ + GroveDbOp::insert_op( + vec![TEST_LEAF.to_vec()], + b"value".to_vec(), + Element::new_item(b"hello".to_vec()), + ), + GroveDbOp::insert_op( + vec![TEST_LEAF.to_vec()], + b"refc".to_vec(), + Element::new_reference(ReferencePathType::SiblingReference(b"value".to_vec())), + ), + GroveDbOp::insert_op( + vec![TEST_LEAF.to_vec()], + b"refb".to_vec(), + Element::new_reference(ReferencePathType::SiblingReference(b"refc".to_vec())), + ), + GroveDbOp::insert_op( + vec![TEST_LEAF.to_vec()], + b"refa".to_vec(), + Element::new_reference(ReferencePathType::SiblingReference(b"refb".to_vec())), + ), + ]; + + db.apply_batch(ops, None, None, grove_version) + .unwrap() + .unwrap(); + + assert!(db + .verify_grovedb(None, true, grove_version) + .unwrap() + .is_empty()); + + // Breaking things there: + db.insert( + &[TEST_LEAF], + b"value", + Element::new_item(b"not hello >:(".to_vec()), + None, + None, + grove_version, + ) + .unwrap() + .unwrap(); + + assert!(!db + .verify_grovedb(None, true, grove_version) + .unwrap() + .is_empty()); + } }