Skip to content

Commit 2b4e67b

Browse files
committed
feat: add nested rb_tree to ic_certification
1 parent c8edca7 commit 2b4e67b

File tree

5 files changed

+210
-80
lines changed

5 files changed

+210
-80
lines changed

packages/ic-certification/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ pub mod hash_tree;
99
pub use crate::hash_tree::*;
1010
pub mod rb_tree;
1111
pub use crate::rb_tree::*;
12+
pub mod nested_rb_tree;
13+
pub use crate::nested_rb_tree::*;
1214

1315
#[doc(inline)]
1416
pub use hash_tree::LookupResult;
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
use crate::{empty, fork, labeled, leaf, pruned, AsHashTree, Hash, HashTree, HashTreeNode, RbTree};
2+
3+
pub trait NestedTreeKeyRequirements: Clone + AsRef<[u8]> + 'static {}
4+
pub trait NestedTreeValueRequirements: AsHashTree + 'static {}
5+
impl<T> NestedTreeKeyRequirements for T where T: Clone + AsRef<[u8]> + 'static {}
6+
impl<T> NestedTreeValueRequirements for T where T: AsHashTree + 'static {}
7+
8+
#[derive(Debug, Clone)]
9+
pub enum NestedTree<K: NestedTreeKeyRequirements, V: NestedTreeValueRequirements> {
10+
Leaf(V),
11+
Nested(RbTree<K, NestedTree<K, V>>),
12+
}
13+
14+
impl<K: NestedTreeKeyRequirements, V: NestedTreeValueRequirements> Default for NestedTree<K, V> {
15+
fn default() -> Self {
16+
NestedTree::Nested(RbTree::<K, NestedTree<K, V>>::new())
17+
}
18+
}
19+
20+
impl<K: NestedTreeKeyRequirements, V: NestedTreeValueRequirements> AsHashTree for NestedTree<K, V> {
21+
fn root_hash(&self) -> Hash {
22+
match self {
23+
NestedTree::Leaf(a) => a.root_hash(),
24+
NestedTree::Nested(tree) => tree.root_hash(),
25+
}
26+
}
27+
28+
fn as_hash_tree(&self) -> HashTree {
29+
match self {
30+
NestedTree::Leaf(a) => a.as_hash_tree(),
31+
NestedTree::Nested(tree) => tree.as_hash_tree(),
32+
}
33+
}
34+
}
35+
36+
impl<K: NestedTreeKeyRequirements, V: NestedTreeValueRequirements> NestedTree<K, V> {
37+
pub fn get(&self, path: &[K]) -> Option<&V> {
38+
if let Some(key) = path.get(0) {
39+
match self {
40+
NestedTree::Leaf(_) => None,
41+
NestedTree::Nested(tree) => tree
42+
.get(key.as_ref())
43+
.and_then(|child| child.get(&path[1..])),
44+
}
45+
} else {
46+
match self {
47+
NestedTree::Leaf(value) => Some(value),
48+
NestedTree::Nested(_) => None,
49+
}
50+
}
51+
}
52+
53+
/// Returns true if there is a leaf at the specified path
54+
pub fn contains_leaf(&self, path: &[K]) -> bool {
55+
if let Some(key) = path.get(0) {
56+
match self {
57+
NestedTree::Leaf(_) => false,
58+
NestedTree::Nested(tree) => tree
59+
.get(key.as_ref())
60+
.map(|child| child.contains_leaf(&path[1..]))
61+
.unwrap_or(false),
62+
}
63+
} else {
64+
matches!(self, NestedTree::Leaf(_))
65+
}
66+
}
67+
68+
/// Returns true if there is a leaf or a subtree at the specified path
69+
pub fn contains_path(&self, path: &[K]) -> bool {
70+
if let Some(key) = path.get(0) {
71+
match self {
72+
NestedTree::Leaf(_) => false,
73+
NestedTree::Nested(tree) => tree
74+
.get(key.as_ref())
75+
.map(|child| child.contains_path(&path[1..]))
76+
.unwrap_or(false),
77+
}
78+
} else {
79+
true
80+
}
81+
}
82+
83+
pub fn insert(&mut self, path: &[K], value: V) {
84+
if let Some(key) = path.get(0) {
85+
match self {
86+
NestedTree::Leaf(_) => {
87+
*self = NestedTree::default();
88+
self.insert(path, value);
89+
}
90+
NestedTree::Nested(tree) => {
91+
if tree.get(key.as_ref()).is_some() {
92+
tree.modify(key.as_ref(), |child| child.insert(&path[1..], value));
93+
} else {
94+
tree.insert(key.clone(), NestedTree::default());
95+
self.insert(path, value);
96+
}
97+
}
98+
}
99+
} else {
100+
*self = NestedTree::Leaf(value);
101+
}
102+
}
103+
104+
pub fn delete(&mut self, path: &[K]) {
105+
if let Some(key) = path.get(0) {
106+
match self {
107+
NestedTree::Leaf(_) => {}
108+
NestedTree::Nested(tree) => {
109+
tree.modify(key.as_ref(), |child| child.delete(&path[1..]));
110+
}
111+
}
112+
} else {
113+
*self = NestedTree::default();
114+
}
115+
}
116+
117+
pub fn witness(&self, path: &[K]) -> HashTree {
118+
if let Some(key) = path.get(0) {
119+
match self {
120+
NestedTree::Leaf(value) => value.as_hash_tree(),
121+
NestedTree::Nested(tree) => {
122+
tree.nested_witness(key.as_ref(), |tree| tree.witness(&path[1..]))
123+
}
124+
}
125+
} else {
126+
self.as_hash_tree()
127+
}
128+
}
129+
}
130+
131+
pub fn merge_hash_trees<'a>(lhs: HashTree, rhs: HashTree) -> HashTree {
132+
match (lhs.root, rhs.root) {
133+
(HashTreeNode::Pruned(l), HashTreeNode::Pruned(r)) => {
134+
if l != r {
135+
panic!("merge_hash_trees: inconsistent hashes");
136+
}
137+
pruned(l)
138+
}
139+
(HashTreeNode::Pruned(_), r) => HashTree { root: r },
140+
(l, HashTreeNode::Pruned(_)) => HashTree { root: l },
141+
(HashTreeNode::Fork(l), HashTreeNode::Fork(r)) => fork(
142+
merge_hash_trees(HashTree { root: l.0 }, HashTree { root: r.0 }),
143+
merge_hash_trees(HashTree { root: l.1 }, HashTree { root: r.1 }),
144+
),
145+
(HashTreeNode::Labeled(l_label, l), HashTreeNode::Labeled(r_label, r)) => {
146+
if l_label != r_label {
147+
panic!("merge_hash_trees: inconsistent hash tree labels");
148+
}
149+
labeled(
150+
l_label,
151+
merge_hash_trees(HashTree { root: *l }, HashTree { root: *r }),
152+
)
153+
}
154+
(HashTreeNode::Empty(), HashTreeNode::Empty()) => empty(),
155+
(HashTreeNode::Leaf(l), HashTreeNode::Leaf(r)) => {
156+
if l != r {
157+
panic!("merge_hash_trees: inconsistent leaves");
158+
}
159+
leaf(l)
160+
}
161+
(_l, _r) => {
162+
panic!("merge_hash_trees: inconsistent tree structure");
163+
}
164+
}
165+
}
166+
167+
#[cfg(test)]
168+
mod tests {
169+
use super::*;
170+
171+
#[test]
172+
fn nested_tree_operation() {
173+
let mut tree: NestedTree<&str, Vec<u8>> = NestedTree::default();
174+
// insertion
175+
tree.insert(&["one", "two"], vec![2]);
176+
tree.insert(&["one", "three"], vec![3]);
177+
assert_eq!(tree.get(&["one", "two"]), Some(&vec![2]));
178+
assert_eq!(tree.get(&["one", "two", "three"]), None);
179+
assert_eq!(tree.get(&["one"]), None);
180+
assert!(tree.contains_leaf(&["one", "two"]));
181+
assert!(tree.contains_path(&["one"]));
182+
assert!(!tree.contains_leaf(&["one", "two", "three"]));
183+
assert!(!tree.contains_path(&["one", "two", "three"]));
184+
assert!(!tree.contains_leaf(&["one"]));
185+
186+
// deleting non-existent key doesn't do anything
187+
tree.delete(&["one", "two", "three"]);
188+
assert_eq!(tree.get(&["one", "two"]), Some(&vec![2]));
189+
assert!(tree.contains_leaf(&["one", "two"]));
190+
191+
// deleting existing key works
192+
tree.delete(&["one", "three"]);
193+
assert_eq!(tree.get(&["one", "two"]), Some(&vec![2]));
194+
assert_eq!(tree.get(&["one", "three"]), None);
195+
assert!(tree.contains_leaf(&["one", "two"]));
196+
assert!(!tree.contains_leaf(&["one", "three"]));
197+
198+
// deleting subtree works
199+
tree.delete(&["one"]);
200+
assert_eq!(tree.get(&["one", "two"]), None);
201+
assert_eq!(tree.get(&["one"]), None);
202+
assert!(!tree.contains_leaf(&["one", "two"]));
203+
assert!(!tree.contains_leaf(&["one"]));
204+
}
205+
}

packages/ic-response-verification-test-utils/src/expr_tree.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use crate::{cbor_encode, NestedTree};
2-
use ic_certification::{labeled, labeled_hash, AsHashTree, Hash, HashTree};
1+
use crate::cbor_encode;
2+
use ic_certification::{labeled, labeled_hash, AsHashTree, Hash, HashTree, NestedTree};
33

44
const LABEL_EXPR: &[u8] = b"http_expr";
55

@@ -50,7 +50,7 @@ impl Default for ExprTree {
5050
impl ExprTree {
5151
pub fn new() -> Self {
5252
Self {
53-
tree: NestedTree::new(),
53+
tree: NestedTree::default(),
5454
}
5555
}
5656

packages/ic-response-verification-test-utils/src/lib.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@ pub use hash::*;
1313
mod timestamp;
1414
pub use timestamp::*;
1515

16-
mod nested_tree;
17-
pub use nested_tree::*;
18-
1916
mod expr_tree;
2017
pub use expr_tree::*;
2118

packages/ic-response-verification-test-utils/src/nested_tree.rs

Lines changed: 0 additions & 74 deletions
This file was deleted.

0 commit comments

Comments
 (0)