Merge pull request #63 from nuttycom/bridgetree_checkpoint_id
Make `bridgetree` polymorphic in checkpoint identifier type.
This commit is contained in:
commit
739441a892
|
@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue