diff --git a/miden-crypto/src/merkle/smt/forest/mod.rs b/miden-crypto/src/merkle/smt/forest/mod.rs index b80efe903..f9b38ce6e 100644 --- a/miden-crypto/src/merkle/smt/forest/mod.rs +++ b/miden-crypto/src/merkle/smt/forest/mod.rs @@ -126,7 +126,7 @@ impl SmtForest { /// Inserts the specified key-value pair into an SMT with the specified root. This will also /// add a new root to the forest. Returns the new root. /// - /// Returns an error if an SMT with the specified root is not in the forest, these is not + /// Returns an error if an SMT with the specified root is not in the forest, there is not /// enough data in the forest to perform the insert, or if the insert would create a leaf /// with too many entries. pub fn insert(&mut self, root: Word, key: Word, value: Word) -> Result { @@ -136,7 +136,7 @@ impl SmtForest { /// Inserts the specified key-value pairs into an SMT with the specified root. This will also /// add a single new root to the forest for the entire batch of inserts. Returns the new root. /// - /// Returns an error if an SMT with the specified root is not in the forest, these is not + /// Returns an error if an SMT with the specified root is not in the forest, there is not /// enough data in the forest to perform the insert, or if the insert would create a leaf /// with too many entries. pub fn batch_insert( diff --git a/miden-crypto/src/merkle/smt/large_forest/error/mod.rs b/miden-crypto/src/merkle/smt/large_forest/error/mod.rs index e959609a7..75c7d73b0 100644 --- a/miden-crypto/src/merkle/smt/large_forest/error/mod.rs +++ b/miden-crypto/src/merkle/smt/large_forest/error/mod.rs @@ -7,22 +7,58 @@ use thiserror::Error; use crate::merkle::{ MerkleError, - smt::large_forest::{history::error::HistoryError, storage}, + smt::large_forest::{error::prefix::PrefixError, history::error::HistoryError, storage}, }; +// LARGE SMT FOREST ERROR +// ================================================================================================ + /// The type of errors returned by operations on the large SMT forest. #[derive(Debug, Error)] pub enum LargeSmtForestError { + /// Errors in the history subsystem of the forest. #[error(transparent)] HistoryError(#[from] HistoryError), + /// Errors with the merkle tree operations of the forest. #[error(transparent)] MerkleError(#[from] MerkleError), + /// Errors with the storage backend of the forest. #[error(transparent)] StorageError(#[from] storage::StorageError), + + /// Errors with the in-memory tree prefixes in the forest. + #[error(transparent)] + PrefixError(#[from] PrefixError), } /// The result type for use within the large SMT forest portion of the library. #[allow(dead_code)] // Temporary pub type Result = core::result::Result; + +pub mod prefix { + use thiserror::Error; + + use crate::{Word, merkle::smt::large_forest::utils::LinearIndex}; + + #[derive(Debug, Eq, Error, PartialEq)] + pub enum PrefixError { + /// Raised if an indexing operation would be out of bounds. + #[error("Index {0} was out of bounds in a prefix with {1} levels")] + IndexOutOfBounds(LinearIndex, u8), + + /// Raised if the forest cannot restore correctly from the saved restoration data. + #[error("Restoration data for tree with root {0} produced root {1}")] + InvalidRestoration(Word, Word), + + /// Raised if the number of leaves in the restoration data provided to the prefix is + /// incorrect for the depth of the prefix. + #[error("Was given {0} leaves but expected {1}")] + WrongLeafCount(u64, u64), + } + + /// The result type for use within the prefix portion of the library. + #[allow(dead_code)] // Temporary + pub type Result = core::result::Result; +} diff --git a/miden-crypto/src/merkle/smt/large_forest/mod.rs b/miden-crypto/src/merkle/smt/large_forest/mod.rs index 5734ce865..9a49dd199 100644 --- a/miden-crypto/src/merkle/smt/large_forest/mod.rs +++ b/miden-crypto/src/merkle/smt/large_forest/mod.rs @@ -37,9 +37,56 @@ mod error; pub mod history; +mod prefix; pub mod storage; pub mod utils; -pub use error::LargeSmtForestError; +pub use error::{LargeSmtForestError, Result}; pub use storage::{Storage, StorageError, StoredTreeHandle}; pub use utils::SubtreeLevels; + +use crate::{Map, Word, merkle::smt::large_forest::prefix::InMemoryPrefix}; + +// SPARSE MERKLE TREE FOREST +// ================================================================================================ + +/// A high-performance forest of sparse merkle trees with pluggable storage. +/// +/// # Performance +/// +/// The performance characteristics of this forest +#[allow(dead_code)] // Temporary, while the tree gets built. +#[derive(Debug)] +pub struct LargeSmtForest { + /// The underlying data storage for the portion of the tree that is not guaranteed to be in + /// memory. It **must not be exposed** to any client of this struct's API to ensure + /// correctness. + storage: S, + + /// The number of levels of each tree that are kept in memory by the forest. + in_memory_depth: SubtreeLevels, + + /// The container for the in-memory prefixes of each tree stored in the forest, identified by + /// their current root. + prefixes: Map, +} + +impl LargeSmtForest { + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + + /// Constructs a new forest backed by the provided `storage`. + /// + /// The constructor will treat whatever state is contained within the provided `storage` as the + /// starting state for the forest. This means that if you pass a newly-initialized storage the + /// forest will start in an empty state, while if you pass a `storage` that already contains + /// some data (e.g. loaded from disk), then the forest will start in _that_ form instead. + /// + /// # Errors + /// + /// - [`LargeSmtForestError::StorageError`] if the forest cannot be started up correctly from + /// storage. + pub fn new(_storage: S) -> Result { + todo!() + } +} diff --git a/miden-crypto/src/merkle/smt/large_forest/prefix.rs b/miden-crypto/src/merkle/smt/large_forest/prefix.rs new file mode 100644 index 000000000..04b963c4d --- /dev/null +++ b/miden-crypto/src/merkle/smt/large_forest/prefix.rs @@ -0,0 +1,484 @@ +//! This module contains the type definition and methods for working with the in-memory prefix of +//! each tree in the forest. + +use alloc::vec::Vec; +use core::ops::{Index, IndexMut}; + +use super::error::prefix::{PrefixError, Result}; +use crate::{ + EMPTY_WORD, Word, + hash::rpo::Rpo256, + merkle::{ + NodeIndex, + smt::{ + SubtreeLevels, + large_forest::utils::{LinearIndex, node_index_to_linear}, + }, + }, +}; + +// IN MEMORY PREFIX +// ================================================================================================ + +/// An in-memory tree prefix that stores all nodes for the first `n` levels of the tree in +/// fully-materialised form. +#[allow(dead_code)] // Temporarily +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct InMemoryPrefix { + /// The number of levels that are stored in the prefix, including the root level. + /// + /// See the documentation on [`SubtreeLevels`] for more information on the invariants of + /// this type. + pub num_levels: SubtreeLevels, + + /// The storage for the nodes in the in-memory prefix, which will have space to store + /// `2.pow(num_levels)` nodes without reallocation. + /// + /// It is laid out such that it is indexable by [`LinearIndex`]. + pub nodes: Vec, +} + +#[allow(dead_code)] // Temporarily +impl InMemoryPrefix { + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + + /// Constructs a new prefix with the specified `num_levels`, using `leaf_data` as the starting + /// value for the leaves of the prefix, and with `expected_root` as the expected root. + /// + /// # Errors + /// + /// - [`PrefixError::InvalidRestoration`] if the provided `leaf_data` does not build into a tree + /// with a root matching `expected_root`. + /// - [`PrefixError::WrongLeafCount`] if the provided `leaf_data` contains the wrong number of + /// leaves for a prefix with `num_levels` levels. + pub fn new( + num_levels: SubtreeLevels, + leaf_data: Vec, + expected_root: Word, + ) -> Result { + // We also want to fail if we are provided with the wrong number of leaves to build the + // in-memory tree. + let expected_leaf_count = 2u64.pow(num_levels.non_root_levels() as u32); + let actual_leaf_count = leaf_data.len() as u64; + if actual_leaf_count != expected_leaf_count { + return Err(PrefixError::WrongLeafCount(actual_leaf_count, expected_leaf_count)); + } + + // Finally, we want to fail if the computed root is incorrect. + let nodes = Self::build_tree_from_leaves(num_levels, leaf_data); + if nodes[1] != expected_root { + return Err(PrefixError::InvalidRestoration(expected_root, nodes[1])); + } + + // If all of that succeeds we have a valid in-memory tree. + Ok(Self { num_levels, nodes }) + } + + /// Builds a fully-materialized merkle tree from the provided `leaves`. + fn build_tree_from_leaves(num_levels: SubtreeLevels, leaves: Vec) -> Vec { + // We start by allocating our output buffer to the correct size with default values of + // EMPTY_WORD. + let num_cells = 2usize.pow(num_levels.into()); + let mut nodes = vec![EMPTY_WORD; num_cells]; + + // We then copy our leaves into the last `leaves.len()` cells of the buffer. + let first_ix = num_cells - leaves.len(); + nodes[first_ix..num_cells].copy_from_slice(&leaves); + + // We then do a bottom-up computation of the tree nodes. This has the potential to be + // parallelized, but whether the effort is worthwhile depends on the usual number of levels + // kept in the prefix, especially as multiple prefixes will be built in parallel by the + // forest. + for i in (1..first_ix).rev() { + let left = nodes[2 * i]; + let right = nodes[2 * i + 1]; + nodes[i] = Rpo256::merge(&[left, right]); + } + + nodes + } + + // ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Gets a reference to the item at the provided index. + /// + /// # Errors + /// + /// - [`PrefixError::IndexOutOfBounds`] if the provided `index` is not in bounds for the + /// container. + pub fn get(&self, index: LinearIndex) -> Result<&Word> { + self.fail_if_oob(index)?; + Ok(&self.nodes[index as usize]) + } + + /// Gets a mutable reference to the item at the provided index. + /// + /// Note that mutating the value at the reference will not update the rest of the in-memory + /// prefix, as this is the caller's responsibility. + /// + /// # Errors + /// + /// - [`PrefixError::IndexOutOfBounds`] if the provided `index` is not in bounds for the + /// container. + pub fn get_mut(&mut self, index: LinearIndex) -> Result<&mut Word> { + self.fail_if_oob(index)?; + Ok(&mut self.nodes[index as usize]) + } + + /// Gets a reference to the item at the provided index. + /// + /// # Errors + /// + /// - [`PrefixError::IndexOutOfBounds`] if the provided `index` is not in bounds for the + /// container. + pub fn get_node_index(&self, index: NodeIndex) -> Result<&Word> { + let lin_ix = node_index_to_linear(index); + self.fail_if_oob(lin_ix)?; + Ok(&self.nodes[lin_ix as usize]) + } + + /// Gets a mutable reference to the item at the provided index. + /// + /// Note that mutating the value at the reference will not update the rest of the in-memory + /// prefix, as this is the caller's responsibility. + /// + /// # Errors + /// + /// - [`PrefixError::IndexOutOfBounds`] if the provided `index` is not in bounds for the + /// container. + pub fn get_mut_node_index(&mut self, index: NodeIndex) -> Result<&mut Word> { + let lin_ix = node_index_to_linear(index); + self.fail_if_oob(lin_ix)?; + Ok(&mut self.nodes[lin_ix as usize]) + } + + // INTERNAL UTILITIES + // ============================================================================================ + + /// Fails with [`PrefixError::IndexOutOfBounds`] if the provided `index` is out of bounds in the + /// prefix. + fn fail_if_oob(&self, index: LinearIndex) -> Result<()> { + if index == 0 || index >= 2u64.pow(self.num_levels.into()) { + return Err(PrefixError::IndexOutOfBounds(index, self.num_levels.into())); + } + Ok(()) + } +} + +// TRAIT IMPLEMENTATIONS +// ================================================================================================ + +impl Index for InMemoryPrefix { + type Output = Word; + + /// # Panics + /// + /// Will panic if the index is out of bounds, or if the index is zero in debug builds. + fn index(&self, index: LinearIndex) -> &Self::Output { + assert!(index > 0, "The prefix uses one-based indexing"); + &self.nodes[index as usize] + } +} + +/// Note that mutating the value at the reference will not update the rest of the in-memory +/// prefix, as this is the caller's responsibility. +impl IndexMut for InMemoryPrefix { + /// # Panics + /// + /// Will panic if the index is out of bounds, or if the index is zero in debug builds. + fn index_mut(&mut self, index: LinearIndex) -> &mut Self::Output { + assert!(index > 0, "The prefix uses one-based indexing"); + &mut self.nodes[index as usize] + } +} + +impl Index for InMemoryPrefix { + type Output = Word; + + /// # Panics + /// + /// Will panic if the index is out of bounds for the prefix. + fn index(&self, index: NodeIndex) -> &Self::Output { + let lin_ix = node_index_to_linear(index); + &self.nodes[lin_ix as usize] + } +} + +/// Note that mutating the value at the reference will not update the rest of the in-memory +/// prefix, as this is the caller's responsibility. +impl IndexMut for InMemoryPrefix { + /// # Panics + /// + /// Will panic if the index is out of bounds for the prefix. + fn index_mut(&mut self, index: NodeIndex) -> &mut Self::Output { + let lin_ix = node_index_to_linear(index); + &mut self.nodes[lin_ix as usize] + } +} + +// TESTS +// ================================================================================================ + +#[cfg(feature = "std")] +#[cfg(test)] +mod test { + use super::Result; + use crate::{ + EMPTY_WORD, + hash::rpo::Rpo256, + merkle::{ + NodeIndex, + smt::{ + SubtreeLevels, + large_forest::{error::prefix::PrefixError, prefix::InMemoryPrefix}, + }, + }, + rand::test_utils::rand_value, + }; + + #[test] + fn new_wrong_leaf_count() { + // It should also error out when passing the wrong number of leaves for the specified number + // of levels. + assert_eq!( + InMemoryPrefix::new(SubtreeLevels::new_unchecked(4), vec![EMPTY_WORD; 6], rand_value()), + Err(PrefixError::WrongLeafCount(6, 8)) + ) + } + + #[test] + fn new_wrong_root() { + // Let's start by setting up some test data. + let leaves = vec![ + rand_value(), + rand_value(), + rand_value(), + rand_value(), + rand_value(), + rand_value(), + rand_value(), + rand_value(), + ]; + let expected_root = Rpo256::merge(&[ + Rpo256::merge(&[ + Rpo256::merge(&[leaves[0], leaves[1]]), + Rpo256::merge(&[leaves[2], leaves[3]]), + ]), + Rpo256::merge(&[ + Rpo256::merge(&[leaves[4], leaves[5]]), + Rpo256::merge(&[leaves[6], leaves[7]]), + ]), + ]); + + // It should error out if the wrong root is provided. + assert_eq!( + InMemoryPrefix::new(SubtreeLevels::new_unchecked(4), leaves, EMPTY_WORD), + Err(PrefixError::InvalidRestoration(EMPTY_WORD, expected_root)) + ) + } + + #[test] + fn new_successful() { + // Let's start by setting up some test data. + let leaves = vec![ + rand_value(), + rand_value(), + rand_value(), + rand_value(), + rand_value(), + rand_value(), + rand_value(), + rand_value(), + ]; + let expected_root = Rpo256::merge(&[ + Rpo256::merge(&[ + Rpo256::merge(&[leaves[0], leaves[1]]), + Rpo256::merge(&[leaves[2], leaves[3]]), + ]), + Rpo256::merge(&[ + Rpo256::merge(&[leaves[4], leaves[5]]), + Rpo256::merge(&[leaves[6], leaves[7]]), + ]), + ]); + + // When we construct it with the correct arguments we should succeed. + assert!( + InMemoryPrefix::new(SubtreeLevels::new_unchecked(4), leaves, expected_root).is_ok() + ); + } + + #[test] + fn get_by_linear_index() -> Result<()> { + // Let's start by setting up some test data. + let leaf_1 = rand_value(); + let leaf_2 = rand_value(); + let leaf_3 = rand_value(); + let leaf_4 = rand_value(); + let leaf_5 = rand_value(); + let leaf_6 = rand_value(); + let leaf_7 = rand_value(); + let leaf_8 = rand_value(); + let leaves = vec![leaf_1, leaf_2, leaf_3, leaf_4, leaf_5, leaf_6, leaf_7, leaf_8]; + + let node_2_0 = Rpo256::merge(&[leaf_1, leaf_2]); + let node_2_1 = Rpo256::merge(&[leaf_3, leaf_4]); + let node_2_2 = Rpo256::merge(&[leaf_5, leaf_6]); + let node_2_3 = Rpo256::merge(&[leaf_7, leaf_8]); + let node_1_0 = Rpo256::merge(&[node_2_0, node_2_1]); + let node_1_1 = Rpo256::merge(&[node_2_2, node_2_3]); + let root = Rpo256::merge(&[node_1_0, node_1_1]); + + let mut prefix = InMemoryPrefix::new(SubtreeLevels::new_unchecked(4), leaves, root)?; + + // We can now query by the linear index to retrieve values and check they are correct. We + // start with `0` which is a special value and shouldn't be accessed, and 16 which is one + // past the end. + assert_eq!(prefix.get(0), Err(PrefixError::IndexOutOfBounds(0, 4))); + assert_eq!(prefix.get(16), Err(PrefixError::IndexOutOfBounds(16, 4))); + + // But if we query at valid indices we should get the right results back. + assert_eq!(prefix.get(1)?, &root); + assert_eq!(prefix.get(15)?, &leaf_8); + assert_eq!(prefix.get(7)?, &node_2_3); + assert_eq!(prefix.get(8)?, &leaf_1); + + // We can also get a mutable reference, allowing us to mutate. + let new_value = rand_value(); + *prefix.get_mut(15)? = new_value; + assert_eq!(prefix.get(15)?, &new_value); + + // But doing this will leave other values unchanged. + assert_eq!(prefix.get(1)?, &root); + + Ok(()) + } + + #[test] + fn get_by_node_index() -> Result<()> { + // Let's start by setting up some test data. + let leaf_1 = rand_value(); + let leaf_2 = rand_value(); + let leaf_3 = rand_value(); + let leaf_4 = rand_value(); + let leaf_5 = rand_value(); + let leaf_6 = rand_value(); + let leaf_7 = rand_value(); + let leaf_8 = rand_value(); + let leaves = vec![leaf_1, leaf_2, leaf_3, leaf_4, leaf_5, leaf_6, leaf_7, leaf_8]; + + let node_2_0 = Rpo256::merge(&[leaf_1, leaf_2]); + let node_2_1 = Rpo256::merge(&[leaf_3, leaf_4]); + let node_2_2 = Rpo256::merge(&[leaf_5, leaf_6]); + let node_2_3 = Rpo256::merge(&[leaf_7, leaf_8]); + let node_1_0 = Rpo256::merge(&[node_2_0, node_2_1]); + let node_1_1 = Rpo256::merge(&[node_2_2, node_2_3]); + let root = Rpo256::merge(&[node_1_0, node_1_1]); + + let mut prefix = InMemoryPrefix::new(SubtreeLevels::new_unchecked(4), leaves, root)?; + + // We can now query by the linear index to retrieve values and check they are correct. We + // start with (4, 0), which is out of bounds. + assert_eq!( + prefix.get_node_index(NodeIndex::new_unchecked(4, 0)), + Err(PrefixError::IndexOutOfBounds(16, 4)) + ); + + // But if we query at valid indices we should get the right results back. + assert_eq!(prefix.get_node_index(NodeIndex::new_unchecked(0, 0))?, &root); + assert_eq!(prefix.get_node_index(NodeIndex::new_unchecked(3, 7))?, &leaf_8); + assert_eq!(prefix.get_node_index(NodeIndex::new_unchecked(2, 3))?, &node_2_3); + assert_eq!(prefix.get_node_index(NodeIndex::new_unchecked(3, 0))?, &leaf_1); + + // We can also get a mutable reference, allowing us to mutate. + let new_value = rand_value(); + *prefix.get_mut_node_index(NodeIndex::new_unchecked(3, 7))? = new_value; + assert_eq!(prefix.get_node_index(NodeIndex::new_unchecked(3, 7))?, &new_value); + + // But doing this will leave other values unchanged. + assert_eq!(prefix.get_node_index(NodeIndex::new_unchecked(0, 0))?, &root); + + Ok(()) + } + + #[test] + fn index_by_linear_index() -> Result<()> { + // Let's start by setting up some test data. + let leaf_1 = rand_value(); + let leaf_2 = rand_value(); + let leaf_3 = rand_value(); + let leaf_4 = rand_value(); + let leaf_5 = rand_value(); + let leaf_6 = rand_value(); + let leaf_7 = rand_value(); + let leaf_8 = rand_value(); + let leaves = vec![leaf_1, leaf_2, leaf_3, leaf_4, leaf_5, leaf_6, leaf_7, leaf_8]; + + let node_2_0 = Rpo256::merge(&[leaf_1, leaf_2]); + let node_2_1 = Rpo256::merge(&[leaf_3, leaf_4]); + let node_2_2 = Rpo256::merge(&[leaf_5, leaf_6]); + let node_2_3 = Rpo256::merge(&[leaf_7, leaf_8]); + let node_1_0 = Rpo256::merge(&[node_2_0, node_2_1]); + let node_1_1 = Rpo256::merge(&[node_2_2, node_2_3]); + let root = Rpo256::merge(&[node_1_0, node_1_1]); + + let mut prefix = InMemoryPrefix::new(SubtreeLevels::new_unchecked(4), leaves, root)?; + + // We can now query by the linear index to retrieve values and check they are correct. + assert_eq!(prefix[1], root); + assert_eq!(prefix[15], leaf_8); + assert_eq!(prefix[7], node_2_3); + assert_eq!(prefix[8], leaf_1); + + // We can also get a mutable reference, allowing us to mutate. + let new_value = rand_value(); + prefix[15] = new_value; + assert_eq!(prefix[15], new_value); + + // But doing this will leave other values unchanged. + assert_eq!(prefix[1], root); + + Ok(()) + } + + #[test] + fn index_by_node_index() -> Result<()> { + // Let's start by setting up some test data. + let leaf_1 = rand_value(); + let leaf_2 = rand_value(); + let leaf_3 = rand_value(); + let leaf_4 = rand_value(); + let leaf_5 = rand_value(); + let leaf_6 = rand_value(); + let leaf_7 = rand_value(); + let leaf_8 = rand_value(); + let leaves = vec![leaf_1, leaf_2, leaf_3, leaf_4, leaf_5, leaf_6, leaf_7, leaf_8]; + + let node_2_0 = Rpo256::merge(&[leaf_1, leaf_2]); + let node_2_1 = Rpo256::merge(&[leaf_3, leaf_4]); + let node_2_2 = Rpo256::merge(&[leaf_5, leaf_6]); + let node_2_3 = Rpo256::merge(&[leaf_7, leaf_8]); + let node_1_0 = Rpo256::merge(&[node_2_0, node_2_1]); + let node_1_1 = Rpo256::merge(&[node_2_2, node_2_3]); + let root = Rpo256::merge(&[node_1_0, node_1_1]); + + let mut prefix = InMemoryPrefix::new(SubtreeLevels::new_unchecked(4), leaves, root)?; + + // We can now query by the linear index to retrieve values and check they are correct. + assert_eq!(prefix[NodeIndex::new_unchecked(0, 0)], root); + assert_eq!(prefix[NodeIndex::new_unchecked(3, 7)], leaf_8); + assert_eq!(prefix[NodeIndex::new_unchecked(2, 3)], node_2_3); + assert_eq!(prefix[NodeIndex::new_unchecked(3, 0)], leaf_1); + + // We can also get a mutable reference, allowing us to mutate. + let new_value = rand_value(); + prefix[NodeIndex::new_unchecked(3, 7)] = new_value; + assert_eq!(prefix[NodeIndex::new_unchecked(3, 7)], new_value); + + // But doing this will leave other values unchanged. + assert_eq!(prefix[NodeIndex::new_unchecked(0, 0)], root); + + Ok(()) + } +} diff --git a/miden-crypto/src/merkle/smt/large_forest/utils.rs b/miden-crypto/src/merkle/smt/large_forest/utils.rs index 3e51fc652..f3269c50e 100644 --- a/miden-crypto/src/merkle/smt/large_forest/utils.rs +++ b/miden-crypto/src/merkle/smt/large_forest/utils.rs @@ -6,11 +6,27 @@ use core::fmt::{Display, Formatter}; +use crate::merkle::NodeIndex; + /// The maximum number of levels that can be stored in a given subtree for a tree with depth 64. /// /// Simply put, it must never include the leaves level, and hence is one less than that depth. pub const MAX_NUM_SUBTREE_LEVELS: u8 = 63; +// TYPE ALIASES +// ================================================================================================ + +/// The type of linear indexes in the in-memory tree prefix. +/// +/// It is assumed to be indexing into a linear container of nodes which contains `2.pow(depth + 1)` +/// entries laid out as follows: +/// +/// - Index 0 is unused, containing a sentinel value. +/// - Index 1 contains the root of the tree. +/// - For a node at index `i`, the left child is found at index `2 * i` and the right child at index +/// `2 * i + 1`. +pub type LinearIndex = u64; + // SUBTREE LEVELS // ================================================================================================ @@ -82,3 +98,40 @@ impl Display for SubtreeLevels { write!(f, "{}", self.value) } } + +// UTILITY FUNCTIONS +// ================================================================================================ + +/// Converts the provided `ix` into a linear index for use in the prefix, based on the addressing +/// scheme set out in the [`LinearIndex`] documentation. +#[must_use] +pub fn node_index_to_linear(ix: NodeIndex) -> LinearIndex { + // The NodeIndex is a pair of (depth, index_from_left) where the root is (0, 0). + (1 << ix.depth() as u64) + ix.value() +} + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn node_index_to_lin() { + // Check the edge-cases to start with, being both the minimum and maximum supported number + // of levels. + assert_eq!(node_index_to_linear(NodeIndex::new_unchecked(0, 0)), 1); + assert_eq!( + node_index_to_linear(NodeIndex::new_unchecked(62, 2u64.pow(62) - 1)), + 2u64.pow(63) - 1 + ); + + // Then we can try some random ones. + assert_eq!(node_index_to_linear(NodeIndex::new_unchecked(7, 3)), 2u64.pow(7) + 3); + assert_eq!( + node_index_to_linear(NodeIndex::new_unchecked(21, 2u64.pow(12))), + 2u64.pow(21) + 2u64.pow(12) + ); + } +} diff --git a/miden-crypto/src/merkle/smt/mod.rs b/miden-crypto/src/merkle/smt/mod.rs index ffbe7efbf..138995144 100644 --- a/miden-crypto/src/merkle/smt/mod.rs +++ b/miden-crypto/src/merkle/smt/mod.rs @@ -29,8 +29,10 @@ pub use large::{ pub use large::{RocksDbConfig, RocksDbStorage}; pub mod large_forest; + pub use large_forest::{ - LargeSmtForestError, Storage as ForestStorage, StorageError, StoredTreeHandle, SubtreeLevels, + LargeSmtForest, LargeSmtForestError, Storage as ForestStorage, StorageError, StoredTreeHandle, + SubtreeLevels, }; mod simple;