Merge pull request #42 from nuttycom/sharing_tree
Alter MerkleBridge to avoid storing duplicate ommers.
This commit is contained in:
commit
40e31f340c
|
@ -8,8 +8,9 @@ use std::collections::{BTreeMap, BTreeSet};
|
|||
use std::convert::TryFrom;
|
||||
use std::fmt::Debug;
|
||||
use std::mem::size_of;
|
||||
use std::ops::Range;
|
||||
|
||||
use super::{Altitude, Hashable, Position, Tree};
|
||||
use super::{Address, Altitude, Hashable, Position, Source, Tree};
|
||||
|
||||
/// A set of leaves of a Merkle tree.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
@ -27,12 +28,20 @@ impl<A> Leaf<A> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum FrontierError {
|
||||
PositionMismatch { expected_ommers: usize },
|
||||
MaxDepthExceeded { altitude: Altitude },
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum PathError {
|
||||
PositionNotWitnessed(Position),
|
||||
BridgeFusionError,
|
||||
FrontierAddressInvalid(Address),
|
||||
BridgeAddressInvalid(Address),
|
||||
}
|
||||
|
||||
/// A `[NonEmptyFrontier]` is a reduced representation of a Merkle tree,
|
||||
/// having either one or two leaf values, and then a set of hashes produced
|
||||
/// by the reduction of previously appended leaf values.
|
||||
|
@ -94,13 +103,6 @@ impl<H> NonEmptyFrontier<H> {
|
|||
pub fn ommers(&self) -> &[H] {
|
||||
&self.ommers
|
||||
}
|
||||
|
||||
/// Returns the value of the most recently appended leaf.
|
||||
pub fn leaf_value(&self) -> &H {
|
||||
match &self.leaf {
|
||||
Leaf::Left(v) | Leaf::Right(_, v) => v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: Hashable + Clone> NonEmptyFrontier<H> {
|
||||
|
@ -124,7 +126,7 @@ impl<H: Hashable + Clone> NonEmptyFrontier<H> {
|
|||
};
|
||||
|
||||
if carry.is_some() {
|
||||
let mut new_ommers = Vec::with_capacity(self.position.altitudes_required().count());
|
||||
let mut new_ommers = Vec::with_capacity(self.position.count_altitudes_required());
|
||||
for (ommer, ommer_lvl) in self.ommers.iter().zip(self.position.ommer_altitudes()) {
|
||||
if let Some((carry_ommer, carry_lvl)) = carry.as_ref() {
|
||||
if *carry_lvl == ommer_lvl {
|
||||
|
@ -158,15 +160,15 @@ impl<H: Hashable + Clone> NonEmptyFrontier<H> {
|
|||
Self::inner_root(self.position, &self.leaf, &self.ommers, None)
|
||||
}
|
||||
|
||||
/// If the tree is full to the specified altitude, return the data
|
||||
/// required to witness a sibling at that altitude.
|
||||
/// If the tree is full to the specified altitude, return root at
|
||||
/// that altitude
|
||||
pub fn witness(&self, sibling_altitude: Altitude) -> Option<H> {
|
||||
if sibling_altitude == Altitude::zero() {
|
||||
match &self.leaf {
|
||||
Leaf::Left(_) => None,
|
||||
Leaf::Right(_, a) => Some(a.clone()),
|
||||
}
|
||||
} else if self.position.is_complete(sibling_altitude) {
|
||||
} else if self.position.is_complete_subtree(sibling_altitude) {
|
||||
// the "incomplete" subtree root is actually complete
|
||||
// if the tree is full to this altitude
|
||||
Some(Self::inner_root(
|
||||
|
@ -183,9 +185,7 @@ impl<H: Hashable + Clone> NonEmptyFrontier<H> {
|
|||
/// If the tree is not full, generate the root of the incomplete subtree
|
||||
/// by hashing with empty branches
|
||||
pub fn witness_incomplete(&self, altitude: Altitude) -> Option<H> {
|
||||
if self.position.is_complete(altitude) {
|
||||
// if the tree is complete to this altitude, its hash should
|
||||
// have already been included in an auth fragment.
|
||||
if self.position.is_complete_subtree(altitude) {
|
||||
None
|
||||
} else {
|
||||
Some(if altitude == Altitude::zero() {
|
||||
|
@ -201,6 +201,53 @@ impl<H: Hashable + Clone> NonEmptyFrontier<H> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn value_at(&self, addr: Address) -> Option<H> {
|
||||
if addr.level() == Altitude::zero() {
|
||||
match &self.leaf {
|
||||
Leaf::Left(a) => {
|
||||
if addr.is_complete_node() {
|
||||
None
|
||||
} else {
|
||||
Some(a)
|
||||
}
|
||||
}
|
||||
Leaf::Right(a, b) => Some(if addr.is_complete_node() { b } else { a }),
|
||||
}
|
||||
.cloned()
|
||||
} else {
|
||||
self.position.ommer_index(addr.level()).and_then(|i| {
|
||||
if addr.is_complete_node() {
|
||||
// we don't have an ommer yet, but we have enough information
|
||||
// to compute the value
|
||||
self.witness(addr.level())
|
||||
} else {
|
||||
Some(self.ommers[i].clone())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs an authentication path for the leaf at the tip of this
|
||||
/// frontier, given a source of node values that complement this frontier.
|
||||
pub fn authentication_path<F>(&self, depth: u8, bridge_value_at: F) -> Result<Vec<H>, PathError>
|
||||
where
|
||||
F: Fn(Address) -> Option<H>,
|
||||
{
|
||||
// construct a complete trailing edge that includes the data from
|
||||
// the following frontier not yet included in the trailing edge.
|
||||
Address::from(self.position())
|
||||
.auth_path(depth.into())
|
||||
.map(|(addr, source)| match source {
|
||||
Source::Past => self
|
||||
.value_at(addr)
|
||||
.ok_or(PathError::FrontierAddressInvalid(addr)),
|
||||
Source::Future => {
|
||||
bridge_value_at(addr).ok_or(PathError::BridgeAddressInvalid(addr))
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
}
|
||||
|
||||
// returns
|
||||
fn inner_root(
|
||||
position: Position,
|
||||
|
@ -310,7 +357,7 @@ impl<H, const DEPTH: u8> Frontier<H, DEPTH> {
|
|||
impl<H: Hashable + Clone, const DEPTH: u8> crate::Frontier<H> for Frontier<H, DEPTH> {
|
||||
fn append(&mut self, value: &H) -> bool {
|
||||
if let Some(frontier) = self.frontier.as_mut() {
|
||||
if frontier.position().is_complete(Altitude(DEPTH)) {
|
||||
if frontier.position().is_complete_subtree(Altitude(DEPTH)) {
|
||||
false
|
||||
} else {
|
||||
frontier.append(value.clone());
|
||||
|
@ -337,122 +384,26 @@ impl<H: Hashable + Clone, const DEPTH: u8> crate::Frontier<H> for Frontier<H, DE
|
|||
}
|
||||
}
|
||||
|
||||
/// Each AuthFragment stores part of the authentication path for the leaf at a
|
||||
/// particular position. Successive fragments may be concatenated to produce
|
||||
/// the authentication path up to one less than the maximum altitude of the
|
||||
/// Merkle frontier corresponding to the leaf at the specified position. Then,
|
||||
/// the authentication path may be completed by hashing with empty roots.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct AuthFragment<A> {
|
||||
/// The position of the leaf for which this path fragment is being constructed.
|
||||
position: Position,
|
||||
/// We track the total number of altitudes collected across all fragments
|
||||
/// constructed for the specified position separately from the length of
|
||||
/// the values vector because the values will usually be split across multiple
|
||||
/// fragments.
|
||||
altitudes_observed: usize,
|
||||
/// The subtree roots at altitudes required for the position that have not
|
||||
/// been included in preceding fragments.
|
||||
values: Vec<A>,
|
||||
}
|
||||
|
||||
impl<A> AuthFragment<A> {
|
||||
/// Construct the new empty authentication path fragment for the specified
|
||||
/// position.
|
||||
pub fn new(position: Position) -> Self {
|
||||
Self {
|
||||
position,
|
||||
altitudes_observed: 0,
|
||||
values: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a fragment from its component parts. This cannot
|
||||
/// perform any meaningful validation that the provided values
|
||||
/// are valid.
|
||||
pub fn from_parts(position: Position, altitudes_observed: usize, values: Vec<A>) -> Self {
|
||||
Self {
|
||||
position,
|
||||
altitudes_observed,
|
||||
values,
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct the successor fragment for this fragment to produce a new empty fragment
|
||||
/// for the specified position.
|
||||
#[must_use]
|
||||
pub fn successor(&self) -> Self {
|
||||
Self {
|
||||
position: self.position,
|
||||
altitudes_observed: self.altitudes_observed,
|
||||
values: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn position(&self) -> Position {
|
||||
self.position
|
||||
}
|
||||
|
||||
pub fn altitudes_observed(&self) -> usize {
|
||||
self.altitudes_observed
|
||||
}
|
||||
|
||||
pub fn values(&self) -> &[A] {
|
||||
&self.values
|
||||
}
|
||||
|
||||
pub fn is_complete(&self) -> bool {
|
||||
self.altitudes_observed >= self.position.altitudes_required().count()
|
||||
}
|
||||
|
||||
pub fn next_required_altitude(&self) -> Option<Altitude> {
|
||||
self.position
|
||||
.all_altitudes_required()
|
||||
.nth(self.altitudes_observed)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Clone> AuthFragment<A> {
|
||||
pub fn fuse(&self, other: &Self) -> Option<Self> {
|
||||
if self.position == other.position
|
||||
&& self.altitudes_observed + other.values.len() == other.altitudes_observed
|
||||
{
|
||||
Some(Self {
|
||||
position: self.position,
|
||||
altitudes_observed: other.altitudes_observed,
|
||||
values: self
|
||||
.values
|
||||
.iter()
|
||||
.chain(other.values.iter())
|
||||
.cloned()
|
||||
.collect(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: Hashable + Clone + PartialEq> AuthFragment<H> {
|
||||
pub fn augment(&mut self, frontier: &NonEmptyFrontier<H>) {
|
||||
if let Some(altitude) = self.next_required_altitude() {
|
||||
if let Some(digest) = frontier.witness(altitude) {
|
||||
self.values.push(digest);
|
||||
self.altitudes_observed += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct MerkleBridge<H: Ord> {
|
||||
/// The position of the final leaf in the frontier of the
|
||||
/// bridge that this bridge is the successor of, or None
|
||||
/// if this is the first bridge in a tree.
|
||||
/// The position of the final leaf in the frontier of the bridge that this bridge is the
|
||||
/// successor of, or None if this is the first bridge in a tree.
|
||||
prior_position: Option<Position>,
|
||||
/// Fragments of authorization path data for prior bridges,
|
||||
/// keyed by bridge index.
|
||||
auth_fragments: BTreeMap<Position, AuthFragment<H>>,
|
||||
/// The set of addresses for which we are waiting to discover the fragments. The values of this
|
||||
/// set and the keys of the `need` map should always be disjoint. Also, this set should
|
||||
/// never contain an address for which the sibling value has been discovered; at that point,
|
||||
/// the address is replaced in this set with its parent and the address/sibling pair is stored
|
||||
/// in `fragments`.
|
||||
///
|
||||
/// Another way to consider the contents of this set is that the values that exist in
|
||||
/// `fragments`, combined with the values in previous bridges' `fragments` and an original leaf
|
||||
/// node, already contain all the values needed to compute the value at the given address.
|
||||
/// Therefore, we are tracking that address as we do not yet have enough information to compute
|
||||
/// its sibling without filling the sibling subtree with empty nodes.
|
||||
tracking: BTreeSet<Address>,
|
||||
/// A map from addresses that were being tracked to the values of their fragments that have been
|
||||
/// discovered while scanning this bridge's range by adding leaves to the bridge's frontier.
|
||||
fragments: BTreeMap<Address, H>,
|
||||
/// The leading edge of the bridge.
|
||||
frontier: NonEmptyFrontier<H>,
|
||||
}
|
||||
|
@ -463,7 +414,8 @@ impl<H: Ord> MerkleBridge<H> {
|
|||
pub fn new(value: H) -> Self {
|
||||
Self {
|
||||
prior_position: None,
|
||||
auth_fragments: BTreeMap::new(),
|
||||
tracking: BTreeSet::new(),
|
||||
fragments: BTreeMap::new(),
|
||||
frontier: NonEmptyFrontier::new(value),
|
||||
}
|
||||
}
|
||||
|
@ -471,12 +423,14 @@ impl<H: Ord> MerkleBridge<H> {
|
|||
/// Construct a new Merkle bridge from its constituent parts.
|
||||
pub fn from_parts(
|
||||
prior_position: Option<Position>,
|
||||
auth_fragments: BTreeMap<Position, AuthFragment<H>>,
|
||||
tracking: BTreeSet<Address>,
|
||||
fragments: BTreeMap<Address, H>,
|
||||
frontier: NonEmptyFrontier<H>,
|
||||
) -> Self {
|
||||
Self {
|
||||
prior_position,
|
||||
auth_fragments,
|
||||
tracking,
|
||||
fragments,
|
||||
frontier,
|
||||
}
|
||||
}
|
||||
|
@ -493,10 +447,18 @@ impl<H: Ord> MerkleBridge<H> {
|
|||
self.frontier.position()
|
||||
}
|
||||
|
||||
/// Returns the fragments of authorization path data for prior bridges,
|
||||
/// keyed by bridge index.
|
||||
pub fn auth_fragments(&self) -> &BTreeMap<Position, AuthFragment<H>> {
|
||||
&self.auth_fragments
|
||||
/// Returns the range of positions observed by this bridge.
|
||||
pub fn position_range(&self) -> Range<Position> {
|
||||
Range {
|
||||
start: self.prior_position.unwrap_or(Position(0)),
|
||||
end: self.position() + 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the set of internal node addresses that we're searching
|
||||
/// for the fragments for.
|
||||
pub fn tracking(&self) -> &BTreeSet<Address> {
|
||||
&self.tracking
|
||||
}
|
||||
|
||||
/// Returns the non-empty frontier of this Merkle bridge.
|
||||
|
@ -527,34 +489,51 @@ impl<'a, H: Hashable + Ord + Clone + 'a> MerkleBridge<H> {
|
|||
/// recently appended to this bridge's frontier.
|
||||
#[must_use]
|
||||
pub fn successor(&self, witness_current_leaf: bool) -> Self {
|
||||
let result = Self {
|
||||
let mut result = Self {
|
||||
prior_position: Some(self.frontier.position()),
|
||||
auth_fragments: self
|
||||
.auth_fragments
|
||||
.iter()
|
||||
.map(|(k, v)| (*k, v.successor())) //TODO: filter_map and discard what we can
|
||||
.chain(if witness_current_leaf {
|
||||
Some((
|
||||
self.frontier.position(),
|
||||
AuthFragment::new(self.frontier.position()),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.collect(),
|
||||
tracking: self.tracking.clone(),
|
||||
fragments: BTreeMap::new(),
|
||||
frontier: self.frontier.clone(),
|
||||
};
|
||||
|
||||
if witness_current_leaf {
|
||||
result.track_current_leaf();
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn track_current_leaf(&mut self) {
|
||||
self.tracking
|
||||
.insert(Address::from(self.frontier.position()).current_incomplete());
|
||||
}
|
||||
|
||||
/// Advances this bridge's frontier by appending the specified node,
|
||||
/// and updates any auth path fragments being tracked if necessary.
|
||||
pub fn append(&mut self, value: H) {
|
||||
self.frontier.append(value);
|
||||
|
||||
for ext in self.auth_fragments.values_mut() {
|
||||
ext.augment(&self.frontier);
|
||||
let mut found = vec![];
|
||||
for address in self.tracking.iter() {
|
||||
// We know that there will only ever be one address that we're
|
||||
// tracking at a given level, because as soon as we find a
|
||||
// value for the sibling of the address we're tracking, we
|
||||
// remove the tracked address and replace it the next parent
|
||||
// of that address for which we need to find a sibling.
|
||||
if let Some(digest) = self.frontier.witness(address.level()) {
|
||||
self.fragments.insert(address.sibling(), digest);
|
||||
found.push(*address);
|
||||
}
|
||||
}
|
||||
|
||||
for address in found {
|
||||
self.tracking.remove(&address);
|
||||
|
||||
// The address of the next incomplete parent note for which
|
||||
// we need to find a sibling.
|
||||
let parent = address.next_incomplete_parent();
|
||||
assert!(!self.fragments.contains_key(&parent));
|
||||
self.tracking.insert(parent);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -583,23 +562,12 @@ impl<'a, H: Hashable + Ord + Clone + 'a> MerkleBridge<H> {
|
|||
if next.can_follow(self) {
|
||||
let fused = Self {
|
||||
prior_position: self.prior_position,
|
||||
auth_fragments: self
|
||||
.auth_fragments
|
||||
tracking: next.tracking.clone(),
|
||||
fragments: self
|
||||
.fragments
|
||||
.iter()
|
||||
.map(|(k, ext)| {
|
||||
// we only need to maintain & augment auth fragments that are in the current
|
||||
// bridge, because we only need to complete the authentication path for the
|
||||
// previous frontier, not the current one.
|
||||
next.auth_fragments
|
||||
.get(k)
|
||||
.map_or((*k, ext.clone()), |next_ext| {
|
||||
(
|
||||
*k,
|
||||
ext.fuse(next_ext)
|
||||
.expect("Found auth fragments at incompatible positions."),
|
||||
)
|
||||
})
|
||||
})
|
||||
.chain(next.fragments.iter())
|
||||
.map(|(k, v)| (*k, v.clone()))
|
||||
.collect(),
|
||||
frontier: next.frontier.clone(),
|
||||
};
|
||||
|
@ -618,8 +586,42 @@ impl<'a, H: Hashable + Ord + Clone + 'a> MerkleBridge<H> {
|
|||
iter.fold(first.cloned(), |acc, b| acc?.fuse(b))
|
||||
}
|
||||
|
||||
fn prune_auth_fragments(&mut self, to_remove: &BTreeSet<Position>) {
|
||||
self.auth_fragments.retain(|k, _| !to_remove.contains(k));
|
||||
/// If this bridge contains sufficient auth fragment information, construct an authentication
|
||||
/// path for the specified position by interleaving with values from the prior frontier. This
|
||||
/// method will panic if the position of the prior frontier does not match this bridge's prior
|
||||
/// position.
|
||||
fn authentication_path(
|
||||
&self,
|
||||
depth: u8,
|
||||
prior_frontier: &NonEmptyFrontier<H>,
|
||||
) -> Result<Vec<H>, PathError> {
|
||||
assert!(Some(prior_frontier.position()) == self.prior_position);
|
||||
|
||||
prior_frontier.authentication_path(depth, |addr| {
|
||||
let r = addr.position_range();
|
||||
if self.frontier.position() < r.start {
|
||||
Some(H::empty_root(addr.level))
|
||||
} else if r.contains(&self.frontier.position()) {
|
||||
if self.frontier.position().is_complete_subtree(addr.level) {
|
||||
self.frontier.witness(addr.level)
|
||||
} else {
|
||||
self.frontier.witness_incomplete(addr.level)
|
||||
}
|
||||
} else {
|
||||
// the frontier's position is after the end of the requested
|
||||
// range, so the requested value should exist in a stored
|
||||
// fragment
|
||||
self.fragments.get(&addr).cloned()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn retain(&mut self, witness_node_addrs: &BTreeSet<Address>) {
|
||||
// Prune away any fragments & tracking addresses we don't need
|
||||
self.tracking
|
||||
.retain(|addr| witness_node_addrs.contains(&addr.sibling()));
|
||||
self.fragments
|
||||
.retain(|addr, _| witness_node_addrs.contains(addr));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -879,7 +881,12 @@ impl<H: Hashable + Ord + Clone, const DEPTH: u8> BridgeTree<H, DEPTH> {
|
|||
pub fn from_frontier(max_checkpoints: usize, frontier: NonEmptyFrontier<H>) -> Self {
|
||||
Self {
|
||||
prior_bridges: vec![],
|
||||
current_bridge: Some(MerkleBridge::from_parts(None, BTreeMap::new(), frontier)),
|
||||
current_bridge: Some(MerkleBridge::from_parts(
|
||||
None,
|
||||
BTreeSet::new(),
|
||||
BTreeMap::new(),
|
||||
frontier,
|
||||
)),
|
||||
saved: BTreeMap::new(),
|
||||
checkpoints: vec![],
|
||||
max_checkpoints,
|
||||
|
@ -918,12 +925,106 @@ impl<H: Hashable + Ord + Clone, const DEPTH: u8> BridgeTree<H, DEPTH> {
|
|||
self.max_checkpoints,
|
||||
)
|
||||
}
|
||||
|
||||
fn authentication_path_inner(
|
||||
&self,
|
||||
position: Position,
|
||||
as_of_root: &H,
|
||||
) -> Result<Vec<H>, PathError> {
|
||||
#[derive(Debug)]
|
||||
enum AuthBase<'a> {
|
||||
Current,
|
||||
Checkpoint(usize, &'a Checkpoint),
|
||||
NotFound,
|
||||
}
|
||||
|
||||
let max_alt = Altitude(DEPTH);
|
||||
|
||||
// Find the earliest checkpoint having a matching root, or the current
|
||||
// root if it matches and there is no earlier matching checkpoint.
|
||||
let auth_base = self
|
||||
.checkpoints
|
||||
.iter()
|
||||
.enumerate()
|
||||
.rev()
|
||||
.take_while(|(_, c)| c.position(&self.prior_bridges) >= Some(position))
|
||||
.filter(|(_, c)| &c.root(&self.prior_bridges, max_alt) == as_of_root)
|
||||
.last()
|
||||
.map(|(i, c)| AuthBase::Checkpoint(i, c))
|
||||
.unwrap_or_else(|| {
|
||||
if self.root(0).as_ref() == Some(as_of_root) {
|
||||
AuthBase::Current
|
||||
} else {
|
||||
AuthBase::NotFound
|
||||
}
|
||||
});
|
||||
|
||||
let saved_idx = self
|
||||
.saved
|
||||
.get(&position)
|
||||
.or_else(|| {
|
||||
if let AuthBase::Checkpoint(i, _) = auth_base {
|
||||
// The saved position might have been forgotten since the checkpoint,
|
||||
// so look for it in each of the subsequent checkpoints' forgotten
|
||||
// items.
|
||||
self.checkpoints[i..].iter().find_map(|c| {
|
||||
// restore the forgotten position, if that position was not also witnessed
|
||||
// in the same checkpoint
|
||||
c.forgotten
|
||||
.get(&position)
|
||||
.filter(|_| !c.witnessed.contains(&position))
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.ok_or(PathError::PositionNotWitnessed(position))?;
|
||||
|
||||
let prior_frontier = &self.prior_bridges[*saved_idx].frontier;
|
||||
|
||||
// Fuse the following bridges to obtain a bridge that has all
|
||||
// of the data to the right of the selected value in the tree,
|
||||
// up to the specified checkpoint depth.
|
||||
let fuse_from = saved_idx + 1;
|
||||
let successor = match auth_base {
|
||||
AuthBase::Current => MerkleBridge::fuse_all(
|
||||
self.prior_bridges[fuse_from..]
|
||||
.iter()
|
||||
.chain(&self.current_bridge),
|
||||
),
|
||||
AuthBase::Checkpoint(_, checkpoint) if fuse_from < checkpoint.bridges_len => {
|
||||
MerkleBridge::fuse_all(self.prior_bridges[fuse_from..checkpoint.bridges_len].iter())
|
||||
}
|
||||
AuthBase::Checkpoint(_, checkpoint) if fuse_from == checkpoint.bridges_len => {
|
||||
// The successor bridge should just be the empty successor to the
|
||||
// checkpointed bridge.
|
||||
if checkpoint.bridges_len > 0 {
|
||||
Some(self.prior_bridges[checkpoint.bridges_len - 1].successor(false))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
AuthBase::Checkpoint(_, _) => {
|
||||
// if the saved index is after the checkpoint, we can't generate
|
||||
// an auth path
|
||||
None
|
||||
}
|
||||
AuthBase::NotFound => None,
|
||||
}
|
||||
.ok_or(PathError::BridgeFusionError)?;
|
||||
|
||||
successor.authentication_path(DEPTH, prior_frontier)
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: Hashable + Ord + Clone + Debug, const DEPTH: u8> Tree<H> for BridgeTree<H, DEPTH> {
|
||||
impl<H: Hashable + Ord + Clone, const DEPTH: u8> Tree<H> for BridgeTree<H, DEPTH> {
|
||||
fn append(&mut self, value: &H) -> bool {
|
||||
if let Some(bridge) = self.current_bridge.as_mut() {
|
||||
if bridge.frontier.position().is_complete(Altitude(DEPTH)) {
|
||||
if bridge
|
||||
.frontier
|
||||
.position()
|
||||
.is_complete_subtree(Altitude(DEPTH))
|
||||
{
|
||||
false
|
||||
} else {
|
||||
bridge.append(value.clone());
|
||||
|
@ -966,6 +1067,7 @@ impl<H: Hashable + Ord + Clone + Debug, const DEPTH: u8> Tree<H> for BridgeTree<
|
|||
fn witness(&mut self) -> Option<Position> {
|
||||
match self.current_bridge.take() {
|
||||
Some(mut cur_b) => {
|
||||
cur_b.track_current_leaf();
|
||||
let pos = cur_b.position();
|
||||
// If the latest bridge is a newly created checkpoint, the last prior
|
||||
// bridge will have the same position and all we need to do is mark
|
||||
|
@ -976,11 +1078,7 @@ impl<H: Hashable + Ord + Clone + Debug, const DEPTH: u8> Tree<H> for BridgeTree<
|
|||
.map_or(false, |prior_b| prior_b.position() == cur_b.position())
|
||||
{
|
||||
// the current bridge has not been advanced, so we just need to make
|
||||
// sure that we have an auth fragment tracking the witnessed leaf
|
||||
cur_b
|
||||
.auth_fragments
|
||||
.entry(pos)
|
||||
.or_insert_with(|| AuthFragment::new(pos));
|
||||
// sure that we have are tracking the witnessed leaf
|
||||
self.current_bridge = Some(cur_b);
|
||||
} else {
|
||||
let successor = cur_b.successor(true);
|
||||
|
@ -1017,11 +1115,6 @@ impl<H: Hashable + Ord + Clone + Debug, const DEPTH: u8> Tree<H> for BridgeTree<
|
|||
|
||||
fn remove_witness(&mut self, position: Position) -> bool {
|
||||
if let Some(idx) = self.saved.remove(&position) {
|
||||
// Stop tracking auth fragments for the removed position
|
||||
if let Some(cur_b) = self.current_bridge.as_mut() {
|
||||
cur_b.auth_fragments.remove(&position);
|
||||
}
|
||||
|
||||
// If the position is one that has *not* just been witnessed since the last checkpoint,
|
||||
// then add it to the set of those forgotten during the current checkpoint span so that
|
||||
// it can be restored on rollback.
|
||||
|
@ -1090,137 +1183,7 @@ impl<H: Hashable + Ord + Clone + Debug, const DEPTH: u8> Tree<H> for BridgeTree<
|
|||
}
|
||||
|
||||
fn authentication_path(&self, position: Position, as_of_root: &H) -> Option<Vec<H>> {
|
||||
#[derive(Debug)]
|
||||
enum AuthBase<'a> {
|
||||
Current,
|
||||
Checkpoint(usize, &'a Checkpoint),
|
||||
NotFound,
|
||||
}
|
||||
|
||||
let max_alt = Altitude(DEPTH);
|
||||
|
||||
// Find the earliest checkpoint having a matching root, or the current
|
||||
// root if it matches and there is no earlier matching checkpoint.
|
||||
let auth_base = self
|
||||
.checkpoints
|
||||
.iter()
|
||||
.enumerate()
|
||||
.rev()
|
||||
.take_while(|(_, c)| c.position(&self.prior_bridges) >= Some(position))
|
||||
.filter(|(_, c)| &c.root(&self.prior_bridges, max_alt) == as_of_root)
|
||||
.last()
|
||||
.map(|(i, c)| AuthBase::Checkpoint(i, c))
|
||||
.unwrap_or_else(|| {
|
||||
if self.root(0).as_ref() == Some(as_of_root) {
|
||||
AuthBase::Current
|
||||
} else {
|
||||
AuthBase::NotFound
|
||||
}
|
||||
});
|
||||
|
||||
let saved_idx = self.saved.get(&position).or_else(|| {
|
||||
if let AuthBase::Checkpoint(i, _) = auth_base {
|
||||
// The saved position might have been forgotten since the checkpoint,
|
||||
// so look for it in each of the subsequent checkpoints' forgotten
|
||||
// items.
|
||||
self.checkpoints[i..].iter().find_map(|c| {
|
||||
// restore the forgotten position, if that position was not also witnessed
|
||||
// in the same checkpoint
|
||||
c.forgotten
|
||||
.get(&position)
|
||||
.filter(|_| !c.witnessed.contains(&position))
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
saved_idx.and_then(|idx| {
|
||||
let frontier = &self.prior_bridges[*idx].frontier;
|
||||
|
||||
// Fuse the following bridges to obtain a bridge that has all
|
||||
// of the data to the right of the selected value in the tree,
|
||||
// up to the specified checkpoint depth.
|
||||
let fuse_from = idx + 1;
|
||||
let fused = match auth_base {
|
||||
AuthBase::Current => MerkleBridge::fuse_all(
|
||||
self.prior_bridges[fuse_from..]
|
||||
.iter()
|
||||
.chain(&self.current_bridge),
|
||||
),
|
||||
AuthBase::Checkpoint(_, checkpoint) if fuse_from < checkpoint.bridges_len => {
|
||||
MerkleBridge::fuse_all(
|
||||
self.prior_bridges[fuse_from..checkpoint.bridges_len].iter(),
|
||||
)
|
||||
}
|
||||
AuthBase::Checkpoint(_, checkpoint) if fuse_from == checkpoint.bridges_len => {
|
||||
// The successor bridge should just be the empty successor to the
|
||||
// checkpointed bridge.
|
||||
if checkpoint.bridges_len > 0 {
|
||||
Some(self.prior_bridges[checkpoint.bridges_len - 1].successor(false))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
AuthBase::Checkpoint(_, _) => {
|
||||
// if the saved index is after the checkpoint, we can't generate
|
||||
// an auth path
|
||||
None
|
||||
}
|
||||
AuthBase::NotFound => None,
|
||||
};
|
||||
|
||||
fused.map(|successor| {
|
||||
// construct a complete trailing edge that includes the data from
|
||||
// the following frontier not yet included in the trailing edge.
|
||||
let auth_fragment = successor.auth_fragments.get(&frontier.position());
|
||||
let rest_frontier = successor.frontier;
|
||||
|
||||
let mut auth_values = auth_fragment.iter().flat_map(|auth_fragment| {
|
||||
let last_altitude = auth_fragment.next_required_altitude();
|
||||
let last_digest =
|
||||
last_altitude.and_then(|lvl| rest_frontier.witness_incomplete(lvl));
|
||||
|
||||
// TODO: can we eliminate this .cloned()?
|
||||
auth_fragment.values.iter().cloned().chain(last_digest)
|
||||
});
|
||||
|
||||
let mut result = vec![];
|
||||
match &frontier.leaf {
|
||||
Leaf::Left(_) => {
|
||||
result.push(auth_values.next().unwrap_or_else(H::empty_leaf));
|
||||
}
|
||||
Leaf::Right(a, _) => {
|
||||
result.push(a.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for (ommer, ommer_lvl) in frontier
|
||||
.ommers
|
||||
.iter()
|
||||
.zip(frontier.position.ommer_altitudes())
|
||||
{
|
||||
for synth_lvl in (result.len() as u8)..(ommer_lvl.into()) {
|
||||
result.push(
|
||||
auth_values
|
||||
.next()
|
||||
.unwrap_or_else(|| H::empty_root(Altitude(synth_lvl))),
|
||||
)
|
||||
}
|
||||
result.push(ommer.clone());
|
||||
}
|
||||
|
||||
for synth_lvl in (result.len() as u8)..DEPTH {
|
||||
result.push(
|
||||
auth_values
|
||||
.next()
|
||||
.unwrap_or_else(|| H::empty_root(Altitude(synth_lvl))),
|
||||
);
|
||||
}
|
||||
|
||||
result
|
||||
})
|
||||
})
|
||||
self.authentication_path_inner(position, as_of_root).ok()
|
||||
}
|
||||
|
||||
fn garbage_collect(&mut self) {
|
||||
|
@ -1241,7 +1204,7 @@ impl<H: Hashable + Ord + Clone + Debug, const DEPTH: u8> Tree<H> for BridgeTree<
|
|||
|
||||
let mut cur: Option<MerkleBridge<H>> = None;
|
||||
let mut merged = 0;
|
||||
let mut prune_fragment_positions: BTreeSet<Position> = BTreeSet::new();
|
||||
let mut witness_node_addrs: BTreeSet<Address> = BTreeSet::new();
|
||||
for (i, next_bridge) in std::mem::take(&mut self.prior_bridges)
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
|
@ -1255,17 +1218,26 @@ impl<H: Hashable + Ord + Clone + Debug, const DEPTH: u8> Tree<H> for BridgeTree<
|
|||
*idx -= merged;
|
||||
}
|
||||
|
||||
// Add the elements of the auth path to the set of addresses we should
|
||||
// continue to track and retain information for
|
||||
for (addr, source) in Address::from(&cur_bridge.frontier.position())
|
||||
.auth_path(Altitude(DEPTH))
|
||||
{
|
||||
if source == Source::Future {
|
||||
witness_node_addrs.insert(addr);
|
||||
}
|
||||
}
|
||||
|
||||
self.prior_bridges.push(cur_bridge);
|
||||
next_bridge
|
||||
} else {
|
||||
// We can fuse these bridges together because we don't need to
|
||||
// remember next_bridge.
|
||||
merged += 1;
|
||||
prune_fragment_positions.insert(cur_bridge.frontier.position());
|
||||
cur_bridge.fuse(&next_bridge).unwrap()
|
||||
};
|
||||
|
||||
new_cur.prune_auth_fragments(&prune_fragment_positions);
|
||||
new_cur.retain(&witness_node_addrs);
|
||||
cur = Some(new_cur);
|
||||
} else {
|
||||
// this case will only occur for the first bridge
|
||||
|
@ -1286,7 +1258,10 @@ impl<H: Hashable + Ord + Clone + Debug, const DEPTH: u8> Tree<H> for BridgeTree<
|
|||
}
|
||||
}
|
||||
if let Err(e) = self.check_consistency() {
|
||||
panic!("Consistency check failed with {:?} for tree {:?}", e, self);
|
||||
panic!(
|
||||
"Consistency check failed after garbage collection with {:?}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1299,6 +1274,26 @@ mod tests {
|
|||
use crate::tests::{apply_operation, arb_operation};
|
||||
use crate::Tree;
|
||||
|
||||
#[test]
|
||||
fn frontier_auth_path() {
|
||||
let mut frontier = NonEmptyFrontier::<String>::new("a".to_string());
|
||||
for c in 'b'..'h' {
|
||||
frontier.append(c.to_string());
|
||||
}
|
||||
let bridge_value_at = |addr: Address| match <u8>::from(addr.level()) {
|
||||
0 => Some("h".to_string()),
|
||||
3 => Some("xxxxxxxx".to_string()),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
Ok(["h", "ef", "abcd", "xxxxxxxx"]
|
||||
.map(|v| v.to_string())
|
||||
.to_vec()),
|
||||
frontier.authentication_path(4, bridge_value_at)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tree_depth() {
|
||||
let mut tree = BridgeTree::<String, 3>::new(100);
|
||||
|
@ -1458,10 +1453,12 @@ mod tests {
|
|||
assert_eq!(t.prior_bridges().len(), 20 + 14 - 2);
|
||||
let auth_paths = has_auth_path
|
||||
.iter()
|
||||
.map(|pos| {
|
||||
t.authentication_path(*pos, &t.root(0).unwrap())
|
||||
.expect("Must be able to get auth path")
|
||||
})
|
||||
.map(
|
||||
|pos| match t.authentication_path_inner(*pos, &t.root(0).unwrap()) {
|
||||
Ok(path) => path,
|
||||
Err(e) => panic!("Failed to get auth path: {:?}", e),
|
||||
},
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
t.garbage_collect();
|
||||
// 20 = 32 - 10 (removed checkpoints) + 1 (not removed due to witness) - 3 (removed witnesses)
|
||||
|
|
305
src/lib.rs
305
src/lib.rs
|
@ -29,15 +29,15 @@ mod sample;
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeSet;
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::num::TryFromIntError;
|
||||
use std::ops::{Add, AddAssign, Sub};
|
||||
use std::ops::{Add, AddAssign, Range, Sub};
|
||||
|
||||
/// A type-safe wrapper for indexing into "levels" of a binary tree, such that
|
||||
/// nodes at altitude `0` are leaves, nodes at altitude `1` are parents
|
||||
/// of nodes at altitude `0`, and so forth. This type is capable of
|
||||
/// representing altitudes in trees containing up to 2^255 leaves.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
#[repr(transparent)]
|
||||
pub struct Altitude(u8);
|
||||
|
||||
|
@ -123,45 +123,34 @@ impl Position {
|
|||
})
|
||||
}
|
||||
|
||||
/// Returns the altitude of each cousin and/or ommer required to construct
|
||||
/// Returns the number of cousins and/or ommers required to construct
|
||||
/// an authentication path to the root of a merkle tree that has `self + 1`
|
||||
/// nodes.
|
||||
pub fn altitudes_required(&self) -> impl Iterator<Item = Altitude> + '_ {
|
||||
pub fn count_altitudes_required(&self) -> usize {
|
||||
(0..=self.max_altitude().0)
|
||||
.into_iter()
|
||||
.filter_map(move |i| {
|
||||
if self.0 == 0 || self.0 & (1 << i) == 0 {
|
||||
Some(Altitude(i))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the altitude of each cousin and/or ommer required to construct
|
||||
/// an authentication path to the root of a merkle tree containing 2^64
|
||||
/// nodes.
|
||||
pub fn all_altitudes_required(&self) -> impl Iterator<Item = Altitude> + '_ {
|
||||
(0..64).into_iter().filter_map(move |i| {
|
||||
if self.0 == 0 || self.0 & (1 << i) == 0 {
|
||||
Some(Altitude(i))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.filter(|i| self.0 == 0 || self.0 & (1 << i) == 0)
|
||||
.count()
|
||||
}
|
||||
|
||||
/// Returns whether the binary tree having `self` as the position of the
|
||||
/// rightmost leaf contains a perfect balanced tree of height
|
||||
/// `to_altitude + 1` that contains the aforesaid leaf, without requiring
|
||||
/// any empty leaves or internal nodes.
|
||||
pub fn is_complete(&self, to_altitude: Altitude) -> bool {
|
||||
for i in 0..(to_altitude.0) {
|
||||
if self.0 & (1 << i) == 0 {
|
||||
return false;
|
||||
}
|
||||
pub fn is_complete_subtree(&self, to_altitude: Altitude) -> bool {
|
||||
!(0..(to_altitude.0)).any(|l| self.0 & (1 << l) == 0)
|
||||
}
|
||||
|
||||
pub fn ommer_index(&self, level: Altitude) -> Option<usize> {
|
||||
if self.0 & (1 << level.0) == 0 || level.0 == 0 {
|
||||
// Ommers only exist where 1's appear in the binary representation of the position.
|
||||
// Ommers don't exist at level 0 because of the design of the `bridgetree::Frontier`
|
||||
// `Leaf` type. This is a problem with the design of `Frontier` though - we should
|
||||
// actually capture level-0 left nodes (in the presence of a right node) in the ommers
|
||||
// list instead, but that's too big of a refactor for right now
|
||||
None
|
||||
} else {
|
||||
Some((1..=(level.0)).filter(|l| (self.0 >> l) & 0x1 == 1).count() - 1)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -203,6 +192,171 @@ impl TryFrom<u64> for Position {
|
|||
}
|
||||
}
|
||||
|
||||
/// The address of an internal node of the Merkle tree.
|
||||
/// When `level == 0`, the index has the same value as the
|
||||
/// position.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
pub struct Address {
|
||||
level: Altitude,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum Source {
|
||||
Past,
|
||||
Future,
|
||||
}
|
||||
|
||||
impl Address {
|
||||
pub fn from_parts(level: Altitude, index: usize) -> Self {
|
||||
Address { level, index }
|
||||
}
|
||||
|
||||
pub fn position_range(&self) -> Range<Position> {
|
||||
Range {
|
||||
start: (self.index << self.level.0).try_into().unwrap(),
|
||||
end: ((self.index + 1) << self.level.0).try_into().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn level(&self) -> Altitude {
|
||||
self.level
|
||||
}
|
||||
|
||||
pub fn index(&self) -> usize {
|
||||
self.index
|
||||
}
|
||||
|
||||
pub fn parent(&self) -> Address {
|
||||
Address {
|
||||
level: self.level + 1,
|
||||
index: self.index >> 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sibling(&self) -> Address {
|
||||
Address {
|
||||
level: self.level,
|
||||
index: if self.index & 0x1 == 0 {
|
||||
self.index + 1
|
||||
} else {
|
||||
self.index - 1
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_complete_node(&self) -> bool {
|
||||
self.index & 0x1 == 1
|
||||
}
|
||||
|
||||
pub fn current_incomplete(&self) -> Address {
|
||||
// find the first zero bit in the index, searching from the least significant bit
|
||||
let mut index = self.index;
|
||||
for level in self.level.0.. {
|
||||
if index & 0x1 == 1 {
|
||||
index >>= 1;
|
||||
} else {
|
||||
return Address {
|
||||
level: Altitude(level),
|
||||
index,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
unreachable!("The loop will always terminate via return in at most 64 iterations.")
|
||||
}
|
||||
|
||||
pub fn next_incomplete_parent(&self) -> Address {
|
||||
if self.is_complete_node() {
|
||||
self.current_incomplete()
|
||||
} else {
|
||||
let complete = Address {
|
||||
level: self.level,
|
||||
index: self.index + 1,
|
||||
};
|
||||
complete.current_incomplete()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the authentication path for this address, beginning with the sibling
|
||||
/// of this node and ending with the sibling of the ancestor of this node that
|
||||
/// is required to compute a root at the specified altitude.
|
||||
pub(crate) fn auth_path(
|
||||
&self,
|
||||
to_altitude: Altitude,
|
||||
) -> impl Iterator<Item = (Address, Source)> {
|
||||
AuthPathIter {
|
||||
to_altitude,
|
||||
current: *self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Position> for Address {
|
||||
fn from(p: Position) -> Self {
|
||||
Address {
|
||||
level: Altitude::zero(),
|
||||
index: p.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Position> for Address {
|
||||
fn from(p: &'a Position) -> Self {
|
||||
Address {
|
||||
level: Altitude::zero(),
|
||||
index: (*p).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Address> for Option<Position> {
|
||||
fn from(addr: Address) -> Self {
|
||||
if addr.level == Altitude::zero() {
|
||||
Some(addr.index.into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Address> for Option<Position> {
|
||||
fn from(addr: &'a Address) -> Self {
|
||||
if addr.level == Altitude::zero() {
|
||||
Some(addr.index.into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use = "iterators are lazy and do nothing unless consumed"]
|
||||
pub(crate) struct AuthPathIter {
|
||||
to_altitude: Altitude,
|
||||
current: Address,
|
||||
}
|
||||
|
||||
impl Iterator for AuthPathIter {
|
||||
type Item = (Address, Source);
|
||||
|
||||
fn next(&mut self) -> Option<(Address, Source)> {
|
||||
if self.current.level() < self.to_altitude {
|
||||
let current = self.current;
|
||||
self.current = current.parent();
|
||||
Some((
|
||||
current.sibling(),
|
||||
if current.is_complete_node() {
|
||||
Source::Past
|
||||
} else {
|
||||
Source::Future
|
||||
},
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait describing the operations that make a value suitable for inclusion in
|
||||
/// an incremental merkle tree.
|
||||
pub trait Hashable: Sized {
|
||||
|
@ -305,7 +459,18 @@ pub(crate) mod tests {
|
|||
|
||||
use super::bridgetree::BridgeTree;
|
||||
use super::sample::{lazy_root, CompleteTree};
|
||||
use super::{Altitude, Hashable, Position, Tree};
|
||||
use super::{Address, Altitude, Hashable, Position, Source, Tree};
|
||||
|
||||
#[test]
|
||||
fn position_is_complete_subtree() {
|
||||
assert!(Position(0).is_complete_subtree(Altitude(0)));
|
||||
assert!(Position(1).is_complete_subtree(Altitude(0)));
|
||||
assert!(!Position(2).is_complete_subtree(Altitude(1)));
|
||||
assert!(Position(3).is_complete_subtree(Altitude(1)));
|
||||
assert!(!Position(4).is_complete_subtree(Altitude(2)));
|
||||
assert!(Position(7).is_complete_subtree(Altitude(2)));
|
||||
assert!(Position(u32::MAX as usize).is_complete_subtree(Altitude(32)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn position_altitudes() {
|
||||
|
@ -318,6 +483,80 @@ pub(crate) mod tests {
|
|||
assert_eq!(Position(8).max_altitude(), Altitude(3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn position_ommer_index() {
|
||||
assert_eq!(None, Position(3).ommer_index(0.into()));
|
||||
assert_eq!(Some(0), Position(3).ommer_index(1.into()));
|
||||
assert_eq!(None, Position(3).ommer_index(2.into()));
|
||||
|
||||
assert_eq!(Some(0), Position(6).ommer_index(1.into()));
|
||||
assert_eq!(Some(1), Position(6).ommer_index(2.into()));
|
||||
assert_eq!(None, Position(8).ommer_index(1.into()));
|
||||
assert_eq!(Some(0), Position(10).ommer_index(1.into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn current_incomplete() {
|
||||
let addr = |l, i| Address::from_parts(Altitude(l), i);
|
||||
assert_eq!(addr(0, 0), addr(0, 0).current_incomplete());
|
||||
assert_eq!(addr(1, 0), addr(0, 1).current_incomplete());
|
||||
assert_eq!(addr(0, 2), addr(0, 2).current_incomplete());
|
||||
assert_eq!(addr(2, 0), addr(0, 3).current_incomplete());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_incomplete_parent() {
|
||||
let addr = |l, i| Address::from_parts(Altitude(l), i);
|
||||
assert_eq!(addr(1, 0), addr(0, 0).next_incomplete_parent());
|
||||
assert_eq!(addr(1, 0), addr(0, 1).next_incomplete_parent());
|
||||
assert_eq!(addr(2, 0), addr(0, 2).next_incomplete_parent());
|
||||
assert_eq!(addr(2, 0), addr(0, 3).next_incomplete_parent());
|
||||
assert_eq!(addr(3, 0), addr(2, 0).next_incomplete_parent());
|
||||
assert_eq!(addr(1, 2), addr(0, 4).next_incomplete_parent());
|
||||
assert_eq!(addr(3, 0), addr(1, 2).next_incomplete_parent());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn address_auth_path() {
|
||||
use Source::*;
|
||||
let addr = |l, i| Address::from_parts(Altitude(l), i);
|
||||
let path_elem = |l, i, s| (Address::from_parts(Altitude(l), i), s);
|
||||
assert_eq!(
|
||||
vec![path_elem(0, 1, Future), path_elem(1, 1, Future)],
|
||||
addr(0, 0).auth_path(Altitude(2)).collect::<Vec<_>>()
|
||||
);
|
||||
assert_eq!(
|
||||
vec![path_elem(0, 3, Future), path_elem(1, 0, Past)],
|
||||
addr(0, 2).auth_path(Altitude(2)).collect::<Vec<_>>()
|
||||
);
|
||||
assert_eq!(
|
||||
vec![
|
||||
path_elem(0, 2, Past),
|
||||
path_elem(1, 0, Past),
|
||||
path_elem(2, 1, Future)
|
||||
],
|
||||
addr(0, 3).auth_path(Altitude(3)).collect::<Vec<_>>()
|
||||
);
|
||||
assert_eq!(
|
||||
vec![
|
||||
path_elem(0, 5, Future),
|
||||
path_elem(1, 3, Future),
|
||||
path_elem(2, 0, Past),
|
||||
path_elem(3, 1, Future)
|
||||
],
|
||||
addr(0, 4).auth_path(Altitude(4)).collect::<Vec<_>>()
|
||||
);
|
||||
assert_eq!(
|
||||
vec![
|
||||
path_elem(0, 7, Future),
|
||||
path_elem(1, 2, Past),
|
||||
path_elem(2, 0, Past),
|
||||
path_elem(3, 1, Future)
|
||||
],
|
||||
addr(0, 6).auth_path(Altitude(4)).collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Types and utilities for shared example tests.
|
||||
//
|
||||
|
|
Loading…
Reference in New Issue