Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: proof panic when proving absent path with intermediary empty tree. #276

Merged
merged 10 commits into from
Oct 26, 2023
Merged
33 changes: 20 additions & 13 deletions grovedb/src/operations/proof/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,16 +169,16 @@

/// Perform a pre-order traversal of the tree based on the provided
/// subqueries
fn prove_subqueries(
&self,
proofs: &mut Vec<u8>,
path: Vec<&[u8]>,
query: &PathQuery,
current_limit: &mut Option<u16>,
current_offset: &mut Option<u16>,
is_first_call: bool,
is_verbose: bool,
) -> CostResult<(), Error> {

Check warning on line 181 in grovedb/src/operations/proof/generate.rs

View workflow job for this annotation

GitHub Actions / clippy

this function has too many arguments (8/7)

warning: this function has too many arguments (8/7) --> grovedb/src/operations/proof/generate.rs:172:5 | 172 | / fn prove_subqueries( 173 | | &self, 174 | | proofs: &mut Vec<u8>, 175 | | path: Vec<&[u8]>, ... | 180 | | is_verbose: bool, 181 | | ) -> CostResult<(), Error> { | |______________________________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments
let mut cost = OperationCost::default();
let mut to_add_to_result_set: u16 = 0;

Expand Down Expand Up @@ -485,17 +485,17 @@

/// Generates query proof given a subtree and appends the result to a proof
/// list
fn generate_and_store_merk_proof<'a, S, B>(
&self,
path: &SubtreePath<B>,
subtree: &'a Merk<S>,
query: &Query,
limit_offset: LimitOffset,
proof_token_type: ProofTokenType,
proofs: &mut Vec<u8>,
is_verbose: bool,
key: &[u8],
) -> CostResult<(Option<u16>, Option<u16>), Error>

Check warning on line 498 in grovedb/src/operations/proof/generate.rs

View workflow job for this annotation

GitHub Actions / clippy

this function has too many arguments (9/7)

warning: this function has too many arguments (9/7) --> grovedb/src/operations/proof/generate.rs:488:5 | 488 | / fn generate_and_store_merk_proof<'a, S, B>( 489 | | &self, 490 | | path: &SubtreePath<B>, 491 | | subtree: &'a Merk<S>, ... | 497 | | key: &[u8], 498 | | ) -> CostResult<(Option<u16>, Option<u16>), Error> | |______________________________________________________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments
where
S: StorageContext<'a> + 'a,
B: AsRef<[u8]>,
Expand All @@ -510,10 +510,22 @@

let mut cost = OperationCost::default();

let mut proof_result = subtree
.prove_without_encoding(query.clone(), limit_offset.0, limit_offset.1)
.unwrap()
.expect("should generate proof");
// if the subtree is empty, return the EmptyTree proof op
if subtree.root_hash().unwrap() == EMPTY_TREE_HASH {
cost_return_on_error_no_add!(
&cost,
write_to_vec(proofs, &[ProofTokenType::EmptyTree.into()])
);
return Ok(limit_offset).wrap_with_cost(cost);
}

let mut proof_result = cost_return_on_error_no_add!(
&cost,
subtree
.prove_without_encoding(query.clone(), limit_offset.0, limit_offset.1)
.unwrap()
.map_err(|_e| Error::InternalError("failed to generate proof"))
);

cost_return_on_error!(&mut cost, self.post_process_proof(path, &mut proof_result));

Expand Down Expand Up @@ -570,24 +582,19 @@
.open_non_transactional_merk_at_path(current_path.as_slice().into(), None)
.unwrap_add_cost(&mut cost);

if subtree.is_err() {
let Ok(subtree) = subtree else {
break;
}
};

let has_item = Element::get(
subtree.as_ref().expect("confirmed not error above"),
key,
true,
)
.unwrap_add_cost(&mut cost);
let has_item = Element::get(&subtree, key, true).unwrap_add_cost(&mut cost);

let mut next_key_query = Query::new();
next_key_query.insert_key(key.to_vec());
cost_return_on_error!(
&mut cost,
self.generate_and_store_merk_proof(
&current_path.as_slice().into(),
&subtree.expect("confirmed not error above"),
&subtree,
&next_key_query,
(None, None),
ProofTokenType::Merk,
Expand Down
20 changes: 19 additions & 1 deletion grovedb/src/operations/proof/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -708,7 +708,25 @@ impl ProofVerifier {

for key in path_slices {
let (proof_token_type, merk_proof, _) = proof_reader.read_proof()?;
if proof_token_type != ProofTokenType::Merk {
if proof_token_type == ProofTokenType::EmptyTree {
// when we encounter the empty tree op, we need to ensure
// that the expected tree hash is the combination of the
// Element_value_hash and the empty root hash [0; 32]
let combined_hash = combine_hash(
value_hash_fn(last_result_set[0].value.as_slice()).value(),
&[0; 32],
)
.unwrap();
if Some(combined_hash) != expected_child_hash {
return Err(Error::InvalidProof(
"proof invalid: could not verify empty subtree while generating absent \
path proof",
));
} else {
last_result_set = vec![];
break;
}
} else if proof_token_type != ProofTokenType::Merk {
return Err(Error::InvalidProof("expected a merk proof for absent path"));
}

Expand Down
23 changes: 23 additions & 0 deletions grovedb/src/tests/query_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2658,3 +2658,26 @@ fn test_query_b_depends_on_query_a() {
assert_eq!(age_result[0].2, Some(Element::new_item(vec![12])));
assert_eq!(age_result[1].2, Some(Element::new_item(vec![46])));
}

#[test]
fn test_prove_absent_path_with_intermediate_emtpy_tree() {
// root
// test_leaf (empty)
let mut grovedb = make_test_grovedb();

// prove the absence of key "book" in ["test_leaf", "invalid"]
let mut query = Query::new();
query.insert_key(b"book".to_vec());
let mut path_query =
PathQuery::new_unsized(vec![TEST_LEAF.to_vec(), b"invalid".to_vec()], query);

let proof = grovedb
.prove_query(&path_query)
.unwrap()
.expect("should generate proofs");

let (root_hash, result_set) =
GroveDb::verify_query(proof.as_slice(), &path_query).expect("should verify proof");
assert_eq!(result_set.len(), 0);
assert_eq!(root_hash, grovedb.root_hash(None).unwrap().unwrap());
}
Loading