Merge pull request #63 from nuttycom/bridgetree_checkpoint_id

Make `bridgetree` polymorphic in checkpoint identifier type.
This commit is contained in:
Kris Nuttycombe 2023-03-24 09:00:25 -06:00 committed by GitHub
commit 739441a892
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 45 additions and 50 deletions

View File

@ -477,7 +477,7 @@ impl<H> MerkleBridge<H> {
} }
} }
impl<'a, H: Hashable + Ord + Clone + 'a> MerkleBridge<H> { impl<'a, H: Hashable + Clone + Ord + 'a> MerkleBridge<H> {
/// Constructs a new bridge to follow this one. If `mark_current_leaf` is true, the successor /// Constructs a new bridge to follow this one. If `mark_current_leaf` is true, the successor
/// will track the information necessary to create a witness for the leaf most /// will track the information necessary to create a witness for the leaf most
/// recently appended to this bridge's frontier. /// recently appended to this bridge's frontier.
@ -613,9 +613,9 @@ impl<'a, H: Hashable + Ord + Clone + 'a> MerkleBridge<H> {
/// bridges; instead, we use [`Checkpoint`] values to be able to rapidly restore the cache to its /// bridges; instead, we use [`Checkpoint`] values to be able to rapidly restore the cache to its
/// previous state. /// previous state.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Checkpoint { pub struct Checkpoint<C> {
/// The unique identifier for this checkpoint. /// The unique identifier for this checkpoint.
id: usize, id: C,
/// The number of bridges that will be retained in a rewind. /// The number of bridges that will be retained in a rewind.
bridges_len: usize, bridges_len: usize,
/// A set of the positions that have been marked during the period that this /// A set of the positions that have been marked during the period that this
@ -627,10 +627,10 @@ pub struct Checkpoint {
forgotten: BTreeSet<Position>, forgotten: BTreeSet<Position>,
} }
impl Checkpoint { impl<C> Checkpoint<C> {
/// Creates a new checkpoint from its constituent parts. /// Creates a new checkpoint from its constituent parts.
pub fn from_parts( pub fn from_parts(
id: usize, id: C,
bridges_len: usize, bridges_len: usize,
marked: BTreeSet<Position>, marked: BTreeSet<Position>,
forgotten: BTreeSet<Position>, forgotten: BTreeSet<Position>,
@ -644,7 +644,7 @@ impl Checkpoint {
} }
/// Creates a new empty checkpoint for the specified [`BridgeTree`] state. /// Creates a new empty checkpoint for the specified [`BridgeTree`] state.
pub fn at_length(bridges_len: usize, id: usize) -> Self { pub fn at_length(bridges_len: usize, id: C) -> Self {
Checkpoint { Checkpoint {
id, id,
bridges_len, bridges_len,
@ -653,10 +653,9 @@ impl Checkpoint {
} }
} }
/// The unique identifier for the checkpoint, which is simply an automatically incrementing /// The unique identifier for the checkpoint.
/// index over all checkpoints that have ever been created in the history of the tree. pub fn id(&self) -> &C {
pub fn id(&self) -> usize { &self.id
self.id
} }
/// Returns the length of the [`BridgeTree::prior_bridges`] vector of the [`BridgeTree`] to /// Returns the length of the [`BridgeTree::prior_bridges`] vector of the [`BridgeTree`] to
@ -714,7 +713,7 @@ impl Checkpoint {
/// A sparse representation of a Merkle tree with linear appending of leaves that contains enough /// A sparse representation of a Merkle tree with linear appending of leaves that contains enough
/// information to produce a witness for any `mark`ed leaf. /// information to produce a witness for any `mark`ed leaf.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BridgeTree<H, const DEPTH: u8> { pub struct BridgeTree<H, C, const DEPTH: u8> {
/// The ordered list of Merkle bridges representing the history /// The ordered list of Merkle bridges representing the history
/// of the tree. There will be one bridge for each saved leaf. /// of the tree. There will be one bridge for each saved leaf.
prior_bridges: Vec<MerkleBridge<H>>, prior_bridges: Vec<MerkleBridge<H>>,
@ -727,14 +726,14 @@ pub struct BridgeTree<H, const DEPTH: u8> {
/// This deque must be maintained to have a minimum size of 1 and a maximum /// This deque must be maintained to have a minimum size of 1 and a maximum
/// size of `max_checkpoints` in order to correctly maintain mark & rewind /// size of `max_checkpoints` in order to correctly maintain mark & rewind
/// semantics. /// semantics.
checkpoints: VecDeque<Checkpoint>, checkpoints: VecDeque<Checkpoint<C>>,
/// The maximum number of checkpoints to retain. If this number is /// The maximum number of checkpoints to retain. If this number is
/// exceeded, the oldest checkpoint will be dropped when creating /// exceeded, the oldest checkpoint will be dropped when creating
/// a new checkpoint. /// a new checkpoint.
max_checkpoints: usize, max_checkpoints: usize,
} }
impl<H: Hashable + Ord + Debug, const DEPTH: u8> Debug for BridgeTree<H, DEPTH> { impl<H: Debug, C: Debug, const DEPTH: u8> Debug for BridgeTree<H, C, DEPTH> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!( write!(
f, f,
@ -756,12 +755,12 @@ pub enum BridgeTreeError {
CheckpointMismatch, CheckpointMismatch,
} }
impl<H, const DEPTH: u8> BridgeTree<H, DEPTH> { impl<H, C, const DEPTH: u8> BridgeTree<H, C, DEPTH> {
/// Construct an empty BridgeTree value with the specified maximum number of checkpoints. /// Construct an empty BridgeTree value with the specified maximum number of checkpoints.
/// ///
/// 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: usize) -> Self { pub fn new(max_checkpoints: usize, initial_checkpoint_id: C) -> Self {
assert!(max_checkpoints >= 1); assert!(max_checkpoints >= 1);
Self { Self {
prior_bridges: vec![], prior_bridges: vec![],
@ -806,7 +805,7 @@ impl<H, const DEPTH: u8> BridgeTree<H, DEPTH> {
} }
/// Returns the checkpoints to which this tree may be rewound. /// Returns the checkpoints to which this tree may be rewound.
pub fn checkpoints(&self) -> &VecDeque<Checkpoint> { pub fn checkpoints(&self) -> &VecDeque<Checkpoint<C>> {
&self.checkpoints &self.checkpoints
} }
@ -823,7 +822,7 @@ impl<H, const DEPTH: u8> BridgeTree<H, DEPTH> {
} }
} }
impl<H: Hashable + Ord + Clone, const DEPTH: u8> BridgeTree<H, 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(max_checkpoints: usize, frontier: NonEmptyFrontier<H>) -> Self { pub fn from_frontier(max_checkpoints: usize, frontier: NonEmptyFrontier<H>) -> Self {
@ -847,7 +846,7 @@ impl<H: Hashable + Ord + Clone, const DEPTH: u8> BridgeTree<H, DEPTH> {
prior_bridges: Vec<MerkleBridge<H>>, prior_bridges: Vec<MerkleBridge<H>>,
current_bridge: Option<MerkleBridge<H>>, current_bridge: Option<MerkleBridge<H>>,
saved: BTreeMap<Position, usize>, saved: BTreeMap<Position, usize>,
checkpoints: VecDeque<Checkpoint>, checkpoints: VecDeque<Checkpoint<C>>,
max_checkpoints: usize, max_checkpoints: usize,
) -> Result<Self, BridgeTreeError> { ) -> Result<Self, BridgeTreeError> {
Self::check_consistency_internal( Self::check_consistency_internal(
@ -880,7 +879,7 @@ impl<H: Hashable + Ord + Clone, const DEPTH: u8> BridgeTree<H, DEPTH> {
prior_bridges: &[MerkleBridge<H>], prior_bridges: &[MerkleBridge<H>],
current_bridge: &Option<MerkleBridge<H>>, current_bridge: &Option<MerkleBridge<H>>,
saved: &BTreeMap<Position, usize>, saved: &BTreeMap<Position, usize>,
checkpoints: &VecDeque<Checkpoint>, checkpoints: &VecDeque<Checkpoint<C>>,
max_checkpoints: usize, max_checkpoints: usize,
) -> Result<(), BridgeTreeError> { ) -> Result<(), BridgeTreeError> {
// check that saved values correspond to bridges // check that saved values correspond to bridges
@ -1050,8 +1049,8 @@ impl<H: Hashable + Ord + Clone, const DEPTH: u8> BridgeTree<H, DEPTH> {
/// It is valid to have multiple checkpoints for the same tree state, and each `rewind` call /// It is valid to have multiple checkpoints for the same tree state, and each `rewind` call
/// will remove a single checkpoint. Successive checkpoint identifiers must always be provided /// will remove a single checkpoint. Successive checkpoint identifiers must always be provided
/// in increasing order. /// in increasing order.
pub fn checkpoint(&mut self, id: usize) -> bool { pub fn checkpoint(&mut self, id: C) -> bool {
if Some(id) > self.checkpoints.back().map(|c| c.id) { if Some(&id) > self.checkpoints.back().map(|c| &c.id) {
match self.current_bridge.take() { match self.current_bridge.take() {
Some(cur_b) => { Some(cur_b) => {
// Do not create a duplicate bridge // Do not create a duplicate bridge
@ -1121,9 +1120,9 @@ impl<H: Hashable + Ord + Clone, const DEPTH: u8> BridgeTree<H, DEPTH> {
checkpoint_depth: usize, checkpoint_depth: usize,
) -> Result<Vec<H>, WitnessingError> { ) -> Result<Vec<H>, WitnessingError> {
#[derive(Debug)] #[derive(Debug)]
enum AuthBase<'a> { enum AuthBase<'a, C> {
Current, Current,
Checkpoint(usize, &'a Checkpoint), Checkpoint(usize, &'a Checkpoint<C>),
} }
// Find the earliest checkpoint having a matching root, or the current // Find the earliest checkpoint having a matching root, or the current
@ -1284,24 +1283,16 @@ mod tests {
use super::*; use super::*;
use incrementalmerkletree::{ use incrementalmerkletree::{
testing::{ testing::{
apply_operation, arb_operation, check_checkpoint_rewind, check_operations, self, apply_operation, arb_operation, check_checkpoint_rewind, check_operations,
check_remove_mark, check_rewind_remove_mark, check_root_hashes, check_witnesses, check_remove_mark, check_rewind_remove_mark, check_root_hashes, check_witnesses,
complete_tree::CompleteTree, CombinedTree, Frontier, SipHashable, Tree, complete_tree::CompleteTree, CombinedTree, SipHashable,
}, },
Hashable, Hashable,
}; };
impl<H: Hashable + Clone, const DEPTH: u8> Frontier<H> for super::Frontier<H, DEPTH> { impl<H: Hashable + Clone + Ord, const DEPTH: u8> testing::Tree<H, usize>
fn append(&mut self, value: H) -> bool { for BridgeTree<H, usize, DEPTH>
super::Frontier::append(self, value) {
}
fn root(&self) -> H {
super::Frontier::root(self)
}
}
impl<H: Hashable + Ord + Clone, const DEPTH: u8> Tree<H, usize> for BridgeTree<H, DEPTH> {
fn append(&mut self, value: H, retention: Retention<usize>) -> bool { fn append(&mut self, value: H, retention: Retention<usize>) -> bool {
let appended = BridgeTree::append(self, value); let appended = BridgeTree::append(self, value);
if appended { if appended {
@ -1449,7 +1440,7 @@ mod tests {
#[test] #[test]
fn tree_depth() { fn tree_depth() {
let mut tree = BridgeTree::<String, 3>::new(100, 0); let mut tree = BridgeTree::<String, usize, 3>::new(100, 0);
for c in 'a'..'i' { for c in 'a'..'i' {
assert!(tree.append(c.to_string())) assert!(tree.append(c.to_string()))
} }
@ -1457,7 +1448,7 @@ mod tests {
} }
fn check_garbage_collect<H: Hashable + Clone + Ord, const DEPTH: u8>( fn check_garbage_collect<H: Hashable + Clone + Ord, const DEPTH: u8>(
mut tree: BridgeTree<H, DEPTH>, mut tree: BridgeTree<H, usize, DEPTH>,
) { ) {
// Add checkpoints until we're sure everything that can be gc'ed will be gc'ed // Add checkpoints until we're sure everything that can be gc'ed will be gc'ed
for i in 0..tree.max_checkpoints { for i in 0..tree.max_checkpoints {
@ -1475,13 +1466,13 @@ mod tests {
fn arb_bridgetree<G: Strategy + Clone>( fn arb_bridgetree<G: Strategy + Clone>(
item_gen: G, item_gen: G,
max_count: usize, max_count: usize,
) -> impl Strategy<Value = BridgeTree<G::Value, 8>> ) -> impl Strategy<Value = BridgeTree<G::Value, usize, 8>>
where where
G::Value: Hashable + Ord + Clone + Debug + 'static, G::Value: Hashable + Clone + Ord + Debug + 'static,
{ {
proptest::collection::vec(arb_operation(item_gen, 0..max_count), 0..max_count).prop_map( proptest::collection::vec(arb_operation(item_gen, 0..max_count), 0..max_count).prop_map(
|ops| { |ops| {
let mut tree: BridgeTree<G::Value, 8> = BridgeTree::new(10, 0); let mut tree: BridgeTree<G::Value, usize, 8> = BridgeTree::new(10, 0);
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));
} }
@ -1517,29 +1508,33 @@ mod tests {
#[test] #[test]
fn root_hashes() { fn root_hashes() {
check_root_hashes(|max_checkpoints| BridgeTree::<String, 4>::new(max_checkpoints, 0)); check_root_hashes(|max_checkpoints| {
BridgeTree::<String, usize, 4>::new(max_checkpoints, 0)
});
} }
#[test] #[test]
fn witness() { fn witness() {
check_witnesses(|max_checkpoints| BridgeTree::<String, 4>::new(max_checkpoints, 0)); check_witnesses(|max_checkpoints| BridgeTree::<String, usize, 4>::new(max_checkpoints, 0));
} }
#[test] #[test]
fn checkpoint_rewind() { fn checkpoint_rewind() {
check_checkpoint_rewind(|max_checkpoints| BridgeTree::<String, 4>::new(max_checkpoints, 0)); check_checkpoint_rewind(|max_checkpoints| {
BridgeTree::<String, usize, 4>::new(max_checkpoints, 0)
});
} }
#[test] #[test]
fn rewind_remove_mark() { fn rewind_remove_mark() {
check_rewind_remove_mark(|max_checkpoints| { check_rewind_remove_mark(|max_checkpoints| {
BridgeTree::<String, 4>::new(max_checkpoints, 0) BridgeTree::<String, usize, 4>::new(max_checkpoints, 0)
}); });
} }
#[test] #[test]
fn garbage_collect() { fn garbage_collect() {
let mut tree: BridgeTree<String, 7> = BridgeTree::new(1000, 0); let mut tree: BridgeTree<String, usize, 7> = BridgeTree::new(1000, 0);
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 {
@ -1550,7 +1545,7 @@ mod tests {
tree.rewind(); tree.rewind();
assert!(tree.root(0) != empty_root); assert!(tree.root(0) != empty_root);
let mut t = BridgeTree::<String, 7>::new(10, 0); let mut t = BridgeTree::<String, usize, 7>::new(10, 0);
let mut to_unmark = vec![]; let mut to_unmark = vec![];
let mut has_witness = vec![]; let mut has_witness = vec![];
for i in 0usize..100 { for i in 0usize..100 {
@ -1592,12 +1587,12 @@ mod tests {
} }
// Combined tree tests // Combined tree tests
fn new_combined_tree<H: Hashable + Ord + Clone + Debug>( fn new_combined_tree<H: Hashable + Clone + Ord + Debug>(
max_checkpoints: usize, max_checkpoints: usize,
) -> CombinedTree<H, usize, CompleteTree<H, usize, 4>, BridgeTree<H, 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, 0),
BridgeTree::<H, 4>::new(max_checkpoints, 0), BridgeTree::<H, usize, 4>::new(max_checkpoints, 0),
) )
} }