Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 410390b

Browse files
authored
cmt: add method to check if the tree is empty (#3640)
1 parent aa5fdee commit 410390b

File tree

3 files changed

+131
-1
lines changed

3 files changed

+131
-1
lines changed

libraries/concurrent-merkle-tree/src/concurrent_merkle_tree.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,23 @@ impl<const MAX_DEPTH: usize, const MAX_BUFFER_SIZE: usize>
146146
Ok(root)
147147
}
148148

149+
/// Errors if one of the leaves of the current merkle tree is non-EMPTY
150+
pub fn prove_tree_is_empty(&self) -> Result<(), ConcurrentMerkleTreeError> {
151+
let mut empty_node_cache = Box::new([EMPTY; MAX_DEPTH]);
152+
if self.get_root()
153+
!= empty_node_cached::<MAX_DEPTH>(MAX_DEPTH as u32, &mut empty_node_cache)
154+
{
155+
return Err(ConcurrentMerkleTreeError::TreeNonEmpty);
156+
}
157+
Ok(())
158+
}
159+
160+
/// Returns the current root of the merkle tree
161+
pub fn get_root(&self) -> [u8; 32] {
162+
self.get_change_log().root
163+
}
164+
165+
/// Returns the most recent changelog
149166
pub fn get_change_log(&self) -> Box<ChangeLog<MAX_DEPTH>> {
150167
Box::new(self.change_logs[self.active_index as usize])
151168
}
@@ -416,7 +433,7 @@ impl<const MAX_DEPTH: usize, const MAX_BUFFER_SIZE: usize>
416433
proof: &[Node; MAX_DEPTH],
417434
leaf_index: u32,
418435
) -> bool {
419-
recompute(leaf, proof, leaf_index) == self.get_change_log().root
436+
recompute(leaf, proof, leaf_index) == self.get_root()
420437
}
421438

422439
/// Note: Enabling `allow_inferred_proof` will fast forward the given proof

libraries/concurrent-merkle-tree/src/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,8 @@ pub enum ConcurrentMerkleTreeError {
3232
"Valid proof was passed to a leaf, but its value has changed since the proof was issued"
3333
)]
3434
LeafContentsModified,
35+
36+
/// Tree has at least 1 non-EMTPY leaf
37+
#[error("Tree is not empty")]
38+
TreeNonEmpty,
3539
}

libraries/concurrent-merkle-tree/tests/tests.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,3 +389,112 @@ async fn test_append_bug_repro_2() {
389389
}
390390
assert_eq!(cmt.get_change_log().root, tree.get_root());
391391
}
392+
393+
#[tokio::test(flavor = "multi_thread")]
394+
/// Test that empty trees are checked properly by adding & removing leaves one by one
395+
async fn test_prove_tree_empty_incremental() {
396+
let (mut cmt, mut tree) = setup();
397+
let mut rng = thread_rng();
398+
cmt.initialize().unwrap();
399+
400+
cmt.prove_tree_is_empty().unwrap();
401+
402+
// Append a random leaf & remove it, and make sure that the tree is empty at the end
403+
let tree_size = 64;
404+
for i in 0..tree_size {
405+
let leaf = rng.gen::<[u8; 32]>();
406+
cmt.append(leaf).unwrap();
407+
tree.add_leaf(leaf, i);
408+
409+
match cmt.prove_tree_is_empty() {
410+
Ok(_) => {
411+
panic!("Tree has a leaf in it -- should not be possible to prove empty!")
412+
}
413+
Err(e) => match e {
414+
ConcurrentMerkleTreeError::TreeNonEmpty => {}
415+
_ => {
416+
panic!("Wrong error thrown. Expected TreeNonEmpty erro")
417+
}
418+
},
419+
}
420+
421+
cmt.set_leaf(
422+
tree.get_root(),
423+
tree.get_leaf(i),
424+
EMPTY,
425+
&tree.get_proof_of_leaf(i),
426+
i as u32,
427+
)
428+
.unwrap();
429+
tree.add_leaf(EMPTY, i);
430+
431+
cmt.prove_tree_is_empty().unwrap();
432+
}
433+
}
434+
435+
#[tokio::test(flavor = "multi_thread")]
436+
/// Test that empty trees are checked properly by adding & removing leaves in a batch
437+
async fn test_prove_tree_empty_batched() {
438+
let (mut cmt, mut tree) = setup();
439+
let mut rng = thread_rng();
440+
cmt.initialize().unwrap();
441+
442+
// Sanity check
443+
cmt.prove_tree_is_empty().unwrap();
444+
445+
// Add random leaves to the tree
446+
let tree_size = 64;
447+
for i in 0..tree_size {
448+
let leaf = rng.gen::<[u8; 32]>();
449+
cmt.append(leaf).unwrap();
450+
tree.add_leaf(leaf, i);
451+
452+
match cmt.prove_tree_is_empty() {
453+
Ok(_) => {
454+
panic!("Tree has a leaf in it -- should not be possible to prove empty!")
455+
}
456+
Err(e) => match e {
457+
ConcurrentMerkleTreeError::TreeNonEmpty => {}
458+
_ => {
459+
panic!("Wrong error thrown. Expected TreeNonEmpty erro")
460+
}
461+
},
462+
}
463+
}
464+
// Remove random leaves
465+
for i in 0..tree_size - 1 {
466+
cmt.set_leaf(
467+
tree.get_root(),
468+
tree.get_leaf(i),
469+
EMPTY,
470+
&tree.get_proof_of_leaf(i),
471+
i as u32,
472+
)
473+
.unwrap();
474+
tree.add_leaf(EMPTY, i);
475+
476+
match cmt.prove_tree_is_empty() {
477+
Ok(_) => {
478+
panic!("Tree has a leaf in it -- should not be possible to prove empty!")
479+
}
480+
Err(e) => match e {
481+
ConcurrentMerkleTreeError::TreeNonEmpty => {}
482+
_ => {
483+
panic!("Wrong error thrown. Expected TreeNonEmpty erro")
484+
}
485+
},
486+
}
487+
}
488+
cmt.set_leaf(
489+
tree.get_root(),
490+
tree.get_leaf(tree_size - 1),
491+
EMPTY,
492+
&tree.get_proof_of_leaf(tree_size - 1),
493+
(tree_size - 1) as u32,
494+
)
495+
.unwrap();
496+
tree.add_leaf(EMPTY, tree_size - 1);
497+
498+
// Check that the last leaf was successfully removed
499+
cmt.prove_tree_is_empty().unwrap();
500+
}

0 commit comments

Comments
 (0)