chore: minor code cleanup

This commit is contained in:
Bobbin Threadbare 2025-02-06 17:52:32 -08:00
parent 1b77fa8039
commit 58d173ef7b
No known key found for this signature in database
GPG key ID: 289C444AD87BC941
3 changed files with 228 additions and 171 deletions

89
Cargo.lock generated
View file

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
@ -142,9 +142,9 @@ dependencies = [
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.16.0" version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
@ -160,9 +160,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.10" version = "1.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" checksum = "755717a7de9ec452bf7f3f1a3099085deabd7f2962b861dae91ecd7a365903d2"
dependencies = [ dependencies = [
"jobserver", "jobserver",
"libc", "libc",
@ -204,9 +204,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.27" version = "4.5.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" checksum = "3e77c3243bd94243c03672cb5154667347c457ca271254724f9f393aee1c05ff"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -226,9 +226,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.24" version = "4.5.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@ -256,9 +256,9 @@ checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.16" version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -409,10 +409,22 @@ dependencies = [
"cfg-if", "cfg-if",
"js-sys", "js-sys",
"libc", "libc",
"wasi", "wasi 0.11.0+wasi-snapshot-preview1",
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "getrandom"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
dependencies = [
"cfg-if",
"libc",
"wasi 0.13.3+wasi-0.2.2",
"windows-targets",
]
[[package]] [[package]]
name = "glob" name = "glob"
version = "0.3.2" version = "0.3.2"
@ -438,6 +450,7 @@ dependencies = [
"allocator-api2", "allocator-api2",
"equivalent", "equivalent",
"foldhash", "foldhash",
"rayon",
"serde", "serde",
] ]
@ -564,7 +577,7 @@ dependencies = [
"cc", "cc",
"clap", "clap",
"criterion", "criterion",
"getrandom", "getrandom 0.2.15",
"glob", "glob",
"hashbrown", "hashbrown",
"hex", "hex",
@ -661,9 +674,9 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.20.2" version = "1.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
[[package]] [[package]]
name = "oorandom" name = "oorandom"
@ -779,7 +792,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [ dependencies = [
"getrandom", "getrandom 0.2.15",
] ]
[[package]] [[package]]
@ -873,9 +886,9 @@ dependencies = [
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.18" version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
[[package]] [[package]]
name = "same-file" name = "same-file"
@ -914,9 +927,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.137" version = "1.0.138"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@ -948,9 +961,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.96" version = "2.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -959,13 +972,13 @@ dependencies = [
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.15.0" version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand",
"getrandom", "getrandom 0.3.1",
"once_cell", "once_cell",
"rustix", "rustix",
"windows-sys", "windows-sys",
@ -1015,9 +1028,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.14" version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
@ -1033,9 +1046,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]] [[package]]
name = "wait-timeout" name = "wait-timeout"
version = "0.2.0" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -1056,6 +1069,15 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasi"
version = "0.13.3+wasi-0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
dependencies = [
"wit-bindgen-rt",
]
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.100" version = "0.2.100"
@ -1244,6 +1266,15 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1507ef312ea5569d54c2c7446a18b82143eb2a2e21f5c3ec7cfbe8200c03bd7c" checksum = "1507ef312ea5569d54c2c7446a18b82143eb2a2e21f5c3ec7cfbe8200c03bd7c"
[[package]]
name = "wit-bindgen-rt"
version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
dependencies = [
"bitflags",
]
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.7.35" version = "0.7.35"

View file

@ -14,6 +14,9 @@ mod tests;
type MutatedSubtreeLeaves = Vec<Vec<SubtreeLeaf>>; type MutatedSubtreeLeaves = Vec<Vec<SubtreeLeaf>>;
// CONCURRENT IMPLEMENTATIONS
// ================================================================================================
impl Smt { impl Smt {
/// Parallel implementation of [`Smt::with_entries()`]. /// Parallel implementation of [`Smt::with_entries()`].
/// ///
@ -115,6 +118,119 @@ impl Smt {
} }
} }
// SUBTREE MUTATION
// --------------------------------------------------------------------------------------------
/// Computes the node mutations and the root of a subtree
fn build_subtree_mutations(
&self,
mut leaves: Vec<SubtreeLeaf>,
tree_depth: u8,
bottom_depth: u8,
) -> (NodeMutations, SubtreeLeaf)
where
Self: Sized,
{
debug_assert!(bottom_depth <= tree_depth);
debug_assert!(Integer::is_multiple_of(&bottom_depth, &SUBTREE_DEPTH));
debug_assert!(leaves.len() <= usize::pow(2, SUBTREE_DEPTH as u32));
let subtree_root_depth = bottom_depth - SUBTREE_DEPTH;
let mut node_mutations: NodeMutations = Default::default();
let mut next_leaves: Vec<SubtreeLeaf> = Vec::with_capacity(leaves.len() / 2);
for current_depth in (subtree_root_depth..bottom_depth).rev() {
debug_assert!(current_depth <= bottom_depth);
let next_depth = current_depth + 1;
let mut iter = leaves.drain(..).peekable();
while let Some(first_leaf) = iter.next() {
// This constructs a valid index because next_depth will never exceed the depth of
// the tree.
let parent_index = NodeIndex::new_unchecked(next_depth, first_leaf.col).parent();
let parent_node = self.get_inner_node(parent_index);
let combined_node = fetch_sibling_pair(&mut iter, first_leaf, parent_node);
let combined_hash = combined_node.hash();
let &empty_hash = EmptySubtreeRoots::entry(tree_depth, current_depth);
// Add the parent node even if it is empty for proper upward updates
next_leaves.push(SubtreeLeaf {
col: parent_index.value(),
hash: combined_hash,
});
node_mutations.insert(
parent_index,
if combined_hash != empty_hash {
NodeMutation::Addition(combined_node)
} else {
NodeMutation::Removal
},
);
}
drop(iter);
leaves = mem::take(&mut next_leaves);
}
debug_assert_eq!(leaves.len(), 1);
let root_leaf = leaves.pop().unwrap();
(node_mutations, root_leaf)
}
// SUBTREE CONSTRUCTION
// --------------------------------------------------------------------------------------------
/// Computes the raw parts for a new sparse Merkle tree from a set of key-value pairs.
///
/// `entries` need not be sorted. This function will sort them.
fn build_subtrees(mut entries: Vec<(RpoDigest, Word)>) -> (InnerNodes, Leaves) {
entries.sort_by_key(|item| {
let index = Self::key_to_leaf_index(&item.0);
index.value()
});
Self::build_subtrees_from_sorted_entries(entries)
}
/// Computes the raw parts for a new sparse Merkle tree from a set of key-value pairs.
///
/// This function is mostly an implementation detail of
/// [`Smt::with_entries_concurrent()`].
fn build_subtrees_from_sorted_entries(entries: Vec<(RpoDigest, Word)>) -> (InnerNodes, Leaves) {
use rayon::prelude::*;
let mut accumulated_nodes: InnerNodes = Default::default();
let PairComputations {
leaves: mut leaf_subtrees,
nodes: initial_leaves,
} = Self::sorted_pairs_to_leaves(entries);
for current_depth in (SUBTREE_DEPTH..=SMT_DEPTH).step_by(SUBTREE_DEPTH as usize).rev() {
let (nodes, mut subtree_roots): (Vec<UnorderedMap<_, _>>, Vec<SubtreeLeaf>) =
leaf_subtrees
.into_par_iter()
.map(|subtree| {
debug_assert!(subtree.is_sorted());
debug_assert!(!subtree.is_empty());
let (nodes, subtree_root) =
build_subtree(subtree, SMT_DEPTH, current_depth);
(nodes, subtree_root)
})
.unzip();
leaf_subtrees = SubtreeLeavesIter::from_leaves(&mut subtree_roots).collect();
accumulated_nodes.extend(nodes.into_iter().flatten());
debug_assert!(!leaf_subtrees.is_empty());
}
(accumulated_nodes, initial_leaves)
}
// LEAF NODE CONSTRUCTION
// --------------------------------------------------------------------------------------------
/// Performs the initial transforms for constructing a [`SparseMerkleTree`] by composing /// Performs the initial transforms for constructing a [`SparseMerkleTree`] by composing
/// subtrees. In other words, this function takes the key-value inputs to the tree, and produces /// subtrees. In other words, this function takes the key-value inputs to the tree, and produces
/// the inputs to feed into [`build_subtree()`]. /// the inputs to feed into [`build_subtree()`].
@ -161,99 +277,6 @@ impl Smt {
(accumulator.leaves, new_pairs) (accumulator.leaves, new_pairs)
} }
/// Computes the node mutations and the root of a subtree
fn build_subtree_mutations(
&self,
mut leaves: Vec<SubtreeLeaf>,
tree_depth: u8,
bottom_depth: u8,
) -> (NodeMutations, SubtreeLeaf)
where
Self: Sized,
{
debug_assert!(bottom_depth <= tree_depth);
debug_assert!(Integer::is_multiple_of(&bottom_depth, &SUBTREE_DEPTH));
debug_assert!(leaves.len() <= usize::pow(2, SUBTREE_DEPTH as u32));
let subtree_root_depth = bottom_depth - SUBTREE_DEPTH;
let mut node_mutations: NodeMutations = Default::default();
let mut next_leaves: Vec<SubtreeLeaf> = Vec::with_capacity(leaves.len() / 2);
for current_depth in (subtree_root_depth..bottom_depth).rev() {
debug_assert!(current_depth <= bottom_depth);
let next_depth = current_depth + 1;
let mut iter = leaves.drain(..).peekable();
while let Some(first_leaf) = iter.next() {
// This constructs a valid index because next_depth will never exceed the depth of
// the tree.
let parent_index = NodeIndex::new_unchecked(next_depth, first_leaf.col).parent();
let parent_node = self.get_inner_node(parent_index);
let combined_node = Self::fetch_sibling_pair(&mut iter, first_leaf, parent_node);
let combined_hash = combined_node.hash();
let &empty_hash = EmptySubtreeRoots::entry(tree_depth, current_depth);
// Add the parent node even if it is empty for proper upward updates
next_leaves.push(SubtreeLeaf {
col: parent_index.value(),
hash: combined_hash,
});
node_mutations.insert(
parent_index,
if combined_hash != empty_hash {
NodeMutation::Addition(combined_node)
} else {
NodeMutation::Removal
},
);
}
drop(iter);
leaves = mem::take(&mut next_leaves);
}
debug_assert_eq!(leaves.len(), 1);
let root_leaf = leaves.pop().unwrap();
(node_mutations, root_leaf)
}
/// Constructs an `InnerNode` representing the sibling pair of which `first_leaf` is a part:
/// - If `first_leaf` is a right child, the left child is copied from the `parent_node`.
/// - If `first_leaf` is a left child, the right child is taken from `iter` if it was also
/// mutated or copied from the `parent_node`.
///
/// Returns the `InnerNode` containing the hashes of the sibling pair.
fn fetch_sibling_pair(
iter: &mut core::iter::Peekable<alloc::vec::Drain<SubtreeLeaf>>,
first_leaf: SubtreeLeaf,
parent_node: InnerNode,
) -> InnerNode {
let is_right_node = first_leaf.col.is_odd();
if is_right_node {
let left_leaf = SubtreeLeaf {
col: first_leaf.col - 1,
hash: parent_node.left,
};
InnerNode {
left: left_leaf.hash,
right: first_leaf.hash,
}
} else {
let right_col = first_leaf.col + 1;
let right_leaf = match iter.peek().copied() {
Some(SubtreeLeaf { col, .. }) if col == right_col => iter.next().unwrap(),
_ => SubtreeLeaf { col: right_col, hash: parent_node.right },
};
InnerNode {
left: first_leaf.hash,
right: right_leaf.hash,
}
}
}
/// Processes sorted key-value pairs to compute leaves for a subtree. /// Processes sorted key-value pairs to compute leaves for a subtree.
/// ///
/// This function groups key-value pairs by their corresponding column index and processes each /// This function groups key-value pairs by their corresponding column index and processes each
@ -340,52 +363,6 @@ impl Smt {
accumulator.leaves = SubtreeLeavesIter::from_leaves(&mut accumulated_leaves).collect(); accumulator.leaves = SubtreeLeavesIter::from_leaves(&mut accumulated_leaves).collect();
accumulator accumulator
} }
/// Computes the raw parts for a new sparse Merkle tree from a set of key-value pairs.
///
/// `entries` need not be sorted. This function will sort them.
fn build_subtrees(mut entries: Vec<(RpoDigest, Word)>) -> (InnerNodes, Leaves) {
entries.sort_by_key(|item| {
let index = Self::key_to_leaf_index(&item.0);
index.value()
});
Self::build_subtrees_from_sorted_entries(entries)
}
/// Computes the raw parts for a new sparse Merkle tree from a set of key-value pairs.
///
/// This function is mostly an implementation detail of
/// [`Smt::with_entries_concurrent()`].
fn build_subtrees_from_sorted_entries(entries: Vec<(RpoDigest, Word)>) -> (InnerNodes, Leaves) {
use rayon::prelude::*;
let mut accumulated_nodes: InnerNodes = Default::default();
let PairComputations {
leaves: mut leaf_subtrees,
nodes: initial_leaves,
} = Self::sorted_pairs_to_leaves(entries);
for current_depth in (SUBTREE_DEPTH..=SMT_DEPTH).step_by(SUBTREE_DEPTH as usize).rev() {
let (nodes, mut subtree_roots): (Vec<UnorderedMap<_, _>>, Vec<SubtreeLeaf>) =
leaf_subtrees
.into_par_iter()
.map(|subtree| {
debug_assert!(subtree.is_sorted());
debug_assert!(!subtree.is_empty());
let (nodes, subtree_root) =
build_subtree(subtree, SMT_DEPTH, current_depth);
(nodes, subtree_root)
})
.unzip();
leaf_subtrees = SubtreeLeavesIter::from_leaves(&mut subtree_roots).collect();
accumulated_nodes.extend(nodes.into_iter().flatten());
debug_assert!(!leaf_subtrees.is_empty());
}
(accumulated_nodes, initial_leaves)
}
} }
// SUBTREES // SUBTREES
@ -399,7 +376,7 @@ const COLS_PER_SUBTREE: u64 = u64::pow(2, SUBTREE_DEPTH as u32);
/// Helper struct for organizing the data we care about when computing Merkle subtrees. /// Helper struct for organizing the data we care about when computing Merkle subtrees.
/// ///
/// Note that these represet "conceptual" leaves of some subtree, not necessarily /// Note that these represent "conceptual" leaves of some subtree, not necessarily
/// the leaf type for the sparse Merkle tree. /// the leaf type for the sparse Merkle tree.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct SubtreeLeaf { pub struct SubtreeLeaf {
@ -432,6 +409,7 @@ impl<K, L> Default for PairComputations<K, L> {
pub(crate) struct SubtreeLeavesIter<'s> { pub(crate) struct SubtreeLeavesIter<'s> {
leaves: core::iter::Peekable<alloc::vec::Drain<'s, SubtreeLeaf>>, leaves: core::iter::Peekable<alloc::vec::Drain<'s, SubtreeLeaf>>,
} }
impl<'s> SubtreeLeavesIter<'s> { impl<'s> SubtreeLeavesIter<'s> {
fn from_leaves(leaves: &'s mut Vec<SubtreeLeaf>) -> Self { fn from_leaves(leaves: &'s mut Vec<SubtreeLeaf>) -> Self {
// TODO: determine if there is any notable performance difference between taking a Vec, // TODO: determine if there is any notable performance difference between taking a Vec,
@ -441,6 +419,7 @@ impl<'s> SubtreeLeavesIter<'s> {
Self { leaves: leaves.drain(..).peekable() } Self { leaves: leaves.drain(..).peekable() }
} }
} }
impl Iterator for SubtreeLeavesIter<'_> { impl Iterator for SubtreeLeavesIter<'_> {
type Item = Vec<SubtreeLeaf>; type Item = Vec<SubtreeLeaf>;
@ -494,8 +473,7 @@ impl Iterator for SubtreeLeavesIter<'_> {
/// more entries than can fit in a depth-8 subtree, if `leaves` contains leaves belonging to /// more entries than can fit in a depth-8 subtree, if `leaves` contains leaves belonging to
/// different depth-8 subtrees, if `bottom_depth` is lower in the tree than the specified /// different depth-8 subtrees, if `bottom_depth` is lower in the tree than the specified
/// maximum depth (`DEPTH`), or if `leaves` is not sorted. /// maximum depth (`DEPTH`), or if `leaves` is not sorted.
#[cfg(feature = "concurrent")] fn build_subtree(
pub(crate) fn build_subtree(
mut leaves: Vec<SubtreeLeaf>, mut leaves: Vec<SubtreeLeaf>,
tree_depth: u8, tree_depth: u8,
bottom_depth: u8, bottom_depth: u8,
@ -570,6 +548,41 @@ pub(crate) fn build_subtree(
(inner_nodes, root) (inner_nodes, root)
} }
/// Constructs an `InnerNode` representing the sibling pair of which `first_leaf` is a part:
/// - If `first_leaf` is a right child, the left child is copied from the `parent_node`.
/// - If `first_leaf` is a left child, the right child is taken from `iter` if it was also mutated
/// or copied from the `parent_node`.
///
/// Returns the `InnerNode` containing the hashes of the sibling pair.
fn fetch_sibling_pair(
iter: &mut core::iter::Peekable<alloc::vec::Drain<SubtreeLeaf>>,
first_leaf: SubtreeLeaf,
parent_node: InnerNode,
) -> InnerNode {
let is_right_node = first_leaf.col.is_odd();
if is_right_node {
let left_leaf = SubtreeLeaf {
col: first_leaf.col - 1,
hash: parent_node.left,
};
InnerNode {
left: left_leaf.hash,
right: first_leaf.hash,
}
} else {
let right_col = first_leaf.col + 1;
let right_leaf = match iter.peek().copied() {
Some(SubtreeLeaf { col, .. }) if col == right_col => iter.next().unwrap(),
_ => SubtreeLeaf { col: right_col, hash: parent_node.right },
};
InnerNode {
left: first_leaf.hash,
right: right_leaf.hash,
}
}
}
#[cfg(feature = "internal")] #[cfg(feature = "internal")]
pub fn build_subtree_for_bench( pub fn build_subtree_for_bench(
leaves: Vec<SubtreeLeaf>, leaves: Vec<SubtreeLeaf>,

View file

@ -34,6 +34,7 @@ fn test_sorted_pairs_to_leaves() {
// Subtree 2. Another normal leaf. // Subtree 2. Another normal leaf.
(RpoDigest::new([ONE, ONE, ONE, Felt::new(1024)]), [ONE; 4]), (RpoDigest::new([ONE, ONE, ONE, Felt::new(1024)]), [ONE; 4]),
]; ];
let control = Smt::with_entries_sequential(entries.clone()).unwrap(); let control = Smt::with_entries_sequential(entries.clone()).unwrap();
let control_leaves: Vec<SmtLeaf> = { let control_leaves: Vec<SmtLeaf> = {
let mut entries_iter = entries.iter().cloned(); let mut entries_iter = entries.iter().cloned();
@ -52,6 +53,7 @@ fn test_sorted_pairs_to_leaves() {
assert_eq!(entries_iter.next(), None); assert_eq!(entries_iter.next(), None);
control_leaves control_leaves
}; };
let control_subtree_leaves: Vec<Vec<SubtreeLeaf>> = { let control_subtree_leaves: Vec<Vec<SubtreeLeaf>> = {
let mut control_leaves_iter = control_leaves.iter(); let mut control_leaves_iter = control_leaves.iter();
let mut next_leaf = || control_leaves_iter.next().unwrap(); let mut next_leaf = || control_leaves_iter.next().unwrap();
@ -68,6 +70,7 @@ fn test_sorted_pairs_to_leaves() {
assert_eq!(control_leaves_iter.next(), None); assert_eq!(control_leaves_iter.next(), None);
control_subtree_leaves control_subtree_leaves
}; };
let subtrees: PairComputations<u64, SmtLeaf> = Smt::sorted_pairs_to_leaves(entries); let subtrees: PairComputations<u64, SmtLeaf> = Smt::sorted_pairs_to_leaves(entries);
// This will check that the hashes, columns, and subtree assignments all match. // This will check that the hashes, columns, and subtree assignments all match.
assert_eq!(subtrees.leaves, control_subtree_leaves); assert_eq!(subtrees.leaves, control_subtree_leaves);
@ -80,6 +83,7 @@ fn test_sorted_pairs_to_leaves() {
.leaves() .leaves()
.map(|(index, value)| (index.index.value(), value.clone())) .map(|(index, value)| (index.index.value(), value.clone()))
.collect(); .collect();
for (column, test_leaf) in subtrees.nodes { for (column, test_leaf) in subtrees.nodes {
if test_leaf.is_empty() { if test_leaf.is_empty() {
continue; continue;
@ -90,6 +94,7 @@ fn test_sorted_pairs_to_leaves() {
assert_eq!(control_leaf, &test_leaf); assert_eq!(control_leaf, &test_leaf);
} }
} }
// Helper for the below tests. // Helper for the below tests.
fn generate_entries(pair_count: u64) -> Vec<(RpoDigest, Word)> { fn generate_entries(pair_count: u64) -> Vec<(RpoDigest, Word)> {
(0..pair_count) (0..pair_count)
@ -101,6 +106,7 @@ fn generate_entries(pair_count: u64) -> Vec<(RpoDigest, Word)> {
}) })
.collect() .collect()
} }
fn generate_updates(entries: Vec<(RpoDigest, Word)>, updates: usize) -> Vec<(RpoDigest, Word)> { fn generate_updates(entries: Vec<(RpoDigest, Word)>, updates: usize) -> Vec<(RpoDigest, Word)> {
const REMOVAL_PROBABILITY: f64 = 0.2; const REMOVAL_PROBABILITY: f64 = 0.2;
let mut rng = thread_rng(); let mut rng = thread_rng();
@ -125,6 +131,7 @@ fn generate_updates(entries: Vec<(RpoDigest, Word)>, updates: usize) -> Vec<(Rpo
sorted_entries.sort_by_key(|(key, _)| Smt::key_to_leaf_index(key).value()); sorted_entries.sort_by_key(|(key, _)| Smt::key_to_leaf_index(key).value());
sorted_entries sorted_entries
} }
#[test] #[test]
fn test_single_subtree() { fn test_single_subtree() {
// A single subtree's worth of leaves. // A single subtree's worth of leaves.
@ -154,6 +161,7 @@ fn test_single_subtree() {
"Subtree-computed root at index {control_root_index:?} does not match control" "Subtree-computed root at index {control_root_index:?} does not match control"
); );
} }
// Test that not just can we compute a subtree correctly, but we can feed the results of one // Test that not just can we compute a subtree correctly, but we can feed the results of one
// subtree into computing another. In other words, test that `build_subtree()` is correctly // subtree into computing another. In other words, test that `build_subtree()` is correctly
// composable. // composable.
@ -201,6 +209,7 @@ fn test_two_subtrees() {
let control_root = control.get_inner_node(index).hash(); let control_root = control.get_inner_node(index).hash();
assert_eq!(control_root, root_leaf.hash, "Root mismatch"); assert_eq!(control_root, root_leaf.hash, "Root mismatch");
} }
#[test] #[test]
fn test_singlethreaded_subtrees() { fn test_singlethreaded_subtrees() {
const PAIR_COUNT: u64 = COLS_PER_SUBTREE * 64; const PAIR_COUNT: u64 = COLS_PER_SUBTREE * 64;
@ -282,6 +291,7 @@ fn test_singlethreaded_subtrees() {
// And of course the root we got from each place should match. // And of course the root we got from each place should match.
assert_eq!(control.root(), root_leaf.hash); assert_eq!(control.root(), root_leaf.hash);
} }
/// The parallel version of `test_singlethreaded_subtree()`. /// The parallel version of `test_singlethreaded_subtree()`.
#[test] #[test]
fn test_multithreaded_subtrees() { fn test_multithreaded_subtrees() {
@ -361,6 +371,7 @@ fn test_multithreaded_subtrees() {
// And of course the root we got from each place should match. // And of course the root we got from each place should match.
assert_eq!(control.root(), root_leaf.hash); assert_eq!(control.root(), root_leaf.hash);
} }
#[test] #[test]
fn test_with_entries_concurrent() { fn test_with_entries_concurrent() {
const PAIR_COUNT: u64 = COLS_PER_SUBTREE * 64; const PAIR_COUNT: u64 = COLS_PER_SUBTREE * 64;
@ -370,6 +381,7 @@ fn test_with_entries_concurrent() {
assert_eq!(smt.root(), control.root()); assert_eq!(smt.root(), control.root());
assert_eq!(smt, control); assert_eq!(smt, control);
} }
/// Concurrent mutations /// Concurrent mutations
#[test] #[test]
fn test_singlethreaded_subtree_mutations() { fn test_singlethreaded_subtree_mutations() {
@ -431,6 +443,7 @@ fn test_singlethreaded_subtree_mutations() {
assert_eq!(test_value, &value); assert_eq!(test_value, &value);
} }
} }
#[test] #[test]
fn test_compute_mutations_parallel() { fn test_compute_mutations_parallel() {
const PAIR_COUNT: u64 = COLS_PER_SUBTREE * 64; const PAIR_COUNT: u64 = COLS_PER_SUBTREE * 64;