Skip to content

Commit

Permalink
Introduce a simple binary tree type.
Browse files Browse the repository at this point in the history
  • Loading branch information
nuttycom committed Mar 8, 2023
1 parent 0ae9b49 commit 8864a84
Show file tree
Hide file tree
Showing 5 changed files with 365 additions and 12 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- uses: actions/checkout@v3
# Build benchmarks to prevent bitrot
- name: Build benchmarks
run: cargo build --workspace --benches
run: cargo build --workspace --benches --all-features

doc-links:
name: Intra-doc links
Expand Down
24 changes: 13 additions & 11 deletions incrementalmerkletree/src/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,16 @@ pub trait Tree<H, C> {

/// Creates a new checkpoint for the current tree state.
///
/// It is valid to have multiple checkpoints for the same tree state, and
/// each `rewind` call will remove a single checkpoint. Returns `false`
/// if the checkpoint identifier provided is less than or equal to the
/// maximum checkpoint identifier observed.
/// It is valid to have multiple checkpoints for the same tree state, and each `rewind` call
/// will remove a single checkpoint. Returns `false` if the checkpoint identifier provided is
/// less than or equal to the maximum checkpoint identifier observed.
fn checkpoint(&mut self, id: C) -> bool;

/// Rewinds the tree state to the previous checkpoint, and then removes
/// that checkpoint record. If there are multiple checkpoints at a given
/// tree state, the tree state will not be altered until all checkpoints
/// at that tree state have been removed using `rewind`. This function
/// return false and leave the tree unmodified if no checkpoints exist.
/// Rewinds the tree state to the previous checkpoint, and then removes that checkpoint record.
///
/// If there are multiple checkpoints at a given tree state, the tree state will not be altered
/// until all checkpoints at that tree state have been removed using `rewind`. This function
/// will return false and leave the tree unmodified if no checkpoints exist.
fn rewind(&mut self) -> bool;
}

Expand Down Expand Up @@ -288,7 +287,10 @@ pub fn check_operations<H: Hashable + Ord + Clone, C: Clone, T: Tree<H, C>>(
tree_checkpoints.push(tree_size);
}
} else {
prop_assert_eq!(tree_size, 1 << tree.depth());
prop_assert_eq!(
tree_size,
tree.current_position().map_or(0, |p| usize::from(p) + 1)
);
}
}
CurrentPosition => {
Expand Down Expand Up @@ -375,7 +377,7 @@ pub fn compute_root_from_witness<H: Hashable>(value: H, position: Position, path
// Types and utilities for cross-verification property tests
//

#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct CombinedTree<H, C, I: Tree<H, C>, E: Tree<H, C>> {
inefficient: I,
efficient: E,
Expand Down
24 changes: 24 additions & 0 deletions shardtree/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,27 @@ description = "A space-efficient Merkle tree with witnessing of marked leaves, c
homepage = "https://github.com/zcash/incrementalmerkletree"
repository = "https://github.com/zcash/incrementalmerkletree"
categories = ["algorithms", "data-structures"]

[dependencies]
either = "1.8"
incrementalmerkletree = { version = "0.3", path = "../incrementalmerkletree" }
proptest = { version = "1.0.0", optional = true }

[dev-dependencies]
assert_matches = "1.5"
criterion = "0.3"
incrementalmerkletree = { version = "0.3", path = "../incrementalmerkletree", features = ["test-dependencies"] }
proptest = "1.0.0"

[features]
test-dependencies = ["proptest"]

[target.'cfg(unix)'.dev-dependencies]
pprof = { version = "0.9", features = ["criterion", "flamegraph"] } # MSRV 1.56
inferno = ">=0.11, <0.11.5" # MSRV 1.59

[[bench]]
name = "shardtree"
harness = false
required-features = ["test-dependencies"]

86 changes: 86 additions & 0 deletions shardtree/benches/shardtree.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use criterion::{criterion_group, criterion_main, Criterion};
use proptest::prelude::*;
use proptest::strategy::ValueTree;
use proptest::test_runner::TestRunner;

use incrementalmerkletree::Address;
use shardtree::{testing::arb_tree, Node};

#[cfg(unix)]
use pprof::criterion::{Output, PProfProfiler};

// An algebra for computing the incomplete roots of a tree (the addresses at which nodes are
// `Nil`). This is used for benchmarking to determine the viability of "attribute grammars" for
// when you want to use `reduce` to compute a value that requires information to be passed top-down
// through the tree.
type RootFn = Box<dyn Fn(Address) -> Vec<Address>>;
pub fn incomplete_roots<V: 'static>(node: Node<RootFn, V>) -> RootFn {
Box::new(move |addr| match &node {
Node::Parent { left, right, .. } => {
let (left_addr, right_addr) = addr
.children()
.expect("A parent node cannot appear at level 0");
let mut left_result = left(left_addr);
let mut right_result = right(right_addr);
left_result.append(&mut right_result);
left_result
}
Node::Leaf { .. } => vec![],
Node::Nil { .. } => vec![addr],
})
}

pub fn bench_shardtree(c: &mut Criterion) {
{
//let mut group = c.benchmark_group("shardtree-incomplete");

let mut runner = TestRunner::deterministic();
let input = arb_tree(Just(()), any::<String>(), 16, 4096)
.new_tree(&mut runner)
.unwrap()
.current();
println!(
"Benchmarking with {} leaves.",
input.reduce(
&(|node| match node {
Node::Parent { left, right } => left + right,
Node::Leaf { .. } => 1,
Node::Nil => 0,
})
)
);

let input_root = Address::from_parts(
input
.reduce(
&(|node| match node {
Node::Parent { left, right } => std::cmp::max(left, right) + 1,
Node::Leaf { .. } => 0,
Node::Nil => 0,
}),
)
.into(),
0,
);

c.bench_function("direct_recursion", |b| {
b.iter(|| input.incomplete(input_root))
});

c.bench_function("reduce", |b| {
b.iter(|| input.reduce(&incomplete_roots)(input_root))
});
}
}

#[cfg(unix)]
criterion_group! {
name = benches;
config = Criterion::default().with_profiler(PProfProfiler::new(100, Output::Flamegraph(None)));
targets = bench_shardtree
}

#[cfg(not(unix))]
criterion_group!(benches, bench_shardtree);

criterion_main!(benches);
Loading

0 comments on commit 8864a84

Please sign in to comment.