Remove initial checkpoints.

The choice to use an initial checkpoint as an anchor for removals in
`unmark` operations produced an awkward situation where it was necessary
to choose a checkpoint identifier just to construct an empty tree. This
removes that decision in favor of eagerly removing data if there is no
checkpoint to tie the removal to.
This commit is contained in:
Kris Nuttycombe 2023-05-19 08:14:55 -06:00
parent a26844a451
commit 4efe39eaa6
4 changed files with 138 additions and 187 deletions

View File

@ -470,13 +470,13 @@ impl<H, C, const DEPTH: u8> BridgeTree<H, C, DEPTH> {
/// ///
/// Panics if `max_checkpoints < 1` because mark/rewind logic depends upon the presence /// Panics if `max_checkpoints < 1` because mark/rewind logic depends upon the presence
/// of checkpoints to function. /// of checkpoints to function.
pub fn new(max_checkpoints: usize, initial_checkpoint_id: C) -> Self { pub fn new(max_checkpoints: usize) -> Self {
assert!(max_checkpoints >= 1); assert!(max_checkpoints >= 1);
Self { Self {
prior_bridges: vec![], prior_bridges: vec![],
current_bridge: None, current_bridge: None,
saved: BTreeMap::new(), saved: BTreeMap::new(),
checkpoints: VecDeque::from(vec![Checkpoint::at_length(0, initial_checkpoint_id)]), checkpoints: VecDeque::new(),
max_checkpoints, max_checkpoints,
} }
} }
@ -535,12 +535,8 @@ impl<H, C, const DEPTH: u8> BridgeTree<H, C, DEPTH> {
impl<H: Hashable + Clone + Ord, C: Clone + Ord, const DEPTH: u8> BridgeTree<H, C, DEPTH> { impl<H: Hashable + Clone + Ord, C: Clone + Ord, const DEPTH: u8> BridgeTree<H, C, DEPTH> {
/// Construct a new BridgeTree that will start recording changes from the state of /// Construct a new BridgeTree that will start recording changes from the state of
/// the specified frontier. /// the specified frontier.
pub fn from_frontier( pub fn from_frontier(max_checkpoints: usize, frontier: NonEmptyFrontier<H>) -> Self {
max_checkpoints: usize, Self {
frontier: NonEmptyFrontier<H>,
checkpoint_id: C,
) -> Self {
let mut bridge = Self {
prior_bridges: vec![], prior_bridges: vec![],
current_bridge: Some(MerkleBridge::from_parts( current_bridge: Some(MerkleBridge::from_parts(
None, None,
@ -551,9 +547,7 @@ impl<H: Hashable + Clone + Ord, C: Clone + Ord, const DEPTH: u8> BridgeTree<H, C
saved: BTreeMap::new(), saved: BTreeMap::new(),
checkpoints: VecDeque::new(), checkpoints: VecDeque::new(),
max_checkpoints, max_checkpoints,
}; }
bridge.checkpoint(checkpoint_id);
bridge
} }
/// Construct a new BridgeTree from its constituent parts, checking for internal /// Construct a new BridgeTree from its constituent parts, checking for internal
@ -717,11 +711,9 @@ impl<H: Hashable + Clone + Ord, C: Clone + Ord, const DEPTH: u8> BridgeTree<H, C
// mark the position as having been marked in the current checkpoint // mark the position as having been marked in the current checkpoint
if let std::collections::btree_map::Entry::Vacant(e) = self.saved.entry(pos) { if let std::collections::btree_map::Entry::Vacant(e) = self.saved.entry(pos) {
let c = self if let Some(c) = self.checkpoints.back_mut() {
.checkpoints c.marked.insert(pos);
.back_mut() }
.expect("Checkpoints deque must never be empty");
c.marked.insert(pos);
e.insert(self.prior_bridges.len() - 1); e.insert(self.prior_bridges.len() - 1);
} }
@ -749,11 +741,11 @@ impl<H: Hashable + Clone + Ord, C: Clone + Ord, const DEPTH: u8> BridgeTree<H, C
/// false if we were already not maintaining a mark at this position. /// false if we were already not maintaining a mark at this position.
pub fn remove_mark(&mut self, position: Position) -> bool { pub fn remove_mark(&mut self, position: Position) -> bool {
if self.saved.contains_key(&position) { if self.saved.contains_key(&position) {
let c = self if let Some(c) = self.checkpoints.back_mut() {
.checkpoints c.forgotten.insert(position);
.back_mut() } else {
.expect("Checkpoints deque must never be empty."); self.saved.remove(&position);
c.forgotten.insert(position); }
true true
} else { } else {
false false
@ -805,12 +797,7 @@ impl<H: Hashable + Clone + Ord, C: Clone + Ord, const DEPTH: u8> BridgeTree<H, C
/// at that tree state have been removed using `rewind`. This function /// at that tree state have been removed using `rewind`. This function
/// return false and leave the tree unmodified if no checkpoints exist. /// return false and leave the tree unmodified if no checkpoints exist.
pub fn rewind(&mut self) -> bool { pub fn rewind(&mut self) -> bool {
if self.checkpoints.len() > 1 { if let Some(c) = self.checkpoints.pop_back() {
let c = self
.checkpoints
.pop_back()
.expect("Checkpoints deque is known to be non-empty.");
// Remove marks for positions that were marked during the lifetime of this checkpoint. // Remove marks for positions that were marked during the lifetime of this checkpoint.
for pos in c.marked { for pos in c.marked {
self.saved.remove(&pos); self.saved.remove(&pos);
@ -1063,7 +1050,7 @@ mod tests {
#[test] #[test]
fn tree_depth() { fn tree_depth() {
let mut tree = BridgeTree::<String, usize, 3>::new(100, 0); let mut tree = BridgeTree::<String, usize, 3>::new(100);
for c in 'a'..'i' { for c in 'a'..'i' {
assert!(tree.append(c.to_string())) assert!(tree.append(c.to_string()))
} }
@ -1095,7 +1082,7 @@ mod tests {
{ {
let pos_gen = (0..max_count).prop_map(|p| Position::try_from(p).unwrap()); let pos_gen = (0..max_count).prop_map(|p| Position::try_from(p).unwrap());
proptest::collection::vec(arb_operation(item_gen, pos_gen), 0..max_count).prop_map(|ops| { proptest::collection::vec(arb_operation(item_gen, pos_gen), 0..max_count).prop_map(|ops| {
let mut tree: BridgeTree<G::Value, usize, 8> = BridgeTree::new(10, 0); let mut tree: BridgeTree<G::Value, usize, 8> = BridgeTree::new(10);
for (i, op) in ops.into_iter().enumerate() { for (i, op) in ops.into_iter().enumerate() {
apply_operation(&mut tree, op.map_checkpoint_id(|_| i)); apply_operation(&mut tree, op.map_checkpoint_id(|_| i));
} }
@ -1130,33 +1117,31 @@ mod tests {
#[test] #[test]
fn root_hashes() { fn root_hashes() {
check_root_hashes(|max_checkpoints| { check_root_hashes(BridgeTree::<String, usize, 4>::new);
BridgeTree::<String, usize, 4>::new(max_checkpoints, 0)
});
} }
#[test] #[test]
fn witness() { fn witness() {
check_witnesses(|max_checkpoints| BridgeTree::<String, usize, 4>::new(max_checkpoints, 0)); check_witnesses(BridgeTree::<String, usize, 4>::new);
} }
#[test] #[test]
fn checkpoint_rewind() { fn checkpoint_rewind() {
check_checkpoint_rewind(|max_checkpoints| { check_checkpoint_rewind(|max_checkpoints| {
BridgeTree::<String, usize, 4>::new(max_checkpoints, 0) BridgeTree::<String, usize, 4>::new(max_checkpoints)
}); });
} }
#[test] #[test]
fn rewind_remove_mark() { fn rewind_remove_mark() {
check_rewind_remove_mark(|max_checkpoints| { check_rewind_remove_mark(|max_checkpoints| {
BridgeTree::<String, usize, 4>::new(max_checkpoints, 0) BridgeTree::<String, usize, 4>::new(max_checkpoints)
}); });
} }
#[test] #[test]
fn garbage_collect() { fn garbage_collect() {
let mut tree: BridgeTree<String, usize, 7> = BridgeTree::new(1000, 0); let mut tree: BridgeTree<String, usize, 7> = BridgeTree::new(1000);
let empty_root = tree.root(0); let empty_root = tree.root(0);
tree.append("a".to_string()); tree.append("a".to_string());
for i in 0..100 { for i in 0..100 {
@ -1167,7 +1152,7 @@ mod tests {
tree.rewind(); tree.rewind();
assert!(tree.root(0) != empty_root); assert!(tree.root(0) != empty_root);
let mut t = BridgeTree::<String, usize, 7>::new(10, 0); let mut t = BridgeTree::<String, usize, 7>::new(10);
let mut to_unmark = vec![]; let mut to_unmark = vec![];
let mut has_witness = vec![]; let mut has_witness = vec![];
for i in 0u64..100 { for i in 0u64..100 {
@ -1213,8 +1198,8 @@ mod tests {
max_checkpoints: usize, max_checkpoints: usize,
) -> CombinedTree<H, usize, CompleteTree<H, usize, 4>, BridgeTree<H, usize, 4>> { ) -> CombinedTree<H, usize, CompleteTree<H, usize, 4>, BridgeTree<H, usize, 4>> {
CombinedTree::new( CombinedTree::new(
CompleteTree::<H, usize, 4>::new(max_checkpoints, 0), CompleteTree::<H, usize, 4>::new(max_checkpoints),
BridgeTree::<H, usize, 4>::new(max_checkpoints, 0), BridgeTree::<H, usize, 4>::new(max_checkpoints),
) )
} }

View File

@ -71,11 +71,11 @@ pub struct CompleteTree<H, C: Ord, const DEPTH: u8> {
impl<H: Hashable, C: Clone + Ord + core::fmt::Debug, const DEPTH: u8> CompleteTree<H, C, DEPTH> { impl<H: Hashable, C: Clone + Ord + core::fmt::Debug, const DEPTH: u8> CompleteTree<H, C, DEPTH> {
/// Creates a new, empty binary tree /// Creates a new, empty binary tree
pub fn new(max_checkpoints: usize, initial_checkpoint_id: C) -> Self { pub fn new(max_checkpoints: usize) -> Self {
Self { Self {
leaves: vec![], leaves: vec![],
marks: BTreeSet::new(), marks: BTreeSet::new(),
checkpoints: BTreeMap::from([(initial_checkpoint_id, Checkpoint::at_length(0))]), checkpoints: BTreeMap::new(),
max_checkpoints, max_checkpoints,
} }
} }
@ -140,22 +140,18 @@ impl<H: Hashable, C: Clone + Ord + core::fmt::Debug, const DEPTH: u8> CompleteTr
/// Marks the current tree state leaf as a value that we're interested in /// Marks the current tree state leaf as a value that we're interested in
/// marking. Returns the current position if the tree is non-empty. /// marking. Returns the current position if the tree is non-empty.
fn mark(&mut self) -> Option<Position> { fn mark(&mut self) -> Option<Position> {
match self.current_position() { if let Some(pos) = self.current_position() {
Some(pos) => { if !self.marks.contains(&pos) {
if !self.marks.contains(&pos) { self.marks.insert(pos);
self.marks.insert(pos);
self.checkpoints if let Some(checkpoint) = self.checkpoints.values_mut().rev().next() {
.iter_mut() checkpoint.marked.insert(pos);
.rev()
.next()
.unwrap()
.1
.marked
.insert(pos);
} }
Some(pos)
} }
None => None,
Some(pos)
} else {
None
} }
} }
@ -282,14 +278,11 @@ impl<H: Hashable + PartialEq + Clone, C: Ord + Clone + core::fmt::Debug, const D
fn remove_mark(&mut self, position: Position) -> bool { fn remove_mark(&mut self, position: Position) -> bool {
if self.marks.contains(&position) { if self.marks.contains(&position) {
self.checkpoints if let Some(c) = self.checkpoints.values_mut().rev().next() {
.iter_mut() c.forgotten.insert(position);
.rev() } else {
.next() self.marks.remove(&position);
.unwrap() }
.1
.forgotten
.insert(position);
true true
} else { } else {
false false
@ -297,7 +290,7 @@ impl<H: Hashable + PartialEq + Clone, C: Ord + Clone + core::fmt::Debug, const D
} }
fn checkpoint(&mut self, id: C) -> bool { fn checkpoint(&mut self, id: C) -> bool {
if Some(&id) > self.checkpoints.iter().rev().next().map(|(id, _)| id) { if Some(&id) > self.checkpoints.keys().rev().next() {
Self::checkpoint(self, id, self.current_position()); Self::checkpoint(self, id, self.current_position());
true true
} else { } else {
@ -306,8 +299,7 @@ impl<H: Hashable + PartialEq + Clone, C: Ord + Clone + core::fmt::Debug, const D
} }
fn rewind(&mut self) -> bool { fn rewind(&mut self) -> bool {
if self.checkpoints.len() > 1 { if let Some((id, c)) = self.checkpoints.iter().rev().next() {
let (id, c) = self.checkpoints.iter().rev().next().unwrap();
self.leaves.truncate(c.leaves_len); self.leaves.truncate(c.leaves_len);
for pos in c.marked.iter() { for pos in c.marked.iter() {
self.marks.remove(pos); self.marks.remove(pos);
@ -342,7 +334,7 @@ mod tests {
expected = SipHashable::combine(lvl.into(), &expected, &expected); expected = SipHashable::combine(lvl.into(), &expected, &expected);
} }
let tree = CompleteTree::<SipHashable, (), DEPTH>::new(100, ()); let tree = CompleteTree::<SipHashable, (), DEPTH>::new(100);
assert_eq!(tree.root(0).unwrap(), expected); assert_eq!(tree.root(0).unwrap(), expected);
} }
@ -351,7 +343,7 @@ mod tests {
const DEPTH: u8 = 3; const DEPTH: u8 = 3;
let values = (0..(1 << DEPTH)).map(SipHashable); let values = (0..(1 << DEPTH)).map(SipHashable);
let mut tree = CompleteTree::<SipHashable, (), DEPTH>::new(100, ()); let mut tree = CompleteTree::<SipHashable, (), DEPTH>::new(100);
for value in values { for value in values {
assert!(tree.append(value, Retention::Ephemeral).is_ok()); assert!(tree.append(value, Retention::Ephemeral).is_ok());
} }
@ -376,21 +368,17 @@ mod tests {
#[test] #[test]
fn append() { fn append() {
check_append(|max_checkpoints| CompleteTree::<String, usize, 4>::new(max_checkpoints, 0)); check_append(CompleteTree::<String, usize, 4>::new);
} }
#[test] #[test]
fn root_hashes() { fn root_hashes() {
check_root_hashes(|max_checkpoints| { check_root_hashes(CompleteTree::<String, usize, 4>::new);
CompleteTree::<String, usize, 4>::new(max_checkpoints, 0)
});
} }
#[test] #[test]
fn witnesses() { fn witnesses() {
check_witnesses(|max_checkpoints| { check_witnesses(CompleteTree::<String, usize, 4>::new);
CompleteTree::<String, usize, 4>::new(max_checkpoints, 0)
});
} }
#[test] #[test]
@ -400,7 +388,7 @@ mod tests {
const DEPTH: u8 = 3; const DEPTH: u8 = 3;
let values = (0..(1 << DEPTH)).map(SipHashable); let values = (0..(1 << DEPTH)).map(SipHashable);
let mut tree = CompleteTree::<SipHashable, (), DEPTH>::new(100, ()); let mut tree = CompleteTree::<SipHashable, (), DEPTH>::new(100);
for value in values { for value in values {
assert!(Tree::append(&mut tree, value, Retention::Marked)); assert!(Tree::append(&mut tree, value, Retention::Marked));
} }
@ -435,14 +423,14 @@ mod tests {
#[test] #[test]
fn checkpoint_rewind() { fn checkpoint_rewind() {
check_checkpoint_rewind(|max_checkpoints| { check_checkpoint_rewind(|max_checkpoints| {
CompleteTree::<String, usize, 4>::new(max_checkpoints, 0) CompleteTree::<String, usize, 4>::new(max_checkpoints)
}); });
} }
#[test] #[test]
fn rewind_remove_mark() { fn rewind_remove_mark() {
check_rewind_remove_mark(|max_checkpoints| { check_rewind_remove_mark(|max_checkpoints| {
CompleteTree::<String, usize, 4>::new(max_checkpoints, 0) CompleteTree::<String, usize, 4>::new(max_checkpoints)
}); });
} }
} }

View File

@ -16,3 +16,7 @@ cc cf1a33ef6df58bbf7cc199b8b1879c3a078e7784aa3edc0aba9ca03772bea5f2 # shrinks to
cc 544e027d994eaf7f97b1c8d9ee7b35522a64a610b1430d56d74ec947018b759d # shrinks to ops = [Append("a", Marked), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Checkpoint(()), Append("a", Marked), Checkpoint(()), Checkpoint(()), Append("a", Checkpoint { id: (), is_marked: false }), Rewind, Rewind, Witness(Position(7), 2)] cc 544e027d994eaf7f97b1c8d9ee7b35522a64a610b1430d56d74ec947018b759d # shrinks to ops = [Append("a", Marked), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Append("a", Ephemeral), Checkpoint(()), Append("a", Marked), Checkpoint(()), Checkpoint(()), Append("a", Checkpoint { id: (), is_marked: false }), Rewind, Rewind, Witness(Position(7), 2)]
cc 55d00b68a0f0a02f83ab53f18a29d16d0233153b69a01414a1622104e0eead31 # shrinks to ops = [Append("a", Marked), Append("a", Checkpoint { id: (), is_marked: false }), Append("a", Marked), Checkpoint(()), Checkpoint(()), Checkpoint(()), Append("a", Checkpoint { id: (), is_marked: false }), Append("a", Checkpoint { id: (), is_marked: false }), Witness(Position(0), 7)] cc 55d00b68a0f0a02f83ab53f18a29d16d0233153b69a01414a1622104e0eead31 # shrinks to ops = [Append("a", Marked), Append("a", Checkpoint { id: (), is_marked: false }), Append("a", Marked), Checkpoint(()), Checkpoint(()), Checkpoint(()), Append("a", Checkpoint { id: (), is_marked: false }), Append("a", Checkpoint { id: (), is_marked: false }), Witness(Position(0), 7)]
cc 9dd966ff1ab66965c5b84153ae13f684258560cdd5e84c7deb24f724cb12aba7 # shrinks to ops = [Append("a", Marked), Append("a", Ephemeral), Append("a", Checkpoint { id: (), is_marked: true }), Checkpoint(()), Append("a", Checkpoint { id: (), is_marked: false }), Rewind, Rewind, Append("a", Checkpoint { id: (), is_marked: false }), Append("a", Checkpoint { id: (), is_marked: false }), Checkpoint(()), Witness(Position(2), 4)] cc 9dd966ff1ab66965c5b84153ae13f684258560cdd5e84c7deb24f724cb12aba7 # shrinks to ops = [Append("a", Marked), Append("a", Ephemeral), Append("a", Checkpoint { id: (), is_marked: true }), Checkpoint(()), Append("a", Checkpoint { id: (), is_marked: false }), Rewind, Rewind, Append("a", Checkpoint { id: (), is_marked: false }), Append("a", Checkpoint { id: (), is_marked: false }), Checkpoint(()), Witness(Position(2), 4)]
cc d53a73021238de143764ee1d48b944abb93bd4bc54f35d16e514261220d3eb78 # shrinks to ops = [Append(SipHashable(0), Marked), Unmark(Position(0))]
cc d9460b8acbc5b4d112cae5d9e2296fcd793999b2b2e1d5405722f2bd8d176c31 # shrinks to ops = [Append("a", Checkpoint { id: (), is_marked: true }), Rewind, Append("a", Ephemeral), Rewind, Unmark(Position(0))]
cc 644c7763bc7bdc65bd9e6eb156b3b1a9b0632571a571c462bd44f3e04a389ca0 # shrinks to ops = [Append("a", Ephemeral), Append("a", Checkpoint { id: (), is_marked: true }), Append("a", Ephemeral), Append("a", Ephemeral), Unmark(Position(1)), Witness(Position(1), 0)]
cc 12790169d3df4280dd155d9cdfa76719318b8ec97a80bd562b7cb182d4f9bc79 # shrinks to ops = [CurrentPosition, CurrentPosition, Append(SipHashable(0), Ephemeral), Append(SipHashable(0), Marked), Append(SipHashable(0), Ephemeral), Checkpoint(()), Checkpoint(()), Checkpoint(()), Unmark(Position(1)), Checkpoint(()), Witness(Position(1), 0)]

View File

@ -1761,21 +1761,12 @@ impl<
where where
S::Error: From<InsertionError> + From<QueryError>, S::Error: From<InsertionError> + From<QueryError>,
{ {
/// Creates a new empty tree and establishes a checkpoint for the empty tree at the given /// Creates a new empty tree.
/// checkpoint identifier. pub fn empty(store: S, max_checkpoints: usize) -> Self {
pub fn empty( Self {
store: S,
max_checkpoints: usize,
initial_checkpoint_id: C,
) -> Result<Self, S::Error> {
let mut result = Self {
store, store,
max_checkpoints, max_checkpoints,
}; }
result
.store
.add_checkpoint(initial_checkpoint_id, Checkpoint::tree_empty())?;
Ok(result)
} }
/// Constructs a wrapper around the provided shard store without initialization. /// Constructs a wrapper around the provided shard store without initialization.
@ -1797,6 +1788,11 @@ where
Level::from(SHARD_HEIGHT - 1) Level::from(SHARD_HEIGHT - 1)
} }
/// Returns the root address of the subtree that contains the specified position.
pub fn subtree_addr(pos: Position) -> Address {
Address::above_position(Self::subtree_level(), pos)
}
/// Returns the leaf value at the specified position, if it is a marked leaf. /// Returns the leaf value at the specified position, if it is a marked leaf.
pub fn get_marked_leaf(&self, position: Position) -> Result<Option<H>, S::Error> { pub fn get_marked_leaf(&self, position: Position) -> Result<Option<H>, S::Error> {
Ok(self Ok(self
@ -2031,19 +2027,13 @@ where
// Update the rightmost subtree to add the `CHECKPOINT` flag to the right-most leaf (which // Update the rightmost subtree to add the `CHECKPOINT` flag to the right-most leaf (which
// need not be a level-0 leaf; it's fine to rewind to a pruned state). // need not be a level-0 leaf; it's fine to rewind to a pruned state).
if let Some(subtree) = self.store.last_shard()? { if let Some(subtree) = self.store.last_shard()? {
if let Some((replacement, checkpoint_position)) = go(subtree.root_addr, &subtree.root) { if let Some((replacement, pos)) = go(subtree.root_addr, &subtree.root) {
if self self.store.put_shard(LocatedTree {
.store root_addr: subtree.root_addr,
.put_shard(LocatedTree { root: replacement,
root_addr: subtree.root_addr, })?;
root: replacement,
})
.is_err()
{
return Ok(false);
}
self.store self.store
.add_checkpoint(checkpoint_id, Checkpoint::at_position(checkpoint_position))?; .add_checkpoint(checkpoint_id, Checkpoint::at_position(pos))?;
// early return once we've updated the tree state // early return once we've updated the tree state
self.prune_excess_checkpoints()?; self.prune_excess_checkpoints()?;
@ -2132,36 +2122,32 @@ where
) -> Result<bool, S::Error> { ) -> Result<bool, S::Error> {
Ok(if checkpoint_depth == 0 { Ok(if checkpoint_depth == 0 {
true true
} else if self.store.checkpoint_count()? > 1 { } else if let Some((checkpoint_id, c)) =
if let Some((checkpoint_id, c)) = self.store.get_checkpoint_at_depth(checkpoint_depth)?
self.store.get_checkpoint_at_depth(checkpoint_depth)? {
{ match c.tree_state {
match c.tree_state { TreeState::Empty => {
TreeState::Empty => { self.store
self.store .truncate(Address::from_parts(Self::subtree_level(), 0))?;
.truncate(Address::from_parts(Self::subtree_level(), 0))?; self.store.truncate_checkpoints(&checkpoint_id)?;
true
}
TreeState::AtPosition(position) => {
let subtree_addr = Address::above_position(Self::subtree_level(), position);
let replacement = self
.store
.get_shard(subtree_addr)?
.and_then(|s| s.truncate_to_position(position));
if let Some(truncated) = replacement {
self.store.truncate(subtree_addr)?;
self.store.put_shard(truncated)?;
self.store.truncate_checkpoints(&checkpoint_id)?; self.store.truncate_checkpoints(&checkpoint_id)?;
true true
} } else {
TreeState::AtPosition(position) => { false
let subtree_addr = Address::above_position(Self::subtree_level(), position);
let replacement = self
.store
.get_shard(subtree_addr)?
.and_then(|s| s.truncate_to_position(position));
if let Some(truncated) = replacement {
self.store.truncate(subtree_addr)?;
self.store.put_shard(truncated)?;
self.store.truncate_checkpoints(&checkpoint_id)?;
true
} else {
false
}
} }
} }
} else {
false
} }
} else { } else {
false false
@ -2361,31 +2347,43 @@ where
pub fn remove_mark( pub fn remove_mark(
&mut self, &mut self,
position: Position, position: Position,
as_of_checkpoint: &C, as_of_checkpoint: Option<&C>,
) -> Result<bool, S::Error> { ) -> Result<bool, S::Error>
where
C: std::fmt::Debug,
{
#[allow(clippy::blocks_in_if_conditions)] #[allow(clippy::blocks_in_if_conditions)]
if self.get_marked_leaf(position)?.is_some() { if self.get_marked_leaf(position)?.is_some() {
if self match as_of_checkpoint {
.store Some(cid) if Some(cid) >= self.store.min_checkpoint_id()?.as_ref() => {
.update_checkpoint_with(as_of_checkpoint, |checkpoint| { self.store.update_checkpoint_with(cid, |checkpoint| {
checkpoint.marks_removed.insert(position); checkpoint.marks_removed.insert(position);
Ok(()) Ok(())
})? })
{ }
return Ok(true); _ => {
} // if no checkpoint was provided, or if the checkpoint is too far in the past,
// remove the mark directly.
if let Some(cid) = self.store.min_checkpoint_id()? { let cleared =
if self.store.update_checkpoint_with(&cid, |checkpoint| { self.store
checkpoint.marks_removed.insert(position); .get_shard(Self::subtree_addr(position))?
Ok(()) .map(|subtree| {
})? { subtree.clear_flags(BTreeMap::from([(
return Ok(true); position,
RetentionFlags::MARKED,
)]))
});
if let Some(cleared) = cleared {
self.store.put_shard(cleared)?;
Ok(true)
} else {
Ok(false)
}
} }
} }
} else {
Ok(false)
} }
Ok(false)
} }
} }
@ -2786,7 +2784,7 @@ mod tests {
#[test] #[test]
fn shardtree_insertion() { fn shardtree_insertion() {
let mut tree: ShardTree<MemoryShardStore<String, usize>, 4, 3> = let mut tree: ShardTree<MemoryShardStore<String, usize>, 4, 3> =
ShardTree::empty(MemoryShardStore::empty(), 100, 0).unwrap(); ShardTree::empty(MemoryShardStore::empty(), 100);
assert_matches!( assert_matches!(
tree.batch_insert( tree.batch_insert(
Position::from(1), Position::from(1),
@ -2915,11 +2913,12 @@ mod tests {
} }
fn remove_mark(&mut self, position: Position) -> bool { fn remove_mark(&mut self, position: Position) -> bool {
if let Ok(Some(c)) = self.store.max_checkpoint_id() { ShardTree::remove_mark(
ShardTree::remove_mark(self, position, &c).unwrap() self,
} else { position,
false self.store.max_checkpoint_id().unwrap().as_ref(),
} )
.unwrap()
} }
fn checkpoint(&mut self, checkpoint_id: C) -> bool { fn checkpoint(&mut self, checkpoint_id: C) -> bool {
@ -2934,60 +2933,35 @@ mod tests {
#[test] #[test]
fn append() { fn append() {
check_append(|m| { check_append(|m| {
ShardTree::<MemoryShardStore<String, usize>, 4, 3>::empty( ShardTree::<MemoryShardStore<String, usize>, 4, 3>::empty(MemoryShardStore::empty(), m)
MemoryShardStore::empty(),
m,
0,
)
.unwrap()
}); });
} }
#[test] #[test]
fn root_hashes() { fn root_hashes() {
check_root_hashes(|m| { check_root_hashes(|m| {
ShardTree::<MemoryShardStore<String, usize>, 4, 3>::empty( ShardTree::<MemoryShardStore<String, usize>, 4, 3>::empty(MemoryShardStore::empty(), m)
MemoryShardStore::empty(),
m,
0,
)
.unwrap()
}); });
} }
#[test] #[test]
fn witnesses() { fn witnesses() {
check_witnesses(|m| { check_witnesses(|m| {
ShardTree::<MemoryShardStore<String, usize>, 4, 3>::empty( ShardTree::<MemoryShardStore<String, usize>, 4, 3>::empty(MemoryShardStore::empty(), m)
MemoryShardStore::empty(),
m,
0,
)
.unwrap()
}); });
} }
#[test] #[test]
fn checkpoint_rewind() { fn checkpoint_rewind() {
check_checkpoint_rewind(|m| { check_checkpoint_rewind(|m| {
ShardTree::<MemoryShardStore<String, usize>, 4, 3>::empty( ShardTree::<MemoryShardStore<String, usize>, 4, 3>::empty(MemoryShardStore::empty(), m)
MemoryShardStore::empty(),
m,
0,
)
.unwrap()
}); });
} }
#[test] #[test]
fn rewind_remove_mark() { fn rewind_remove_mark() {
check_rewind_remove_mark(|m| { check_rewind_remove_mark(|m| {
ShardTree::<MemoryShardStore<String, usize>, 4, 3>::empty( ShardTree::<MemoryShardStore<String, usize>, 4, 3>::empty(MemoryShardStore::empty(), m)
MemoryShardStore::empty(),
m,
0,
)
.unwrap()
}); });
} }
@ -3002,8 +2976,8 @@ mod tests {
ShardTree<MemoryShardStore<H, usize>, 4, 3>, ShardTree<MemoryShardStore<H, usize>, 4, 3>,
> { > {
CombinedTree::new( CombinedTree::new(
CompleteTree::new(max_checkpoints, 0), CompleteTree::new(max_checkpoints),
ShardTree::empty(MemoryShardStore::empty(), max_checkpoints, 0).unwrap(), ShardTree::empty(MemoryShardStore::empty(), max_checkpoints),
) )
} }