Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- [BREAKING] Upgraded direct `rand` dependencies to 0.10, updating RNG trait bounds and removing direct `rand_hc` usage ([#995](https://github.com/0xMiden/crypto/pull/995)).
- perf: fuse per-group accumulator and defer allocations ([#1008](https://github.com/0xMiden/crypto/pull/1008))
- [BREAKING] Reorganized `miden-lifted-stark` internals: consolidated `align`, `bitrev`, `horner`, and `packing` helpers under a new `util` module; moved `reconstruct_quotient` onto `LiftedCoset`; removed the legacy `fri::*` re-export facade ([#1000](https://github.com/0xMiden/crypto/pull/1000)).
- [BREAKING] Extracted `BackendReader`, allowing `LargeSmtForest<S>` to work with read-only storage backends ([#986](https://github.com/0xMiden/crypto/pull/986)).

## 0.25.0 (2026-05-01)

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ test-p3-parallel: ## Run Miden STARK crate tests with the parallel feature enabl

.PHONY: test-large-smt
test-large-smt: ## Run large SMT unit tests and RocksDB integration tests
cargo nextest run --success-output immediate --profile large-smt --cargo-profile test-release --features rocksdb
cargo nextest run --success-output immediate --profile large-smt --cargo-profile test-release --features persistent-forest

.PHONY: test
test: test-default test-no-std test-docs test-large-smt ## Run all tests except concurrent SMT tests
Expand Down
19 changes: 2 additions & 17 deletions miden-crypto/src/merkle/smt/large/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ type MutatedLeaves = (MutatedSubtreeLeaves, Map<u64, SmtLeaf>, Map<Word, Word>,
///
/// `LargeSmt` implements [`Clone`] when its storage is cloneable. The in-memory top is shared and
/// detaches on mutation.
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct LargeSmt<S: SmtStorageReader> {
storage: S,
/// Shared flat array representation of in-memory nodes.
Expand All @@ -351,17 +351,6 @@ pub struct LargeSmt<S: SmtStorageReader> {
entry_count: usize,
}

impl<S: SmtStorageReader + Clone> Clone for LargeSmt<S> {
fn clone(&self) -> Self {
Self {
storage: self.storage.clone(),
in_memory_nodes: self.in_memory_nodes.clone(),
leaf_count: self.leaf_count,
entry_count: self.entry_count,
}
}
}

impl<S: SmtStorageReader> LargeSmt<S> {
// CONSTANTS
// --------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -478,6 +467,7 @@ impl<S: SmtStorageReader> LargeSmt<S> {
<Self as SparseMerkleTreeReader<SMT_DEPTH>>::get_inner_node(self, index)
}

// Triggers copy-on-write: clones the shared node array only if other references exist.
pub(crate) fn in_memory_nodes_mut(&mut self) -> &mut [Word] {
Arc::make_mut(&mut self.in_memory_nodes)
}
Expand Down Expand Up @@ -526,11 +516,6 @@ impl<S: SmtStorage> LargeSmt<S> {
entry_count: self.entry_count,
})
}
}

impl<S: SmtStorage> LargeSmt<S> {
// STATE MUTATORS
// --------------------------------------------------------------------------------------------

/// Inserts a value at the specified key, returning the previous value associated with that key.
/// Recall that by definition, any key that hasn't been updated is associated with
Expand Down
67 changes: 64 additions & 3 deletions miden-crypto/src/merkle/smt/large_forest/backend/memory/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,59 @@ use crate::{
merkle::smt::{
LeafIndex, SMT_DEPTH, Smt, SmtLeaf, SmtProof, VersionId,
large_forest::{
Backend,
Backend, BackendReader,
backend::{BackendError, MutationSet, Result},
operation::{SmtForestUpdateBatch, SmtUpdateBatch},
root::{LineageId, TreeEntry, TreeWithRoot},
},
},
};

// IN-MEMORY BACKEND SNAPSHOT
// ================================================================================================

/// A read-only, point-in-time snapshot of an [`InMemoryBackend`].
///
/// This type intentionally implements only [`BackendReader`], not [`Backend`]. It is returned by
/// [`InMemoryBackend::reader`] to hand out a detached copy of the backend state without exposing
/// any mutation capabilities.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct InMemoryBackendSnapshot(InMemoryBackend);

impl BackendReader for InMemoryBackendSnapshot {
fn open(&self, lineage: LineageId, key: Word) -> Result<SmtProof> {
self.0.open(lineage, key)
}

fn get_leaf(&self, lineage: LineageId, leaf_index: LeafIndex<SMT_DEPTH>) -> Result<SmtLeaf> {
self.0.get_leaf(lineage, leaf_index)
}

fn get(&self, lineage: LineageId, key: Word) -> Result<Option<Word>> {
self.0.get(lineage, key)
}

fn version(&self, lineage: LineageId) -> Result<VersionId> {
self.0.version(lineage)
}

fn lineages(&self) -> Result<impl Iterator<Item = LineageId>> {
self.0.lineages()
}

fn trees(&self) -> Result<impl Iterator<Item = TreeWithRoot>> {
self.0.trees()
}

fn entry_count(&self, lineage: LineageId) -> Result<usize> {
self.0.entry_count(lineage)
}

fn entries(&self, lineage: LineageId) -> Result<impl Iterator<Item = Result<TreeEntry>>> {
self.0.entries(lineage)
}
}

// IN-MEMORY BACKEND
// ================================================================================================

Expand All @@ -37,12 +82,17 @@ impl InMemoryBackend {
let trees = Map::default();
Self { trees }
}

/// Converts this backend into a read-only snapshot.
pub fn into_snapshot(self) -> InMemoryBackendSnapshot {
InMemoryBackendSnapshot(self)
}
}

// BACKEND TRAIT
// BACKEND READER TRAIT
// ================================================================================================

impl Backend for InMemoryBackend {
impl BackendReader for InMemoryBackend {
/// Returns an opening for the specified `key` in the SMT with the specified `lineage`.
///
/// # Errors
Expand Down Expand Up @@ -134,6 +184,17 @@ impl Backend for InMemoryBackend {
let tree = self.trees.get(&lineage).ok_or(BackendError::UnknownLineage(lineage))?;
Ok(tree.tree.entries().map(|(k, v)| Ok(TreeEntry { key: *k, value: *v })))
}
}

// BACKEND TRAIT
// ================================================================================================

impl Backend for InMemoryBackend {
type Reader = InMemoryBackendSnapshot;

fn reader(&self) -> Result<Self::Reader> {
Ok(self.clone().into_snapshot())
}

/// Adds the provided `lineage` to the forest.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use proptest::prelude::*;
use crate::{
EMPTY_WORD,
merkle::smt::{
Backend, Smt, SmtForestUpdateBatch, SmtUpdateBatch, TreeWithRoot,
Backend, BackendReader, Smt, SmtForestUpdateBatch, SmtUpdateBatch, TreeWithRoot,
large_forest::{
InMemoryBackend,
test_utils::{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use itertools::Itertools;
use crate::{
EMPTY_WORD, Word,
merkle::smt::{
Backend, BackendError, Smt, SmtForestUpdateBatch, SmtUpdateBatch, VersionId,
Backend, BackendError, BackendReader, Smt, SmtForestUpdateBatch, SmtUpdateBatch, VersionId,
large_forest::{
InMemoryBackend,
backend::Result,
Expand Down
49 changes: 33 additions & 16 deletions miden-crypto/src/merkle/smt/large_forest/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,18 @@ use crate::{
},
};

// BACKEND
// BACKEND READER
// ================================================================================================

/// The backing storage for the SMT forest, providing the necessary high-level methods for
/// performing operations on the full trees that make up the forest, while allowing the forest
/// itself to be storage agnostic.
/// The read-only interface for the SMT forest storage backend.
///
/// This trait provides the query operations necessary to read the full trees that make up the
/// forest. It is a supertrait of [`Backend`], which extends it with write operations.
///
/// # Backend Data Storage
///
/// Having a generic [`Backend`] provides no guarantees to the user about how it stores data and
/// what patterns are used for data access under the hood. It is, however, guaranteed to store
/// Having a generic [`BackendReader`] provides no guarantees to the user about how it stores data
/// and what patterns are used for data access under the hood. It is, however, guaranteed to store
/// _only_ the data necessary to describe the latest state of each tree in the forest.
///
/// # Error Handling
Expand All @@ -56,12 +57,6 @@ use crate::{
///
/// # Expected Behavior
///
/// Certain methods on this trait (e.g. [`Backend::update_tree`]) provide behaviors expected for
/// that method. These combine with the following trait-level behavior requirements to become part
/// of the contract of the method, but a portion that cannot be encoded in the type system. Any
/// failure to conform to these expected behaviors is **considered a bug in the implementation** of
/// the backend, and must be rectified.
///
/// The following behavior is expected of all methods in implementations of this trait:
///
/// - For any failure derived from user input (see _User-Derived Errors_ above), the data and the
Expand All @@ -70,13 +65,10 @@ use crate::{
/// caller by returning a variant of [`BackendError`] that is **not [`BackendError::Internal`]**.
/// Methods may place additional constraints on which errors are used to signal certain failures.
/// Such failures should not lead to data corruption of any persistent data.
pub trait Backend
pub trait BackendReader
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit (feel free to ignore): Would ReadOnlyBackend be a better name?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, but would need to update SmtStorageReader and SparseMerkleTreeReader also for consistency.

where
Self: Debug,
{
// QUERIES
// ============================================================================================

/// Returns an opening for the specified `key` in the SMT with the specified `lineage`.
///
/// It is the responsibility of the forest to ensure lineage existence before querying the
Expand Down Expand Up @@ -148,6 +140,31 @@ where
/// - `None` will be returned upon successful completion, or at any time after an error has been
/// returned.
fn entries(&self, lineage: LineageId) -> Result<impl Iterator<Item = Result<TreeEntry>>>;
}

// BACKEND
// ================================================================================================

/// The full read-write interface for the SMT forest storage backend.
///
/// This trait extends [`BackendReader`] with mutation operations, allowing the forest to add new
/// lineages and update existing ones.
///
/// # Implementation Contract
///
/// Method-level doc comments describe invariants that cannot be encoded in the type system.
/// Implementations are responsible for upholding them.
pub trait Backend: BackendReader {
/// The read-only view type returned by [`Self::reader`].
///
/// The returned type implements [`BackendReader`] but not [`Backend`], providing a read-only
/// guarantee. Implementations may return either a point-in-time snapshot or a live view, but
/// the view must always reflect a consistent committed state (not partial writes). Holding the
/// reader must not block writes in any way.
type Reader: BackendReader;

/// Returns a read-only view of this backend that observes its current state.
fn reader(&self) -> Result<Self::Reader>;

// SINGLE-TREE MODIFIERS
// ============================================================================================
Expand Down
Loading
Loading