Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions miden-crypto/src/merkle/smt/large_forest/error/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ pub mod subtree;

use thiserror::Error;

use crate::merkle::{
MerkleError,
smt::large_forest::{error::prefix::PrefixError, history::error::HistoryError, storage},
use crate::{
Word,
merkle::{
MerkleError,
smt::large_forest::{error::prefix::PrefixError, history::error::HistoryError, storage},
},
};

// LARGE SMT FOREST ERROR
Expand All @@ -31,6 +34,10 @@ pub enum LargeSmtForestError {
/// Errors with the in-memory tree prefixes in the forest.
#[error(transparent)]
PrefixError(#[from] PrefixError),

/// Raised when an attempt is made to modify a frozen tree.
#[error("Attempted to modify frozen tree with root {0}")]
InvalidModification(Word),
}

/// The result type for use within the large SMT forest portion of the library.
Expand Down
266 changes: 258 additions & 8 deletions miden-crypto/src/merkle/smt/large_forest/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,57 @@

mod error;
pub mod history;
pub mod operation;
mod prefix;
pub mod root;
pub mod storage;
pub mod utils;

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};

use crate::{
Map, Set, Word,
merkle::{
EmptySubtreeRoots,
smt::{
SMT_DEPTH, SmtProof,
large_forest::{
history::{History, VersionId},
operation::{SmtForestUpdateBatch, SmtUpdateBatch},
prefix::InMemoryPrefix,
root::RootInfo,
},
},
},
};
// SPARSE MERKLE TREE FOREST
// ================================================================================================

/// A high-performance forest of sparse merkle trees with pluggable storage.
///
/// # Current and Frozen Trees
///
/// Trees in the forest fall into two categories:
///
/// 1. **Current:** These trees represent the latest version of their 'tree lineage' and can be
/// modified to generate a new tree version in the forest.
/// 2. **Frozen:** These are historical versions of trees that are no longer current, and are
/// considered 'frozen' and hence cannot be modified to generate a new tree version in the
/// forest. This is because being able to do so would effectively create a "fork" in the history,
/// and hence allow the forest to yield potentially invalid responses with regard to the
/// blockchain history.
///
/// If an attempt is made to modify a frozen tree, the method in question will yield an
/// [`LargeSmtForestError::InvalidModification`] error as doing so represents a programmer bug.
///
/// # Performance
///
/// The performance characteristics of this forest
#[allow(dead_code)] // Temporary, while the tree gets built.
/// The performance characteristics of this forest depend heavily on the choice of underlying
/// [`Storage`] implementation. Where something more specific can be said about a particular method
/// call, the documentation for that method will state it.
#[allow(dead_code)] // Temporarily
#[derive(Debug)]
pub struct LargeSmtForest<S: Storage> {
/// The underlying data storage for the portion of the tree that is not guaranteed to be in
Expand All @@ -68,13 +100,41 @@ pub struct LargeSmtForest<S: Storage> {

/// The container for the in-memory prefixes of each tree stored in the forest, identified by
/// their current root.
///
/// Must contain an entry for every root that has an entry in both [`Self::histories`] and
/// [`Self::full_tree_versions`].
prefixes: Map<Word, InMemoryPrefix>,

/// The container for the historical versions of each tree stored in the forest, identified by
/// the current root.
///
/// Must contain an entry for every root that has an entry in both [`Self::prefixes`] and
/// [`Self::full_tree_versions`].
histories: Map<Word, History>,

/// A mapping from the roots of the full trees stored in this forest to their corresponding
/// versions.
///
/// Must contain an entry for every root that has an entry in both [`Self::prefixes`] and
/// [`Self::histories`].
full_tree_versions: Map<Word, VersionId>,
}

impl<S: Storage> LargeSmtForest<S> {
// CONSTRUCTORS
// --------------------------------------------------------------------------------------------
// CONSTRUCTION AND BASIC QUERIES
// ================================================================================================

/// These functions deal with the creation of new forest instances, and hence rely on the ability to
/// query storage to do so.
///
/// # Performance
///
/// All the methods in this impl block require access to the underlying [`Storage`] instance to
/// return results. This means that their performance will depend heavily on the specific instance
/// with which the forest was constructed.
///
/// Where anything more specific can be said about performance, the method documentation will
/// contain more detail.
impl<S: Storage> LargeSmtForest<S> {
/// Constructs a new forest backed by the provided `storage`.
///
/// The constructor will treat whatever state is contained within the provided `storage` as the
Expand All @@ -87,6 +147,196 @@ impl<S: Storage> LargeSmtForest<S> {
/// - [`LargeSmtForestError::StorageError`] if the forest cannot be started up correctly from
/// storage.
pub fn new(_storage: S) -> Result<Self> {
todo!()
todo!("LargeSmtForest::new")
}
}

/// These methods provide the ability to perform basic queries on the forest without the need to
/// access the underlying tree storage.
///
/// # Performance
///
/// All of these methods can be performed fully in-memory, and hence their performance is
/// predictable on a given machine regardless of the choice of [`Storage`] instance for the forest.
impl<S: Storage> LargeSmtForest<S> {
/// Returns a set of all the roots that the forest knows about, including those of all
/// versions.
pub fn roots(&self) -> Set<Word> {
let mut roots: Set<Word> = self.prefixes.keys().cloned().collect();
self.histories.values().for_each(|h| roots.extend(h.roots()));
roots
}

/// Returns the number of trees in the forest.
pub fn tree_count(&self) -> usize {
// History::num_versions does not account for the 'current version' so we add one to each of
// those counts, and then we add one overall to account for the "phantom empty tree".
self.histories.values().map(|h| h.num_versions() + 1).sum::<usize>() + 1
}

/// Returns `true` if the provided `root` points to a tree that is the latest version, and
/// `false` otherwise.
///
/// A tree being the latest version is one that can be modified to yield a new version.
pub fn is_latest_version(&self, root: Word) -> bool {
self.prefixes.contains_key(&root) || *EmptySubtreeRoots::entry(SMT_DEPTH, 0) == root
}
}

// QUERIES
// ================================================================================================

/// These methods pertain to non-mutating queries about the data stored in the forest. They differ
/// from the simple queries in the previous block by requiring access to storage to function.
///
/// # Performance
///
/// All the methods in this impl block require access to the underlying [`Storage`] instance to
/// return results. This means that their performance will depend heavily on the specific instance
/// with which the forest was constructed.
///
/// Where anything more specific can be said about performance, the method documentation will
/// contain more detail.
impl<S: Storage> LargeSmtForest<S> {
/// Returns an opening for the specified `key` in the SMT with the specified `root`.
///
/// # Errors
///
/// - [`LargeSmtForestError::StorageError`] if an error occurs when trying to read from storage.
/// - [`LargeSmtForestError::MerkleError`] if no tree with the provided `root` exists in the
/// forest, or if the forest does not contain sufficient data to provide an opening for `key`.
pub fn open(&self, _root: Word, _key: Word) -> Result<SmtProof> {
todo!("LargeSmtForest::open")
}

/// Returns data describing what information the forest knows about the provided `root`.
pub fn contains_root(&self, root: Word) -> RootInfo {
if self.prefixes.contains_key(&root) {
RootInfo::LatestVersion
} else if let Some(h) = self.histories.get(&root)
&& h.is_known_root(root)
{
RootInfo::HistoricalVersion
} else if root == *EmptySubtreeRoots::entry(SMT_DEPTH, 0) {
RootInfo::EmptyTree
} else {
RootInfo::Missing
}
}
}

// SINGLE-TREE MODIFIERS
// ================================================================================================

/// These methods pertain to modifications that can be made to a single tree in the forest. They
/// exploit parallelism within the single target tree wherever possible.
///
/// # Performance
///
/// All the methods in this impl block require access to the underlying [`Storage`] instance to
/// return results. This means that their performance will depend heavily on the specific instance
/// with which the forest was constructed.
///
/// Where anything more specific can be said about performance, the method documentation will
/// contain more detail.
#[allow(dead_code)] // Temporarily
impl<S: Storage> LargeSmtForest<S> {
/// Performs the provided `operations` on the tree with the provided `root`, adding a single new
/// root to the forest, giving it for the entire batch and returning that root.
///
/// If applying the `operations` results in no changes to the tree, then `root` will be returned
/// unchanged and no new tree will be allocated.
///
/// # Errors
///
/// - [`LargeSmtForestError::StorageError`] if an error occurs when trying to access storage.
pub fn batch_modify(
&mut self,
_root: Word,
_new_version: VersionId,
_operations: SmtUpdateBatch,
) -> Result<Word> {
todo!("LargeSmtForest::batch_modify")
}

/// Inserts the specified `key`, `value` pair into the tree in the forest with the specified
/// `root`, returning the new root of that tree.
///
/// Any insertion operation where `root` is equal to the root of the empty tree will generate a
/// new unique tree in the forest, rather than adding history to an existing tree.
///
/// # Errors
///
/// - [`LargeSmtForestError::StorageError`] if an error occurs when trying to access storage.
fn insert(&mut self, _root: Word, _key: Word, _proof: SmtProof) -> Result<Word> {
todo!("LargeSmtForest::insert")
}

/// Removes the `key` and its associated value from the tree specified by `root`, returning the
/// new root of the tree after performing that modification.
///
/// Note that if `key` does not exist in the tree with the provided `root`, then `root` will be
/// returned unchanged and no new tree will be allocated.
///
/// # Errors
///
/// - [`LargeSmtForestError::StorageError`] if an error occurs when trying to access storage.
fn remove(&mut self, _root: Word, _key: Word) -> Result<Word> {
todo!("LargeSmtForest::remove")
}
}

// MULTI-TREE MODIFIERS
// ================================================================================================

/// These methods pertain to modifications that can be made to multiple trees in the forest at once.
/// They exploit parallelism both between trees and within trees wherever possible.
///
/// # Performance
///
/// All the methods in this impl block require access to the underlying [`Storage`] instance to
/// return results. This means that their performance will depend heavily on the specific instance
/// with which the forest was constructed.
///
/// Where anything more specific can be said about performance, the method documentation will
/// contain more detail.
impl<S: Storage> LargeSmtForest<S> {
/// Performs the provided `operations` on the forest, adding at most one new root to the forest
/// for each target root in `operations`, returning a mapping from old root to new root.
///
/// If applying the associated batch to any given tree in the forest results in no changes to
/// the tree, the initial root will be returned and no new tree will be allocated.
///
/// # Errors
///
/// - [`LargeSmtForestError::StorageError`] if an error occurs when trying to access storage.
pub fn batch_modify_forest(
&mut self,
_operations: SmtForestUpdateBatch,
) -> Result<Map<Word, Word>> {
todo!("LargeSmtForest::batch_modify_forest")
}

/// Removes all tree versions in the forest that are older than the provided `version`.
///
/// In the case that the current version of a given tree in the forest is older than `version`,
/// that current version is nevertheless retained.
pub fn truncate(&mut self, version: VersionId) {
// We start by clearing any history for which the `version` corresponds to the latest
// version and hence the full tree.
self.full_tree_versions.iter().for_each(|(k, v)| {
if *v == version {
self.histories
.get_mut(k)
.expect("A full tree did not have a corresponding history, but is required to")
.clear();
}
});

// Then we just run through all the histories and truncate them to this version if needed,
// which provides the correct behaviour.
self.histories.values_mut().for_each(|h| {
h.truncate(version);
});
}
}
Loading