Skip to content

Commit eddd8b9

Browse files
committed
shardtree: Allow pruning of certain Reference leaves.
1 parent b3fb00b commit eddd8b9

File tree

3 files changed

+116
-49
lines changed

3 files changed

+116
-49
lines changed

shardtree/src/batch.rs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ fn unite<H: Hashable + Clone + PartialEq>(
271271
LocatedTree {
272272
root_addr: lroot.root_addr.parent(),
273273
root: if lroot.root_addr.level() < prune_below {
274-
Tree::unite(lroot.root_addr.level(), None, lroot.root, rroot.root)
274+
Tree::unite(lroot.root_addr.level(), None, lroot.root, rroot.root, false)
275275
} else {
276276
Tree::parent(None, lroot.root, rroot.root)
277277
},
@@ -424,13 +424,7 @@ mod tests {
424424
nil(),
425425
parent(nil(), leaf(("a".to_string(), RetentionFlags::EPHEMERAL)),)
426426
),
427-
parent(
428-
leaf(("bc".to_string(), RetentionFlags::EPHEMERAL)),
429-
parent(
430-
leaf(("d".to_string(), RetentionFlags::EPHEMERAL)),
431-
leaf(("e".to_string(), RetentionFlags::REFERENCE)),
432-
)
433-
)
427+
leaf(("bcde".to_string(), RetentionFlags::REFERENCE)),
434428
)
435429
},
436430
);

shardtree/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@ impl<
429429
ann.clone(),
430430
new_left,
431431
Tree::empty(),
432+
false,
432433
),
433434
pos,
434435
)
@@ -441,6 +442,7 @@ impl<
441442
ann.clone(),
442443
left.as_ref().clone(),
443444
new_right,
445+
false,
444446
),
445447
pos,
446448
))

shardtree/src/prunable.rs

Lines changed: 112 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ bitflags! {
3333
/// action.
3434
const MARKED = 0b00000010;
3535

36-
/// A leaf with `REFERENCE` retention will not be considered prunable until the `REFERENCE`
37-
/// flag is removed from the leaf. The `REFERENCE` flag will be removed at any point that
38-
/// the leaf is overwritten without `REFERENCE` retention, and `REFERENCE` retention cannot
39-
/// be added to an existing leaf.
36+
/// A left-hand leaf with `REFERENCE` retention will not be considered prunable until the
37+
/// corresponding right-hand sibling is prunable. When a `REFERENCE` node becomes prunable,
38+
/// the `REFERENCE` retention is propagated to the parent node that replaces the
39+
/// `REFERENCE` node and its sibling.
4040
const REFERENCE = 0b00000100;
4141
}
4242
}
@@ -263,11 +263,26 @@ impl<H: Hashable + Clone + PartialEq> PrunableTree<H> {
263263
ann,
264264
left.as_ref().clone().prune(level - 1),
265265
right.as_ref().clone().prune(level - 1),
266+
true,
266267
),
267268
other => other,
268269
}
269270
}
270271

272+
fn is_right_reference_frontier(&self) -> bool {
273+
fn go<H: Hashable + Clone + PartialEq>(
274+
tree: &Tree<Option<Arc<H>>, (H, RetentionFlags)>,
275+
) -> bool {
276+
match &tree.0 {
277+
Node::Parent { left, right, .. } => go(right) && left.is_nil(),
278+
Node::Leaf { value: (_, flags) } => *flags == RetentionFlags::REFERENCE,
279+
Node::Nil => false,
280+
}
281+
}
282+
283+
go(self)
284+
}
285+
271286
/// Merge two subtrees having the same root address.
272287
///
273288
/// The merge operation is checked to be strictly additive and returns an error if merging
@@ -287,22 +302,31 @@ impl<H: Hashable + Clone + PartialEq> PrunableTree<H> {
287302
let no_default_fill = addr.position_range_end();
288303
match (t0, t1) {
289304
(Tree(Node::Nil), other) | (other, Tree(Node::Nil)) => Ok(other),
290-
(Tree(Node::Leaf { value: vl }), Tree(Node::Leaf { value: vr })) => {
291-
if vl.0 == vr.0 {
305+
(Tree(Node::Leaf { value: (vl, fl) }), Tree(Node::Leaf { value: (vr, fr) })) => {
306+
if vl == vr {
292307
// Merge the flags together.
293-
Ok(Tree::leaf((vl.0, vl.1 | vr.1)))
308+
Ok(Tree::leaf((vl, fl | fr)))
294309
} else {
295-
trace!(left = ?vl.0, right = ?vr.0, "Merge conflict for leaves");
310+
trace!(left = ?vl, right = ?vr, "Merge conflict for leaves");
296311
Err(MergeError::Conflict(addr))
297312
}
298313
}
299-
(Tree(Node::Leaf { value }), parent @ Tree(Node::Parent { .. }))
300-
| (parent @ Tree(Node::Parent { .. }), Tree(Node::Leaf { value })) => {
314+
(Tree(Node::Leaf { value: (v, flags) }), parent @ Tree(Node::Parent { .. }))
315+
| (parent @ Tree(Node::Parent { .. }), Tree(Node::Leaf { value: (v, flags) })) => {
301316
let parent_hash = parent.root_hash(addr, no_default_fill);
302-
if parent_hash.iter().all(|r| r == &value.0) {
303-
Ok(parent.reannotate_root(Some(Arc::new(value.0))))
317+
if parent_hash.iter().all(|r| r == &v) {
318+
if parent.is_right_reference_frontier() {
319+
// Since the `Parent` node being merged with the leaf is is a reference
320+
// frontier with only right-hand nodes, and is therefore only required
321+
// in the tree for making witnesses for nodes to the right of this
322+
// node, we can simply update the current leaf by adding the
323+
// `REFERENCE` flag to its current retention flags.
324+
Ok(Tree::leaf((v, flags | RetentionFlags::REFERENCE)))
325+
} else {
326+
Ok(parent.reannotate_root(Some(Arc::new(v))))
327+
}
304328
} else {
305-
trace!(leaf = ?value, node = ?parent_hash, "Merge conflict for leaf into node");
329+
trace!(leaf = ?(v, flags), node = ?parent_hash, "Merge conflict for leaf into node");
306330
Err(MergeError::Conflict(addr))
307331
}
308332
}
@@ -335,6 +359,7 @@ impl<H: Hashable + Clone + PartialEq> PrunableTree<H> {
335359
lann.or(rann),
336360
go(l_addr, ll.as_ref().clone(), rl.as_ref().clone())?,
337361
go(r_addr, lr.as_ref().clone(), rr.as_ref().clone())?,
362+
false,
338363
))
339364
} else {
340365
unreachable!()
@@ -350,22 +375,32 @@ impl<H: Hashable + Clone + PartialEq> PrunableTree<H> {
350375
go(root_addr, self, other)
351376
}
352377

353-
/// Unite two nodes by either constructing a new parent node, or, if both nodes are ephemeral
354-
/// leaves or Nil, constructing a replacement parent by hashing leaf values together (or a
355-
/// replacement `Nil` value).
378+
/// Unite two nodes by either constructing a new parent node with the supplied nodes as
379+
/// children, or, if both nodes are ephemeral leaves or Nil, constructing a replacement parent
380+
/// by hashing leaf values together (or a replacement `Nil` value).
356381
///
357-
/// `level` must be the level of the two nodes that are being joined.
358-
pub(crate) fn unite(level: Level, ann: Option<Arc<H>>, left: Self, right: Self) -> Self {
382+
/// - `level`: the level of the two nodes that are being joined.
383+
/// - `pruning`: should be set to `true` in the case that left-hand `REFERENCE` nodes may be
384+
/// pruned so long as their right-hand sibling is not `MARKED`.
385+
pub(crate) fn unite(
386+
level: Level,
387+
ann: Option<Arc<H>>,
388+
left: Self,
389+
right: Self,
390+
pruning: bool,
391+
) -> Self {
359392
match (left, right) {
360393
(Tree(Node::Nil), Tree(Node::Nil)) if ann.is_none() => Tree::empty(),
361-
(Tree(Node::Leaf { value: lv }), Tree(Node::Leaf { value: rv }))
362-
// we can prune right-hand leaves that are not marked or reference leaves; if a
363-
// leaf is a checkpoint then that information will be propagated to the replacement
364-
// leaf
365-
if lv.1 == RetentionFlags::EPHEMERAL &&
366-
(rv.1 & (RetentionFlags::MARKED | RetentionFlags::REFERENCE)) == RetentionFlags::EPHEMERAL =>
394+
(Tree(Node::Leaf { value: (lv, lf) }), Tree(Node::Leaf { value: (rv, rf) }))
395+
if (lf == RetentionFlags::EPHEMERAL
396+
|| (pruning
397+
&& (lf | RetentionFlags::REFERENCE) == RetentionFlags::REFERENCE))
398+
&& (rf & RetentionFlags::MARKED) == RetentionFlags::EPHEMERAL =>
367399
{
368-
Tree::leaf((H::combine(level, &lv.0, &rv.0), rv.1))
400+
// We can prune if the right-hand leaf is not a marked leaf, and the left-hand leaf
401+
// has `EPHEMERAL` retention. `REFERENCE` and `CHECKPOINT` reference flags will be
402+
// propagated to the replacement leaf.
403+
Tree::leaf((H::combine(level, &lv, &rv), lf | rf))
369404
}
370405
(left, right) => Tree::parent(ann, left, right),
371406
}
@@ -615,14 +650,20 @@ impl<H: Hashable + Clone + PartialEq> LocatedPrunableTree<H> {
615650
// to the left to truncate the left child and then reconstruct the
616651
// node with `Nil` as the right sibling
617652
go(position, l_child, left.as_ref()).map(|left| {
618-
Tree::unite(l_child.level(), ann.clone(), left, Tree::empty())
653+
Tree::unite(l_child.level(), ann.clone(), left, Tree::empty(), false)
619654
})
620655
} else {
621656
// we are truncating within the range of the right node, so recurse
622657
// to the right to truncate the right child and then reconstruct the
623658
// node with the left sibling unchanged
624659
go(position, r_child, right.as_ref()).map(|right| {
625-
Tree::unite(r_child.level(), ann.clone(), left.as_ref().clone(), right)
660+
Tree::unite(
661+
r_child.level(),
662+
ann.clone(),
663+
left.as_ref().clone(),
664+
right,
665+
false,
666+
)
626667
})
627668
}
628669
}
@@ -651,7 +692,10 @@ impl<H: Hashable + Clone + PartialEq> LocatedPrunableTree<H> {
651692
/// to fill out the tree.
652693
///
653694
/// In the case that a leaf node would be replaced by an incomplete subtree, the resulting
654-
/// parent node will be annotated with the existing leaf value.
695+
/// parent node will be annotated with the existing leaf value, unless all of the leaves of the
696+
/// inserted tree are `REFERENCE` nodes and the inserted subtree corresponds to the right-hand
697+
/// edge of this tree; in this case, the existing leaf is simply reannotated with `REFERENCE`
698+
/// retention.
655699
///
656700
/// Returns the updated tree, along with the addresses of any [`Node::Nil`] nodes that were
657701
/// inserted in the process of creating the parent nodes down to the insertion point, or an
@@ -716,37 +760,46 @@ impl<H: Hashable + Clone + PartialEq> LocatedPrunableTree<H> {
716760
if subtree.root.has_computable_root() {
717761
Ok((
718762
if subtree.root.is_leaf() {
719-
// When replacing a leaf with a leaf, `REFERENCE` retention
720-
// will be discarded unless both leaves have `REFERENCE`
721-
// retention.
763+
// When replacing a leaf with a leaf, the retention flags
764+
// from both leaves are combined.
722765
subtree
723766
.root
724767
.try_map::<(H, RetentionFlags), InsertionError, _>(
725768
&|(v0, ret0)| {
726769
if v0 == value {
727-
let retention_result: RetentionFlags =
728-
((*retention | *ret0)
729-
- RetentionFlags::REFERENCE)
730-
| (RetentionFlags::REFERENCE
731-
& *retention
732-
& *ret0);
733-
Ok((value.clone(), retention_result))
770+
Ok((value.clone(), *retention | *ret0))
734771
} else {
735772
Err(InsertionError::Conflict(root_addr))
736773
}
737774
},
738775
)?
739776
} else {
740777
// It is safe to replace the existing root unannotated, because we
741-
// can always recompute the root from the subtree.
778+
// can always recompute the root from the subtree; we
779+
// checked that the subtree has a computable root above.
742780
subtree.root
743781
},
744782
vec![],
745783
))
746784
} else if subtree.root.node_value().iter().all(|v| v == &value) {
747785
Ok((
748-
// at this point we statically know the root to be a parent
749-
subtree.root.reannotate_root(Some(Arc::new(value.clone()))),
786+
// At this point we know the root to be a parent. If the
787+
// inserted subtree is a reference frontier with only
788+
// right-hand nodes, then we can simply reannotate the current
789+
// leaf by adding the `REFERENCE` flag to its current retention
790+
// flags.
791+
if subtree.root().is_right_reference_frontier()
792+
&& subtree.max_position() == Some(root_addr.max_position())
793+
{
794+
Tree::leaf((
795+
value.clone(),
796+
*retention | RetentionFlags::REFERENCE,
797+
))
798+
} else {
799+
// Otherwise, we replace the node with the subtree,
800+
// reannotated to its value.
801+
subtree.root.reannotate_root(Some(Arc::new(value.clone())))
802+
},
750803
vec![],
751804
))
752805
} else {
@@ -785,6 +838,7 @@ impl<H: Hashable + Clone + PartialEq> LocatedPrunableTree<H> {
785838
ann.clone(),
786839
new_left,
787840
right.as_ref().clone(),
841+
false,
788842
),
789843
incomplete,
790844
))
@@ -797,6 +851,7 @@ impl<H: Hashable + Clone + PartialEq> LocatedPrunableTree<H> {
797851
ann.clone(),
798852
left.as_ref().clone(),
799853
new_right,
854+
false,
800855
),
801856
incomplete,
802857
))
@@ -1001,6 +1056,7 @@ impl<H: Hashable + Clone + PartialEq> LocatedPrunableTree<H> {
10011056
ann.clone(),
10021057
go(&to_clear[0..p], l_addr, left),
10031058
go(&to_clear[p..], r_addr, right),
1059+
false,
10041060
)
10051061
}
10061062
Node::Leaf { value: (h, r) } => {
@@ -1214,6 +1270,21 @@ mod tests {
12141270
);
12151271
}
12161272

1273+
#[test]
1274+
fn is_right_reference_frontier() {
1275+
let t = parent(
1276+
leaf(("a".to_string(), RetentionFlags::EPHEMERAL)),
1277+
parent(nil(), leaf(("b".to_string(), RetentionFlags::REFERENCE))),
1278+
);
1279+
assert!(!t.is_right_reference_frontier());
1280+
1281+
let t = parent(
1282+
nil(),
1283+
parent(nil(), leaf(("b".to_string(), RetentionFlags::REFERENCE))),
1284+
);
1285+
assert!(t.is_right_reference_frontier());
1286+
}
1287+
12171288
#[test]
12181289
fn located_insert_subtree() {
12191290
let t: LocatedPrunableTree<String> = LocatedTree {

0 commit comments

Comments
 (0)