feat(smt): impl lowest common ancestor for leaf indices
This commit is contained in:
parent
c414a875f3
commit
b289e7ed73
1 changed files with 79 additions and 0 deletions
|
@ -379,6 +379,48 @@ impl<const DEPTH: u8> LeafIndex<DEPTH> {
|
|||
pub fn value(&self) -> u64 {
|
||||
self.index.value()
|
||||
}
|
||||
|
||||
/// Lowest common ancestor — finds the lowest (highest depth) [`NodeIndex`] that is an ancestor
|
||||
/// of both `self` and `rhs`.
|
||||
///
|
||||
/// The general case algorithm is `O(n)`, however leaf indexes are always at the same depth,
|
||||
/// and we only need find the depth of the lowest-common ancestor (since we can trivially get
|
||||
/// its horizontal position based on either child's position), so we can reduce this to
|
||||
/// `O(log n)`.
|
||||
pub fn lca(&self, other: &Self) -> NodeIndex {
|
||||
let mut self_scalar = self.index.to_scalar_index();
|
||||
let mut other_scalar = other.index.to_scalar_index();
|
||||
|
||||
while self_scalar != other_scalar {
|
||||
self_scalar >>= 1;
|
||||
other_scalar >>= 1;
|
||||
}
|
||||
|
||||
// Once we've shifted them enough to be equal, we've found a scalar index with the depth of
|
||||
// the lowest common ancestor. Time to convert that scalar index to a depth, and apply that
|
||||
// depth to either of our `NodeIndex`s to get the full position of that ancestor.
|
||||
|
||||
// In general, we can get the depth of a binary tree's scalar index by taking the binary
|
||||
// logarithm of that index. However, for the root node, the scalar index is 0, and the
|
||||
// logarithm is undefined for 0, so we trivally special case the root index.
|
||||
if self_scalar == 0 {
|
||||
return NodeIndex::root();
|
||||
}
|
||||
|
||||
let depth = {
|
||||
let depth = u128::ilog2(self_scalar);
|
||||
// The scalar index should not be able to exceed `u8::MAX + u64::MAX` (as those are the
|
||||
// maximum values `NodeIndex` can hold), and the binary logarithm of `u8::MAX +
|
||||
// u64::MAX` is 64, which fits in a u8. In other words, this assert should only be
|
||||
// possible to fail if `to_scalar_index()` is wildly incorrect.
|
||||
debug_assert!(depth <= u8::MAX as u32);
|
||||
depth as u8
|
||||
};
|
||||
|
||||
let mut lca = self.index;
|
||||
lca.move_up_to(depth);
|
||||
lca
|
||||
}
|
||||
}
|
||||
|
||||
impl LeafIndex<SMT_MAX_DEPTH> {
|
||||
|
@ -456,3 +498,40 @@ impl<const DEPTH: u8, K, V> MutationSet<DEPTH, K, V> {
|
|||
self.new_root
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use proptest::prelude::*;
|
||||
|
||||
use crate::merkle::{LeafIndex, NodeIndex, SMT_DEPTH};
|
||||
|
||||
prop_compose! {
|
||||
fn leaf_index()(value in 0..2u64.pow(u64::BITS - 1)) -> LeafIndex<SMT_DEPTH> {
|
||||
LeafIndex::new(value).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
proptest! {
|
||||
/// Tests that the O(log n) algorithm has the same results as the naïve version.
|
||||
#[test]
|
||||
fn test_leaf_lca(left in leaf_index(), right in leaf_index()) {
|
||||
let control: NodeIndex = {
|
||||
let mut left = left.index;
|
||||
let mut right = right.index;
|
||||
|
||||
loop {
|
||||
if left == right {
|
||||
break left;
|
||||
}
|
||||
left.move_up();
|
||||
right.move_up();
|
||||
}
|
||||
};
|
||||
|
||||
let actual: NodeIndex = left.lca(&right);
|
||||
|
||||
assert_eq!(actual, control);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue