incrementalmerkletree: add `Reference` retention type.

This new retention type is intended to be used when inserting frontiers
that should not automatically be pruned.

Also, improve documentation for the `Retention` type.
This commit is contained in:
Kris Nuttycombe 2024-05-20 11:14:40 -06:00
parent 57b6e8999f
commit e55ff2d7f2
9 changed files with 143 additions and 63 deletions

View File

@ -7,6 +7,15 @@ and this project adheres to Rust's notion of
## Unreleased ## Unreleased
### Added
- `incrementalmerkletree::Marking`
### Changed
- `incrementalmerkletree::Retention`
- Has added variant `Retention::Reference`
- `Retention::Checkpoint::is_marked` has been replaced by `Retention::Checkpoint::marking`
to permit checkpoints with `Reference` retention to be represented.
## [0.5.1] - 2024-03-25 ## [0.5.1] - 2024-03-25
### Added ### Added

View File

@ -62,35 +62,72 @@ pub mod witness;
#[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))] #[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))]
pub mod testing; pub mod testing;
/// An enumeration of the additional marking states that can be applied
/// to leaves with [`Retention::Checkpoint`] retention.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Marking {
/// A checkpoint with `Marked` marking will have `Marked` retention after `Checkpoint`
/// retention is removed.
Marked,
/// A checkpoint with `Reference` marking will have `Reference` retention after `Checkpoint`
/// retention is removed.
Reference,
/// A checkpoint with `None` marking will have `Ephemeral` retention after `Checkpoint`
/// retention is removed.
None,
}
/// A type for metadata that is used to determine when and how a leaf can be pruned from a tree. /// A type for metadata that is used to determine when and how a leaf can be pruned from a tree.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Retention<C> { pub enum Retention<C> {
/// A leaf with `Ephemeral` retention will be pruned whenever its sibling is also a leaf with
/// `Ephemeral` retention.
Ephemeral, Ephemeral,
Checkpoint { id: C, is_marked: bool }, /// A leaf with `Checkpoint` retention will be retained in the tree during pruning. If
/// `Checkpoint` retention is removed from the leaf, then the retention for the leaf will
/// become either `Ephemeral`, `Marked`, or `Reference` depending upon the value of the
/// `marking` field.
Checkpoint { id: C, marking: Marking },
/// A leaf with `Marked` retention will be retained in the tree during pruning. `Marked`
/// retention may only be explicitly removed.
Marked, Marked,
/// A leaf with `Reference` retention will be retained in the tree during pruning. `Reference`
/// retention is removed whenever the associated leaf is overwritten with a tree node having
/// `Ephemeral`, `Checkpoint`, or `Marked` retention.
Reference,
} }
impl<C> Retention<C> { impl<C> Retention<C> {
/// Returns whether the associated node has [`Retention::Checkpoint`] retention.
pub fn is_checkpoint(&self) -> bool { pub fn is_checkpoint(&self) -> bool {
matches!(self, Retention::Checkpoint { .. }) matches!(self, Retention::Checkpoint { .. })
} }
/// Returns whether the associated node has [`Retention::Marked`] retention
/// or [`Retention::Checkpoint`] retention with [`Marking::Marked`] marking.
pub fn is_marked(&self) -> bool { pub fn is_marked(&self) -> bool {
match self { match self {
Retention::Ephemeral => false, Retention::Marked
Retention::Checkpoint { is_marked, .. } => *is_marked, | Retention::Checkpoint {
Retention::Marked => true, marking: Marking::Marked,
..
} => true,
_ => false,
} }
} }
/// Applies the provided function to the checkpoint identifier, if any, and returns a new
/// `Retention` value having the same structure with the resulting value used as the checkpoint
/// identifier.
pub fn map<'a, D, F: Fn(&'a C) -> D>(&'a self, f: F) -> Retention<D> { pub fn map<'a, D, F: Fn(&'a C) -> D>(&'a self, f: F) -> Retention<D> {
match self { match self {
Retention::Ephemeral => Retention::Ephemeral, Retention::Ephemeral => Retention::Ephemeral,
Retention::Checkpoint { id, is_marked } => Retention::Checkpoint { Retention::Checkpoint { id, marking } => Retention::Checkpoint {
id: f(id), id: f(id),
is_marked: *is_marked, marking: *marking,
}, },
Retention::Marked => Retention::Marked, Retention::Marked => Retention::Marked,
Retention::Reference => Retention::Reference,
} }
} }
} }

View File

@ -5,7 +5,7 @@ use core::marker::PhantomData;
use proptest::prelude::*; use proptest::prelude::*;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use crate::{Hashable, Level, Position, Retention}; use crate::{Hashable, Level, Marking, Position, Retention};
pub mod complete_tree; pub mod complete_tree;
@ -208,10 +208,22 @@ impl<H: Hashable + Clone, C: Clone> Operation<H, C> {
} }
} }
/// Returns a strategy for creating a uniformly-distributed [`Marking`]
/// value.
pub fn arb_marking() -> impl Strategy<Value = Marking> {
prop_oneof![
Just(Marking::Marked),
Just(Marking::Reference),
Just(Marking::None)
]
}
/// Returns a strategy for creating a uniformly-distributed [`Retention`]
/// value.
pub fn arb_retention() -> impl Strategy<Value = Retention<()>> { pub fn arb_retention() -> impl Strategy<Value = Retention<()>> {
prop_oneof![ prop_oneof![
Just(Retention::Ephemeral), Just(Retention::Ephemeral),
any::<bool>().prop_map(|is_marked| Retention::Checkpoint { id: (), is_marked }), arb_marking().prop_map(|marking| Retention::Checkpoint { id: (), marking }),
Just(Retention::Marked), Just(Retention::Marked),
] ]
} }
@ -557,7 +569,7 @@ pub fn check_root_hashes<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: F
0, 0,
Retention::Checkpoint { Retention::Checkpoint {
id: 1, id: 1,
is_marked: true, marking: Marking::Marked,
}, },
); );
for _ in 0..3 { for _ in 0..3 {
@ -735,7 +747,7 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
0, 0,
Checkpoint { Checkpoint {
id: 1, id: 1,
is_marked: true, marking: Marking::Marked,
}, },
); );
assert!(tree.rewind()); assert!(tree.rewind());
@ -769,7 +781,7 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
6, 6,
Checkpoint { Checkpoint {
id: 1, id: 1,
is_marked: true, marking: Marking::Marked,
}, },
); );
tree.assert_append(7, Ephemeral); tree.assert_append(7, Ephemeral);
@ -838,7 +850,7 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
H::from_u64(3), H::from_u64(3),
Checkpoint { Checkpoint {
id: C::from_u64(1), id: C::from_u64(1),
is_marked: true, marking: Marking::Marked,
}, },
), ),
Append(H::from_u64(4), Marked), Append(H::from_u64(4), Marked),
@ -847,21 +859,21 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
H::from_u64(5), H::from_u64(5),
Checkpoint { Checkpoint {
id: C::from_u64(3), id: C::from_u64(3),
is_marked: false, marking: Marking::None,
}, },
), ),
Append( Append(
H::from_u64(6), H::from_u64(6),
Checkpoint { Checkpoint {
id: C::from_u64(4), id: C::from_u64(4),
is_marked: false, marking: Marking::None,
}, },
), ),
Append( Append(
H::from_u64(7), H::from_u64(7),
Checkpoint { Checkpoint {
id: C::from_u64(5), id: C::from_u64(5),
is_marked: false, marking: Marking::None,
}, },
), ),
Witness(3u64.into(), 5), Witness(3u64.into(), 5),
@ -890,7 +902,7 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
H::from_u64(0), H::from_u64(0),
Checkpoint { Checkpoint {
id: C::from_u64(1), id: C::from_u64(1),
is_marked: true, marking: Marking::Marked,
}, },
), ),
Append(H::from_u64(0), Ephemeral), Append(H::from_u64(0), Ephemeral),
@ -900,7 +912,7 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
H::from_u64(0), H::from_u64(0),
Checkpoint { Checkpoint {
id: C::from_u64(2), id: C::from_u64(2),
is_marked: false, marking: Marking::None,
}, },
), ),
Append(H::from_u64(0), Ephemeral), Append(H::from_u64(0), Ephemeral),
@ -939,7 +951,7 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
H::from_u64(0), H::from_u64(0),
Checkpoint { Checkpoint {
id: C::from_u64(4), id: C::from_u64(4),
is_marked: false, marking: Marking::None,
}, },
), ),
Rewind, Rewind,
@ -958,14 +970,14 @@ pub fn check_witnesses<H: TestHashable, C: TestCheckpoint, T: Tree<H, C>, F: Fn(
H::from_u64(0), H::from_u64(0),
Checkpoint { Checkpoint {
id: C::from_u64(1), id: C::from_u64(1),
is_marked: true, marking: Marking::Marked,
}, },
), ),
Append( Append(
H::from_u64(0), H::from_u64(0),
Checkpoint { Checkpoint {
id: C::from_u64(4), id: C::from_u64(4),
is_marked: false, marking: Marking::None,
}, },
), ),
Witness(Position(2), 2), Witness(Position(2), 2),
@ -1034,7 +1046,7 @@ pub fn check_remove_mark<C: TestCheckpoint, T: Tree<String, C>, F: Fn(usize) ->
"a", "a",
Retention::Checkpoint { Retention::Checkpoint {
id: C::from_u64(1), id: C::from_u64(1),
is_marked: true, marking: Marking::Marked,
}, },
), ),
witness(1, 1), witness(1, 1),

View File

@ -2,6 +2,7 @@
use std::cmp::min; use std::cmp::min;
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
use crate::Marking;
use crate::{testing::Tree, Hashable, Level, Position, Retention}; use crate::{testing::Tree, Hashable, Level, Position, Retention};
const MAX_COMPLETE_SIZE_ERROR: &str = "Positions of a `CompleteTree` must fit into the platform word size, because larger complete trees are not representable."; const MAX_COMPLETE_SIZE_ERROR: &str = "Positions of a `CompleteTree` must fit into the platform word size, because larger complete trees are not representable.";
@ -104,11 +105,11 @@ impl<H: Hashable, C: Clone + Ord + core::fmt::Debug, const DEPTH: u8> CompleteTr
append(&mut self.leaves, value, DEPTH)?; append(&mut self.leaves, value, DEPTH)?;
self.mark(); self.mark();
} }
Retention::Checkpoint { id, is_marked } => { Retention::Checkpoint { id, marking } => {
let latest_checkpoint = self.checkpoints.keys().rev().next(); let latest_checkpoint = self.checkpoints.keys().rev().next();
if Some(&id) > latest_checkpoint { if Some(&id) > latest_checkpoint {
append(&mut self.leaves, value, DEPTH)?; append(&mut self.leaves, value, DEPTH)?;
if is_marked { if marking == Marking::Marked {
self.mark(); self.mark();
} }
self.checkpoint(id, self.current_position()); self.checkpoint(id, self.current_position());
@ -119,7 +120,7 @@ impl<H: Hashable, C: Clone + Ord + core::fmt::Debug, const DEPTH: u8> CompleteTr
}); });
} }
} }
Retention::Ephemeral => { Retention::Ephemeral | Retention::Reference => {
append(&mut self.leaves, value, DEPTH)?; append(&mut self.leaves, value, DEPTH)?;
} }
} }

View File

@ -1,6 +1,8 @@
use std::fmt; use std::fmt;
use incrementalmerkletree::{witness::IncrementalWitness, Address, Hashable, Level, Retention}; use incrementalmerkletree::{
witness::IncrementalWitness, Address, Hashable, Level, Marking, Retention,
};
use crate::{ use crate::{
store::ShardStore, InsertionError, LocatedPrunableTree, LocatedTree, PrunableTree, store::ShardStore, InsertionError, LocatedPrunableTree, LocatedTree, PrunableTree,
@ -198,7 +200,7 @@ impl<H: Hashable + Clone + PartialEq> LocatedPrunableTree<H> {
c.ommers_iter().cloned(), c.ommers_iter().cloned(),
&Retention::Checkpoint { &Retention::Checkpoint {
id: checkpoint_id, id: checkpoint_id,
is_marked: false, marking: Marking::None,
}, },
self.root_addr.level(), self.root_addr.level(),
) )

View File

@ -25,6 +25,7 @@
use core::fmt::Debug; use core::fmt::Debug;
use either::Either; use either::Either;
use incrementalmerkletree::frontier::Frontier; use incrementalmerkletree::frontier::Frontier;
use incrementalmerkletree::Marking;
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
use std::sync::Arc; use std::sync::Arc;
use tracing::{debug, trace}; use tracing::{debug, trace};
@ -281,18 +282,19 @@ impl<
self.insert_frontier_nodes(nonempty_frontier, leaf_retention) self.insert_frontier_nodes(nonempty_frontier, leaf_retention)
} else { } else {
match leaf_retention { match leaf_retention {
Retention::Ephemeral => Ok(()), Retention::Ephemeral | Retention::Reference => Ok(()),
Retention::Checkpoint { Retention::Checkpoint {
id, id,
is_marked: false, marking: Marking::None | Marking::Reference,
} => self } => self
.store .store
.add_checkpoint(id, Checkpoint::tree_empty()) .add_checkpoint(id, Checkpoint::tree_empty())
.map_err(ShardTreeError::Storage), .map_err(ShardTreeError::Storage),
Retention::Checkpoint { Retention::Marked
is_marked: true, .. | Retention::Checkpoint {
} marking: Marking::Marked,
| Retention::Marked => Err(ShardTreeError::Insert( ..
} => Err(ShardTreeError::Insert(
InsertionError::MarkedRetentionInvalid, InsertionError::MarkedRetentionInvalid,
)), )),
} }
@ -344,7 +346,7 @@ impl<
.map_err(ShardTreeError::Storage)?; .map_err(ShardTreeError::Storage)?;
} }
if let Retention::Checkpoint { id, is_marked: _ } = leaf_retention { if let Retention::Checkpoint { id, .. } = leaf_retention {
trace!("Adding checkpoint {:?} at {:?}", id, leaf_position); trace!("Adding checkpoint {:?} at {:?}", id, leaf_position);
self.store self.store
.add_checkpoint(id, Checkpoint::at_position(leaf_position)) .add_checkpoint(id, Checkpoint::at_position(leaf_position))
@ -1310,7 +1312,7 @@ mod tests {
check_witness_consistency, check_witnesses, complete_tree::CompleteTree, CombinedTree, check_witness_consistency, check_witnesses, complete_tree::CompleteTree, CombinedTree,
SipHashable, SipHashable,
}, },
Address, Hashable, Level, Position, Retention, Address, Hashable, Level, Marking, Position, Retention,
}; };
use crate::{ use crate::{
@ -1411,7 +1413,7 @@ mod tests {
'd'.to_string(), 'd'.to_string(),
Retention::Checkpoint { Retention::Checkpoint {
id: 11, id: 11,
is_marked: false marking: Marking::None
}, },
), ),
Ok(()), Ok(()),

View File

@ -4,6 +4,7 @@ use std::collections::{BTreeMap, BTreeSet};
use std::sync::Arc; use std::sync::Arc;
use bitflags::bitflags; use bitflags::bitflags;
use incrementalmerkletree::Marking;
use incrementalmerkletree::{ use incrementalmerkletree::{
frontier::NonEmptyFrontier, Address, Hashable, Level, Position, Retention, frontier::NonEmptyFrontier, Address, Hashable, Level, Position, Retention,
}; };
@ -30,6 +31,12 @@ bitflags! {
/// A leaf with `MARKED` retention can be pruned only as a consequence of an explicit deletion /// A leaf with `MARKED` retention can be pruned only as a consequence of an explicit deletion
/// action. /// action.
const MARKED = 0b00000010; const MARKED = 0b00000010;
/// A leaf with `REFERENCE` retention will not be considered prunable until the `REFERENCE`
/// flag is removed from the leaf. The `REFERENCE` flag will be removed at any point that
/// the leaf is overwritten without `REFERENCE` retention, and `REFERENCE` retention cannot
/// be added to an existing leaf.
const REFERENCE = 0b00000100;
} }
} }
@ -47,14 +54,17 @@ impl<'a, C> From<&'a Retention<C>> for RetentionFlags {
fn from(retention: &'a Retention<C>) -> Self { fn from(retention: &'a Retention<C>) -> Self {
match retention { match retention {
Retention::Ephemeral => RetentionFlags::EPHEMERAL, Retention::Ephemeral => RetentionFlags::EPHEMERAL,
Retention::Checkpoint { is_marked, .. } => { Retention::Checkpoint {
if *is_marked { marking: Marking::Marked,
RetentionFlags::CHECKPOINT | RetentionFlags::MARKED ..
} else { } => RetentionFlags::CHECKPOINT | RetentionFlags::MARKED,
RetentionFlags::CHECKPOINT Retention::Checkpoint {
} marking: Marking::Reference,
} ..
} => RetentionFlags::CHECKPOINT | RetentionFlags::REFERENCE,
Retention::Checkpoint { .. } => RetentionFlags::CHECKPOINT,
Retention::Marked => RetentionFlags::MARKED, Retention::Marked => RetentionFlags::MARKED,
Retention::Reference => RetentionFlags::REFERENCE,
} }
} }
} }

View File

@ -219,7 +219,7 @@ mod tests {
append_str, check_operations, unmark, witness, CombinedTree, Operation, TestHashable, append_str, check_operations, unmark, witness, CombinedTree, Operation, TestHashable,
Tree, Tree,
}, },
Hashable, Position, Retention, Hashable, Marking, Position, Retention,
}; };
use super::CachingShardStore; use super::CachingShardStore;
@ -300,7 +300,7 @@ mod tests {
String::from_u64(0), String::from_u64(0),
Checkpoint { Checkpoint {
id: 1, id: 1,
is_marked: true, marking: Marking::Marked,
}, },
)); ));
for _ in 0..3 { for _ in 0..3 {
@ -542,7 +542,7 @@ mod tests {
String::from_u64(0), String::from_u64(0),
Checkpoint { Checkpoint {
id: 1, id: 1,
is_marked: true, marking: Marking::Marked,
}, },
)); ));
assert!(tree.rewind()); assert!(tree.rewind());
@ -584,7 +584,7 @@ mod tests {
String::from_u64(6), String::from_u64(6),
Checkpoint { Checkpoint {
id: 1, id: 1,
is_marked: true, marking: Marking::Marked,
}, },
)); ));
assert!(tree.append(String::from_u64(7), Ephemeral)); assert!(tree.append(String::from_u64(7), Ephemeral));
@ -671,7 +671,7 @@ mod tests {
String::from_u64(3), String::from_u64(3),
Checkpoint { Checkpoint {
id: 1, id: 1,
is_marked: true, marking: Marking::Marked,
}, },
), ),
Append(String::from_u64(4), Marked), Append(String::from_u64(4), Marked),
@ -680,21 +680,21 @@ mod tests {
String::from_u64(5), String::from_u64(5),
Checkpoint { Checkpoint {
id: 3, id: 3,
is_marked: false, marking: Marking::None,
}, },
), ),
Append( Append(
String::from_u64(6), String::from_u64(6),
Checkpoint { Checkpoint {
id: 4, id: 4,
is_marked: false, marking: Marking::None,
}, },
), ),
Append( Append(
String::from_u64(7), String::from_u64(7),
Checkpoint { Checkpoint {
id: 5, id: 5,
is_marked: false, marking: Marking::None,
}, },
), ),
Witness(3u64.into(), 5), Witness(3u64.into(), 5),
@ -732,7 +732,7 @@ mod tests {
String::from_u64(0), String::from_u64(0),
Checkpoint { Checkpoint {
id: 1, id: 1,
is_marked: true, marking: Marking::Marked,
}, },
), ),
Append(String::from_u64(0), Ephemeral), Append(String::from_u64(0), Ephemeral),
@ -742,7 +742,7 @@ mod tests {
String::from_u64(0), String::from_u64(0),
Checkpoint { Checkpoint {
id: 2, id: 2,
is_marked: false, marking: Marking::None,
}, },
), ),
Append(String::from_u64(0), Ephemeral), Append(String::from_u64(0), Ephemeral),
@ -790,7 +790,7 @@ mod tests {
String::from_u64(0), String::from_u64(0),
Checkpoint { Checkpoint {
id: 4, id: 4,
is_marked: false, marking: Marking::None,
}, },
), ),
Rewind, Rewind,
@ -818,14 +818,14 @@ mod tests {
String::from_u64(0), String::from_u64(0),
Checkpoint { Checkpoint {
id: 1, id: 1,
is_marked: true, marking: Marking::Marked,
}, },
), ),
Append( Append(
String::from_u64(0), String::from_u64(0),
Checkpoint { Checkpoint {
id: 4, id: 4,
is_marked: false, marking: Marking::None,
}, },
), ),
Witness(Position::from(2), 2), Witness(Position::from(2), 2),
@ -962,7 +962,7 @@ mod tests {
"a", "a",
Retention::Checkpoint { Retention::Checkpoint {
id: 1, id: 1,
is_marked: true, marking: Marking::Marked,
}, },
), ),
witness(1, 1), witness(1, 1),

View File

@ -86,7 +86,14 @@ where
leaf, leaf,
match (is_checkpoint, is_marked) { match (is_checkpoint, is_marked) {
(false, false) => Retention::Ephemeral, (false, false) => Retention::Ephemeral,
(true, is_marked) => Retention::Checkpoint { id, is_marked }, (true, is_marked) => Retention::Checkpoint {
id,
marking: if is_marked {
Marking::Marked
} else {
Marking::None
},
},
(false, true) => Retention::Marked, (false, true) => Retention::Marked,
}, },
) )
@ -121,10 +128,10 @@ where
.map(|(id, (leaf, retention))| { .map(|(id, (leaf, retention))| {
let pos = Position::try_from(id).unwrap(); let pos = Position::try_from(id).unwrap();
match retention { match retention {
Retention::Ephemeral => (), Retention::Ephemeral | Retention::Reference => (),
Retention::Checkpoint { is_marked, .. } => { Retention::Checkpoint { marking, .. } => {
checkpoint_positions.push(pos); checkpoint_positions.push(pos);
if is_marked { if marking == Marking::Marked {
marked_positions.push(pos); marked_positions.push(pos);
} }
} }
@ -234,7 +241,7 @@ pub fn check_shardtree_insertion<
tree.batch_insert( tree.batch_insert(
Position::from(1), Position::from(1),
vec![ vec![
("b".to_string(), Retention::Checkpoint { id: 1, is_marked: false }), ("b".to_string(), Retention::Checkpoint { id: 1, marking: Marking::None }),
("c".to_string(), Retention::Ephemeral), ("c".to_string(), Retention::Ephemeral),
("d".to_string(), Retention::Marked), ("d".to_string(), Retention::Marked),
].into_iter() ].into_iter()
@ -285,7 +292,7 @@ pub fn check_shardtree_insertion<
Position::from(10), Position::from(10),
vec![ vec![
("k".to_string(), Retention::Ephemeral), ("k".to_string(), Retention::Ephemeral),
("l".to_string(), Retention::Checkpoint { id: 2, is_marked: false }), ("l".to_string(), Retention::Checkpoint { id: 2, marking: Marking::None }),
("m".to_string(), Retention::Ephemeral), ("m".to_string(), Retention::Ephemeral),
].into_iter() ].into_iter()
), ),
@ -386,7 +393,7 @@ pub fn check_witness_with_pruned_subtrees<
'c' => Retention::Marked, 'c' => Retention::Marked,
'h' => Retention::Checkpoint { 'h' => Retention::Checkpoint {
id: 3, id: 3,
is_marked: false, marking: Marking::None,
}, },
_ => Retention::Ephemeral, _ => Retention::Ephemeral,
}, },