Skip to content

Commit

Permalink
Introduce SparseMerkleTree trait (#245)
Browse files Browse the repository at this point in the history
  • Loading branch information
plafer authored Jan 18, 2024
1 parent 0819bf5 commit 12df4c8
Show file tree
Hide file tree
Showing 13 changed files with 828 additions and 686 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ Cargo.lock

# Generated by cmake
cmake-build-*

# VS Code
.vscode/
14 changes: 11 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,27 @@ harness = false
default = ["std"]
executable = ["dep:clap", "dep:rand_utils", "std"]
serde = ["dep:serde", "serde?/alloc", "winter_math/serde"]
std = ["blake3/std", "dep:cc", "dep:libc", "winter_crypto/std", "winter_math/std", "winter_utils/std"]
std = [
"blake3/std",
"dep:cc",
"dep:libc",
"winter_crypto/std",
"winter_math/std",
"winter_utils/std",
]

[dependencies]
blake3 = { version = "1.5", default-features = false }
clap = { version = "4.4", features = ["derive"], optional = true }
libc = { version = "0.2", default-features = false, optional = true }
libc = { version = "0.2", default-features = false, optional = true }
rand_utils = { version = "0.7", package = "winter-rand-utils", optional = true }
serde = { version = "1.0", features = [ "derive" ], default-features = false, optional = true }
serde = { version = "1.0", features = ["derive"], default-features = false, optional = true }
winter_crypto = { version = "0.7", package = "winter-crypto", default-features = false }
winter_math = { version = "0.7", package = "winter-math", default-features = false }
winter_utils = { version = "0.7", package = "winter-utils", default-features = false }

[dev-dependencies]
seq-macro = { version = "0.3" }
criterion = { version = "0.5", features = ["html_reports"] }
proptest = "1.4"
rand_utils = { version = "0.7", package = "winter-rand-utils" }
Expand Down
87 changes: 43 additions & 44 deletions benches/smt.rs
Original file line number Diff line number Diff line change
@@ -1,67 +1,66 @@
use core::mem::swap;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use miden_crypto::{merkle::SimpleSmt, Felt, Word};
use miden_crypto::{
merkle::{LeafIndex, SimpleSmt},
Felt, Word,
};
use rand_utils::prng_array;
use seq_macro::seq;

fn smt_rpo(c: &mut Criterion) {
// setup trees

let mut seed = [0u8; 32];
let mut trees = vec![];
let leaf = generate_word(&mut seed);

for depth in 14..=20 {
let leaves = ((1 << depth) - 1) as u64;
seq!(DEPTH in 14..=20 {
let leaves = ((1 << DEPTH) - 1) as u64;
for count in [1, leaves / 2, leaves] {
let entries: Vec<_> = (0..count)
.map(|i| {
let word = generate_word(&mut seed);
(i, word)
})
.collect();
let tree = SimpleSmt::with_leaves(depth, entries).unwrap();
trees.push((tree, count));
}
}

let leaf = generate_word(&mut seed);
let mut tree = SimpleSmt::<DEPTH>::with_leaves(entries).unwrap();

// benchmarks
// benchmark 1
let mut insert = c.benchmark_group("smt update_leaf".to_string());
{
let depth = DEPTH;
let key = count >> 2;
insert.bench_with_input(
format!("simple smt(depth:{depth},count:{count})"),
&(key, leaf),
|b, (key, leaf)| {
b.iter(|| {
tree.insert(black_box(LeafIndex::<DEPTH>::new(*key).unwrap()), black_box(*leaf));
});
},
);

let mut insert = c.benchmark_group("smt update_leaf".to_string());
}
insert.finish();

for (tree, count) in trees.iter_mut() {
let depth = tree.depth();
let key = *count >> 2;
insert.bench_with_input(
format!("simple smt(depth:{depth},count:{count})"),
&(key, leaf),
|b, (key, leaf)| {
b.iter(|| {
tree.update_leaf(black_box(*key), black_box(*leaf)).unwrap();
});
},
);
}
// benchmark 2
let mut path = c.benchmark_group("smt get_leaf_path".to_string());
{
let depth = DEPTH;
let key = count >> 2;
path.bench_with_input(
format!("simple smt(depth:{depth},count:{count})"),
&key,
|b, key| {
b.iter(|| {
tree.open(black_box(&LeafIndex::<DEPTH>::new(*key).unwrap()));
});
},
);

insert.finish();

let mut path = c.benchmark_group("smt get_leaf_path".to_string());

for (tree, count) in trees.iter_mut() {
let depth = tree.depth();
let key = *count >> 2;
path.bench_with_input(
format!("simple smt(depth:{depth},count:{count})"),
&key,
|b, key| {
b.iter(|| {
tree.get_leaf_path(black_box(*key)).unwrap();
});
},
);
}

path.finish();
}
path.finish();
}
});
}

criterion_group!(smt_group, smt_rpo);
Expand Down
61 changes: 32 additions & 29 deletions benches/store.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use criterion::{black_box, criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion};
use miden_crypto::merkle::{DefaultMerkleStore as MerkleStore, MerkleTree, NodeIndex, SimpleSmt};
use miden_crypto::merkle::{
DefaultMerkleStore as MerkleStore, LeafIndex, MerkleTree, NodeIndex, SimpleSmt, SMT_MAX_DEPTH,
};
use miden_crypto::Word;
use miden_crypto::{hash::rpo::RpoDigest, Felt};
use rand_utils::{rand_array, rand_value};
Expand Down Expand Up @@ -28,26 +30,26 @@ fn random_index(range: u64, depth: u8) -> NodeIndex {
fn get_empty_leaf_simplesmt(c: &mut Criterion) {
let mut group = c.benchmark_group("get_empty_leaf_simplesmt");

let depth = SimpleSmt::MAX_DEPTH;
const DEPTH: u8 = SMT_MAX_DEPTH;
let size = u64::MAX;

// both SMT and the store are pre-populated with empty hashes, accessing these values is what is
// being benchmarked here, so no values are inserted into the backends
let smt = SimpleSmt::new(depth).unwrap();
let smt = SimpleSmt::<DEPTH>::new().unwrap();
let store = MerkleStore::from(&smt);
let root = smt.root();

group.bench_function(BenchmarkId::new("SimpleSmt", depth), |b| {
group.bench_function(BenchmarkId::new("SimpleSmt", DEPTH), |b| {
b.iter_batched(
|| random_index(size, depth),
|| random_index(size, DEPTH),
|index| black_box(smt.get_node(index)),
BatchSize::SmallInput,
)
});

group.bench_function(BenchmarkId::new("MerkleStore", depth), |b| {
group.bench_function(BenchmarkId::new("MerkleStore", DEPTH), |b| {
b.iter_batched(
|| random_index(size, depth),
|| random_index(size, DEPTH),
|index| black_box(store.get_node(root, index)),
BatchSize::SmallInput,
)
Expand Down Expand Up @@ -104,23 +106,22 @@ fn get_leaf_simplesmt(c: &mut Criterion) {
.enumerate()
.map(|(c, v)| (c.try_into().unwrap(), v.into()))
.collect::<Vec<(u64, Word)>>();
let smt = SimpleSmt::with_leaves(SimpleSmt::MAX_DEPTH, smt_leaves.clone()).unwrap();
let smt = SimpleSmt::<SMT_MAX_DEPTH>::with_leaves(smt_leaves.clone()).unwrap();
let store = MerkleStore::from(&smt);
let depth = smt.depth();
let root = smt.root();
let size_u64 = size as u64;

group.bench_function(BenchmarkId::new("SimpleSmt", size), |b| {
b.iter_batched(
|| random_index(size_u64, depth),
|| random_index(size_u64, SMT_MAX_DEPTH),
|index| black_box(smt.get_node(index)),
BatchSize::SmallInput,
)
});

group.bench_function(BenchmarkId::new("MerkleStore", size), |b| {
b.iter_batched(
|| random_index(size_u64, depth),
|| random_index(size_u64, SMT_MAX_DEPTH),
|index| black_box(store.get_node(root, index)),
BatchSize::SmallInput,
)
Expand All @@ -132,26 +133,26 @@ fn get_leaf_simplesmt(c: &mut Criterion) {
fn get_node_of_empty_simplesmt(c: &mut Criterion) {
let mut group = c.benchmark_group("get_node_of_empty_simplesmt");

let depth = SimpleSmt::MAX_DEPTH;
const DEPTH: u8 = SMT_MAX_DEPTH;

// both SMT and the store are pre-populated with the empty hashes, accessing the internal nodes
// of these values is what is being benchmarked here, so no values are inserted into the
// backends.
let smt = SimpleSmt::new(depth).unwrap();
let smt = SimpleSmt::<DEPTH>::new().unwrap();
let store = MerkleStore::from(&smt);
let root = smt.root();
let half_depth = depth / 2;
let half_depth = DEPTH / 2;
let half_size = 2_u64.pow(half_depth as u32);

group.bench_function(BenchmarkId::new("SimpleSmt", depth), |b| {
group.bench_function(BenchmarkId::new("SimpleSmt", DEPTH), |b| {
b.iter_batched(
|| random_index(half_size, half_depth),
|index| black_box(smt.get_node(index)),
BatchSize::SmallInput,
)
});

group.bench_function(BenchmarkId::new("MerkleStore", depth), |b| {
group.bench_function(BenchmarkId::new("MerkleStore", DEPTH), |b| {
b.iter_batched(
|| random_index(half_size, half_depth),
|index| black_box(store.get_node(root, index)),
Expand Down Expand Up @@ -212,10 +213,10 @@ fn get_node_simplesmt(c: &mut Criterion) {
.enumerate()
.map(|(c, v)| (c.try_into().unwrap(), v.into()))
.collect::<Vec<(u64, Word)>>();
let smt = SimpleSmt::with_leaves(SimpleSmt::MAX_DEPTH, smt_leaves.clone()).unwrap();
let smt = SimpleSmt::<SMT_MAX_DEPTH>::with_leaves(smt_leaves.clone()).unwrap();
let store = MerkleStore::from(&smt);
let root = smt.root();
let half_depth = smt.depth() / 2;
let half_depth = SMT_MAX_DEPTH / 2;
let half_size = 2_u64.pow(half_depth as u32);

group.bench_function(BenchmarkId::new("SimpleSmt", size), |b| {
Expand Down Expand Up @@ -286,23 +287,24 @@ fn get_leaf_path_simplesmt(c: &mut Criterion) {
.enumerate()
.map(|(c, v)| (c.try_into().unwrap(), v.into()))
.collect::<Vec<(u64, Word)>>();
let smt = SimpleSmt::with_leaves(SimpleSmt::MAX_DEPTH, smt_leaves.clone()).unwrap();
let smt = SimpleSmt::<SMT_MAX_DEPTH>::with_leaves(smt_leaves.clone()).unwrap();
let store = MerkleStore::from(&smt);
let depth = smt.depth();
let root = smt.root();
let size_u64 = size as u64;

group.bench_function(BenchmarkId::new("SimpleSmt", size), |b| {
b.iter_batched(
|| random_index(size_u64, depth),
|index| black_box(smt.get_path(index)),
|| random_index(size_u64, SMT_MAX_DEPTH),
|index| {
black_box(smt.open(&LeafIndex::<SMT_MAX_DEPTH>::new(index.value()).unwrap()))
},
BatchSize::SmallInput,
)
});

group.bench_function(BenchmarkId::new("MerkleStore", size), |b| {
b.iter_batched(
|| random_index(size_u64, depth),
|| random_index(size_u64, SMT_MAX_DEPTH),
|index| black_box(store.get_path(root, index)),
BatchSize::SmallInput,
)
Expand Down Expand Up @@ -352,7 +354,7 @@ fn new(c: &mut Criterion) {
.map(|(c, v)| (c.try_into().unwrap(), v.into()))
.collect::<Vec<(u64, Word)>>()
},
|l| black_box(SimpleSmt::with_leaves(SimpleSmt::MAX_DEPTH, l)),
|l| black_box(SimpleSmt::<SMT_MAX_DEPTH>::with_leaves(l)),
BatchSize::SmallInput,
)
});
Expand All @@ -367,7 +369,7 @@ fn new(c: &mut Criterion) {
.collect::<Vec<(u64, Word)>>()
},
|l| {
let smt = SimpleSmt::with_leaves(SimpleSmt::MAX_DEPTH, l).unwrap();
let smt = SimpleSmt::<SMT_MAX_DEPTH>::with_leaves(l).unwrap();
black_box(MerkleStore::from(&smt));
},
BatchSize::SmallInput,
Expand Down Expand Up @@ -433,24 +435,25 @@ fn update_leaf_simplesmt(c: &mut Criterion) {
.enumerate()
.map(|(c, v)| (c.try_into().unwrap(), v.into()))
.collect::<Vec<(u64, Word)>>();
let mut smt = SimpleSmt::with_leaves(SimpleSmt::MAX_DEPTH, smt_leaves.clone()).unwrap();
let mut smt = SimpleSmt::<SMT_MAX_DEPTH>::with_leaves(smt_leaves.clone()).unwrap();
let mut store = MerkleStore::from(&smt);
let depth = smt.depth();
let root = smt.root();
let size_u64 = size as u64;

group.bench_function(BenchmarkId::new("SimpleSMT", size), |b| {
b.iter_batched(
|| (rand_value::<u64>() % size_u64, random_word()),
|(index, value)| black_box(smt.update_leaf(index, value)),
|(index, value)| {
black_box(smt.insert(LeafIndex::<SMT_MAX_DEPTH>::new(index).unwrap(), value))
},
BatchSize::SmallInput,
)
});

let mut store_root = root;
group.bench_function(BenchmarkId::new("MerkleStore", size), |b| {
b.iter_batched(
|| (random_index(size_u64, depth), random_word()),
|| (random_index(size_u64, SMT_MAX_DEPTH), random_word()),
|(index, value)| {
// The MerkleTree automatically updates its internal root, the Store maintains
// the old root and adds the new one. Here we update the root to have a fair
Expand Down
Loading

0 comments on commit 12df4c8

Please sign in to comment.