Skip to content

Commit

Permalink
fix: proof panic when proving absent path with intermediary empty tre…
Browse files Browse the repository at this point in the history
…e. (#276)

* update op ordering

* prevent panic in merk proof construction

* cleanup

* fix proof construction

* fix verification

* add documentation

* clippy fixes

* fixed non used error

* fmt

---------

Co-authored-by: Quantum Explorer <[email protected]>
  • Loading branch information
iammadab and QuantumExplorer authored Oct 26, 2023
1 parent 6e94623 commit 1119847
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 14 deletions.
33 changes: 20 additions & 13 deletions grovedb/src/operations/proof/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand Down Expand Up @@ -570,24 +582,19 @@ 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());
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());
}

0 comments on commit 1119847

Please sign in to comment.