diff --git a/grovedb/src/operations/proof/generate.rs b/grovedb/src/operations/proof/generate.rs index 82852359..fad64c84 100644 --- a/grovedb/src/operations/proof/generate.rs +++ b/grovedb/src/operations/proof/generate.rs @@ -510,10 +510,22 @@ impl GroveDb { 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)); @@ -570,16 +582,11 @@ impl GroveDb { .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()); @@ -587,7 +594,7 @@ impl GroveDb { &mut cost, self.generate_and_store_merk_proof( ¤t_path.as_slice().into(), - &subtree.expect("confirmed not error above"), + &subtree, &next_key_query, (None, None), ProofTokenType::Merk, diff --git a/grovedb/src/operations/proof/verify.rs b/grovedb/src/operations/proof/verify.rs index 9e8c6e44..a69935bf 100644 --- a/grovedb/src/operations/proof/verify.rs +++ b/grovedb/src/operations/proof/verify.rs @@ -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")); } diff --git a/grovedb/src/tests/query_tests.rs b/grovedb/src/tests/query_tests.rs index 0bb6a1f0..c8e53ff8 100644 --- a/grovedb/src/tests/query_tests.rs +++ b/grovedb/src/tests/query_tests.rs @@ -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()); +}