Skip to main content

miden_protocol/block/account_tree/
mod.rs

1use alloc::string::ToString;
2use alloc::vec::Vec;
3
4#[cfg(feature = "std")]
5use miden_crypto::merkle::smt::{LargeSmt, LargeSmtError, SmtStorage};
6
7use crate::Word;
8use crate::account::{AccountId, AccountIdPrefix};
9use crate::block::{SmtBackend, SmtBackendReader};
10use crate::crypto::merkle::MerkleError;
11use crate::crypto::merkle::smt::{MutationSet, SMT_DEPTH, Smt, SmtLeaf};
12use crate::errors::AccountTreeError;
13use crate::utils::serde::{
14    ByteReader,
15    ByteWriter,
16    Deserializable,
17    DeserializationError,
18    Serializable,
19};
20
21mod partial;
22pub use partial::PartialAccountTree;
23
24mod witness;
25pub use witness::AccountWitness;
26
27mod account_id_key;
28pub use account_id_key::AccountIdKey;
29
30// ACCOUNT TREE
31// ================================================================================================
32
33/// The sparse merkle tree of all accounts in the blockchain.
34///
35/// The key is the [`AccountId`] while the value is the current state commitment of the account,
36/// i.e. [`Account::to_commitment`](crate::account::Account::to_commitment). If the account is new,
37/// then the commitment is the [`EMPTY_WORD`](crate::EMPTY_WORD).
38///
39/// Each account ID occupies exactly one leaf in the tree, which is identified by its
40/// [`AccountId::prefix`]. In other words, account ID prefixes are unique in the blockchain.
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct AccountTree<S = Smt> {
43    smt: S,
44}
45
46impl<S> Default for AccountTree<S>
47where
48    S: Default,
49{
50    fn default() -> Self {
51        Self { smt: Default::default() }
52    }
53}
54
55impl<S> AccountTree<S>
56where
57    S: SmtBackendReader<Error = MerkleError>,
58{
59    // CONSTANTS
60    // --------------------------------------------------------------------------------------------
61
62    /// The depth of the account tree.
63    pub const DEPTH: u8 = SMT_DEPTH;
64
65    // CONSTRUCTORS
66    // --------------------------------------------------------------------------------------------
67
68    /// Creates a new `AccountTree` from its inner representation with validation.
69    ///
70    /// This constructor validates that the provided SMT upholds the guarantees of the
71    /// [`AccountTree`]. The constructor ensures only the uniqueness of the account ID prefix.
72    ///
73    /// # Errors
74    ///
75    /// Returns an error if:
76    /// - The SMT contains invalid account IDs.
77    /// - The SMT contains duplicate account ID prefixes.
78    pub fn new(smt: S) -> Result<Self, AccountTreeError> {
79        for (_leaf_idx, leaf) in smt.leaves() {
80            match leaf {
81                SmtLeaf::Empty(_) => {
82                    // Empty leaves are fine (shouldn't be returned by leaves() but handle anyway)
83                    continue;
84                },
85                SmtLeaf::Single((key, _)) => {
86                    // Single entry is good - verify it's a valid account ID
87                    AccountIdKey::try_from_word(key).map_err(|err| {
88                        AccountTreeError::InvalidAccountIdKey { key, source: err }
89                    })?;
90                },
91                SmtLeaf::Multiple(entries) => {
92                    // Multiple entries means duplicate prefixes
93                    // Extract one of the keys to identify the duplicate prefix
94                    if let Some((key, _)) = entries.first() {
95                        let key = *key;
96                        let account_id = AccountIdKey::try_from_word(key).map_err(|err| {
97                            AccountTreeError::InvalidAccountIdKey { key, source: err }
98                        })?;
99
100                        return Err(AccountTreeError::DuplicateIdPrefix {
101                            duplicate_prefix: account_id.prefix(),
102                        });
103                    }
104                },
105            }
106        }
107
108        Ok(Self::new_unchecked(smt))
109    }
110
111    /// Creates a new `AccountTree` from its inner representation without validation.
112    ///
113    /// # Warning
114    ///
115    /// Assumes the provided SMT upholds the guarantees of the [`AccountTree`]. Specifically:
116    /// - Each account ID prefix must be unique (no duplicate prefixes allowed)
117    /// - The SMT should only contain valid account IDs and their state commitments
118    ///
119    /// See type-level documentation for more details on these invariants. Using this constructor
120    /// with an SMT that violates these guarantees may lead to undefined behavior.
121    pub fn new_unchecked(smt: S) -> Self {
122        AccountTree { smt }
123    }
124
125    // PUBLIC ACCESSORS
126    // --------------------------------------------------------------------------------------------
127
128    /// Returns an opening of the leaf associated with the `account_id`. This is a proof of the
129    /// current state commitment of the given account ID.
130    ///
131    /// Conceptually, an opening is a Merkle path to the leaf, as well as the leaf itself.
132    ///
133    /// # Panics
134    ///
135    /// Panics if the SMT backend fails to open the leaf (only possible with `LargeSmt` backend).
136    pub fn open(&self, account_id: AccountId) -> AccountWitness {
137        let key = AccountIdKey::from(account_id).as_word();
138        let proof = self.smt.open(&key);
139
140        AccountWitness::from_smt_proof(account_id, proof)
141    }
142
143    /// Returns the current state commitment of the given account ID.
144    pub fn get(&self, account_id: AccountId) -> Word {
145        let key = AccountIdKey::from(account_id).as_word();
146        self.smt.get_value(&key)
147    }
148
149    /// Returns the root of the tree.
150    pub fn root(&self) -> Word {
151        self.smt.root()
152    }
153
154    /// Returns true if the tree contains a leaf for the given account ID prefix.
155    pub fn contains_account_id_prefix(&self, account_id_prefix: AccountIdPrefix) -> bool {
156        let key = AccountIdKey::id_prefix_to_smt_key(account_id_prefix);
157        let is_empty = matches!(self.smt.get_leaf(&key), SmtLeaf::Empty(_));
158        !is_empty
159    }
160
161    /// Returns the number of account IDs in this tree.
162    pub fn num_accounts(&self) -> usize {
163        // Because each ID's prefix is unique in the tree and occupies a single leaf, the number of
164        // IDs in the tree is equivalent to the number of leaves in the tree.
165        self.smt.num_leaves()
166    }
167
168    /// Returns an iterator over the account ID state commitment pairs in the tree.
169    pub fn account_commitments(&self) -> impl Iterator<Item = (AccountId, Word)> {
170        self.smt.leaves().map(|(_leaf_idx, leaf)| {
171            // SAFETY: By construction no Multiple variant is ever present in the tree.
172            // The Empty variant is not returned by Smt::leaves, because it only returns leaves that
173            // are actually present.
174            let SmtLeaf::Single((key, commitment)) = leaf else {
175                unreachable!("empty and multiple variant should never be encountered")
176            };
177
178            (
179                // SAFETY: By construction, the tree only contains valid IDs.
180                AccountIdKey::try_from_word(key)
181                    .expect("account tree should only contain valid IDs"),
182                commitment,
183            )
184        })
185    }
186}
187
188impl<S> AccountTree<S>
189where
190    S: SmtBackend<Error = MerkleError>,
191{
192    // PUBLIC MUTATORS
193    // --------------------------------------------------------------------------------------------
194
195    /// Computes the necessary changes to insert the specified (account ID, state commitment) pairs
196    /// into this tree, allowing for validation before applying those changes.
197    ///
198    /// [`Self::apply_mutations`] can be used in order to commit these changes to the tree.
199    ///
200    /// If the `concurrent` feature of `miden-crypto` is enabled, this function uses a parallel
201    /// implementation to compute the mutations, otherwise it defaults to the sequential
202    /// implementation.
203    ///
204    /// This is a thin wrapper around [`Smt::compute_mutations`]. See its documentation for more
205    /// details.
206    ///
207    /// # Errors
208    ///
209    /// Returns an error if:
210    /// - an insertion of an account ID would violate the uniqueness of account ID prefixes in the
211    ///   tree.
212    /// - the list of provided account updates contains duplicates.
213    pub fn compute_mutations(
214        &self,
215        account_commitments: impl IntoIterator<Item = (AccountId, Word)>,
216    ) -> Result<AccountMutationSet, AccountTreeError> {
217        let mutation_set = self
218            .smt
219            .compute_mutations(Vec::from_iter(
220                account_commitments
221                    .into_iter()
222                    .map(|(id, commitment)| (AccountIdKey::from(id).as_word(), commitment)),
223            ))
224            .map_err(AccountTreeError::ComputeMutations)?;
225
226        for id_key in mutation_set.new_pairs().keys() {
227            // Check if the insertion would be valid.
228            match self.smt.get_leaf(id_key) {
229                // Inserting into an empty leaf is valid.
230                SmtLeaf::Empty(_) => (),
231                SmtLeaf::Single((existing_key, _)) => {
232                    // If the key matches the existing one, then we're updating the leaf, which is
233                    // valid. If it does not match, then we would insert a duplicate.
234                    if existing_key != *id_key {
235                        return Err(AccountTreeError::DuplicateIdPrefix {
236                            duplicate_prefix: AccountIdKey::try_from_word(*id_key)
237                                .expect("account tree should only contain valid IDs")
238                                .prefix(),
239                        });
240                    }
241                },
242                SmtLeaf::Multiple(_) => {
243                    unreachable!(
244                        "account tree should never contain duplicate ID prefixes and therefore never a multiple leaf"
245                    )
246                },
247            }
248        }
249
250        Ok(AccountMutationSet::new(mutation_set))
251    }
252
253    /// Inserts the state commitment for the given account ID, returning the previous state
254    /// commitment associated with that ID.
255    ///
256    /// This also recomputes all hashes between the leaf (associated with the key) and the root,
257    /// updating the root itself.
258    ///
259    /// # Errors
260    ///
261    /// Returns an error if:
262    /// - the prefix of the account ID already exists in the tree.
263    pub fn insert(
264        &mut self,
265        account_id: AccountId,
266        state_commitment: Word,
267    ) -> Result<Word, AccountTreeError> {
268        let key = AccountIdKey::from(account_id).as_word();
269        // SAFETY: account tree should not contain multi-entry leaves and so the maximum number
270        // of entries per leaf should never be exceeded.
271        let prev_value = self.smt.insert(key, state_commitment)
272            .expect("account tree should always have a single value per key, and hence cannot exceed the maximum leaf number");
273
274        // If the leaf of the account ID now has two or more entries, we've inserted a duplicate
275        // prefix.
276        if self.smt.get_leaf(&key).num_entries() >= 2 {
277            return Err(AccountTreeError::DuplicateIdPrefix {
278                duplicate_prefix: account_id.prefix(),
279            });
280        }
281
282        Ok(prev_value)
283    }
284
285    /// Applies the prospective mutations computed with [`Self::compute_mutations`] to this tree.
286    ///
287    /// # Errors
288    ///
289    /// Returns an error if:
290    /// - `mutations` was computed on a tree with a different root than this one.
291    pub fn apply_mutations(
292        &mut self,
293        mutations: AccountMutationSet,
294    ) -> Result<(), AccountTreeError> {
295        self.smt
296            .apply_mutations(mutations.into_mutation_set())
297            .map_err(AccountTreeError::ApplyMutations)
298    }
299
300    /// Applies the prospective mutations computed with [`Self::compute_mutations`] to this tree
301    /// and returns the reverse mutation set.
302    ///
303    /// Applying the reverse mutation sets to the updated tree will revert the changes.
304    ///
305    /// # Errors
306    ///
307    /// Returns an error if:
308    /// - `mutations` was computed on a tree with a different root than this one.
309    pub fn apply_mutations_with_reversion(
310        &mut self,
311        mutations: AccountMutationSet,
312    ) -> Result<AccountMutationSet, AccountTreeError> {
313        let reversion = self
314            .smt
315            .apply_mutations_with_reversion(mutations.into_mutation_set())
316            .map_err(AccountTreeError::ApplyMutations)?;
317        Ok(AccountMutationSet::new(reversion))
318    }
319}
320
321impl AccountTree<Smt> {
322    /// Creates a new [`AccountTree`] with the provided entries.
323    ///
324    /// This is a convenience method for testing that creates an SMT backend with the provided
325    /// entries and wraps it in an AccountTree. It validates that the entries don't contain
326    /// duplicate prefixes.
327    ///
328    /// # Errors
329    ///
330    /// Returns an error if:
331    /// - The provided entries contain duplicate account ID prefixes
332    /// - The backend fails to create the SMT with the entries
333    pub fn with_entries<I>(
334        entries: impl IntoIterator<Item = (AccountId, Word), IntoIter = I>,
335    ) -> Result<Self, AccountTreeError>
336    where
337        I: ExactSizeIterator<Item = (AccountId, Word)>,
338    {
339        // Create the SMT with the entries
340        let smt = Smt::with_entries(
341            entries
342                .into_iter()
343                .map(|(id, commitment)| (AccountIdKey::from(id).as_word(), commitment)),
344        )
345        .map_err(duplicate_state_commitment_error)?;
346
347        AccountTree::new(smt)
348    }
349}
350
351#[cfg(feature = "std")]
352impl<Backend> AccountTree<LargeSmt<Backend>>
353where
354    Backend: SmtStorage,
355{
356    /// Creates a new account tree from the provided entries using the given storage backend.
357    ///
358    /// This is a convenience method that creates an SMT on the provided storage backend using the
359    /// provided entries and wraps it in an AccountTree.
360    ///
361    /// # Errors
362    ///
363    /// Returns an error if the provided entries contain duplicate account ID prefixes or duplicate
364    /// state commitments for the same account ID.
365    ///
366    /// # Panics
367    ///
368    /// Panics if a storage error is encountered.
369    pub fn with_storage_from_entries(
370        storage: Backend,
371        entries: impl IntoIterator<Item = (AccountId, Word)>,
372    ) -> Result<Self, AccountTreeError> {
373        use crate::block::smt_backend::large_smt_error_to_merkle_error;
374
375        let leaves = entries
376            .into_iter()
377            .map(|(id, commitment)| (AccountIdKey::from(id).as_word(), commitment));
378
379        let smt = LargeSmt::<Backend>::with_entries(storage, leaves)
380            .map_err(large_smt_error_to_merkle_error)
381            .map_err(duplicate_state_commitment_error)?;
382
383        AccountTree::new(smt)
384    }
385
386    /// Returns a read-only account tree backed by a reader view of this tree's storage.
387    pub fn reader(&self) -> Result<AccountTree<LargeSmt<Backend::Reader>>, LargeSmtError> {
388        Ok(AccountTree::new_unchecked(self.smt.reader()?))
389    }
390}
391
392// HELPER FUNCTIONS
393// ================================================================================================
394
395/// Maps the duplicate-key error returned by the SMT constructors to an [`AccountTreeError`].
396fn duplicate_state_commitment_error(err: MerkleError) -> AccountTreeError {
397    let MerkleError::DuplicateValuesForIndex(leaf_idx) = err else {
398        unreachable!("the only error returned by the SMT constructors is a duplicate-value error");
399    };
400
401    // SAFETY: Since we only inserted account IDs into the SMT, it is guaranteed that the leaf_idx
402    // is a valid Felt as well as a valid account ID prefix.
403    AccountTreeError::DuplicateStateCommitments {
404        prefix: AccountIdPrefix::new_unchecked(
405            crate::Felt::try_from(leaf_idx).expect("leaf index should be a valid felt"),
406        ),
407    }
408}
409
410// SERIALIZATION
411// ================================================================================================
412
413impl Serializable for AccountTree {
414    fn write_into<W: ByteWriter>(&self, target: &mut W) {
415        self.account_commitments().collect::<Vec<_>>().write_into(target);
416    }
417}
418
419impl Deserializable for AccountTree {
420    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
421        let entries = Vec::<(AccountId, Word)>::read_from(source)?;
422
423        // Validate uniqueness of account ID prefixes before creating the tree
424        let mut seen_prefixes = alloc::collections::BTreeSet::new();
425        for (id, _) in &entries {
426            if !seen_prefixes.insert(id.prefix()) {
427                return Err(DeserializationError::InvalidValue(format!(
428                    "Duplicate account ID prefix: {}",
429                    id.prefix()
430                )));
431            }
432        }
433
434        // Create the SMT with validated entries
435        let smt = Smt::with_entries(
436            entries.into_iter().map(|(k, v)| (AccountIdKey::from(k).as_word(), v)),
437        )
438        .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
439        Ok(Self::new_unchecked(smt))
440    }
441}
442
443// ACCOUNT MUTATION SET
444// ================================================================================================
445
446/// A newtype wrapper around a [`MutationSet`] for use in the [`AccountTree`].
447///
448/// It guarantees that applying the contained mutations will result in an account tree with unique
449/// account ID prefixes.
450///
451/// It is returned by and used in methods on the [`AccountTree`].
452#[derive(Debug, Clone, PartialEq, Eq)]
453pub struct AccountMutationSet {
454    mutation_set: MutationSet<SMT_DEPTH, Word, Word>,
455}
456
457impl AccountMutationSet {
458    // CONSTRUCTORS
459    // --------------------------------------------------------------------------------------------
460
461    /// Creates a new [`AccountMutationSet`] from the provided raw mutation set.
462    fn new(mutation_set: MutationSet<SMT_DEPTH, Word, Word>) -> Self {
463        Self { mutation_set }
464    }
465
466    // PUBLIC ACCESSORS
467    // --------------------------------------------------------------------------------------------
468
469    /// Returns a reference to the underlying [`MutationSet`].
470    pub fn as_mutation_set(&self) -> &MutationSet<SMT_DEPTH, Word, Word> {
471        &self.mutation_set
472    }
473
474    // PUBLIC MUTATORS
475    // --------------------------------------------------------------------------------------------
476
477    /// Consumes self and returns the underlying [`MutationSet`].
478    pub fn into_mutation_set(self) -> MutationSet<SMT_DEPTH, Word, Word> {
479        self.mutation_set
480    }
481}
482
483// TESTS
484// ================================================================================================
485
486#[cfg(test)]
487pub(super) mod tests {
488    use std::vec::Vec;
489
490    use assert_matches::assert_matches;
491
492    use super::*;
493    use crate::account::{AccountType, AssetCallbackFlag};
494    use crate::testing::account_id::{AccountIdBuilder, account_id};
495
496    pub(crate) fn setup_duplicate_prefix_ids() -> [(AccountId, Word); 2] {
497        let id0 = AccountId::try_from(account_id(
498            AccountType::Public,
499            AssetCallbackFlag::Disabled,
500            0xaabb_ccdd,
501        ))
502        .unwrap();
503        let id1 = AccountId::try_from(account_id(
504            AccountType::Public,
505            AssetCallbackFlag::Disabled,
506            0xaabb_ccff,
507        ))
508        .unwrap();
509        assert_eq!(id0.prefix(), id1.prefix(), "test requires that these ids have the same prefix");
510
511        let commitment0 = Word::from([0, 0, 0, 42u32]);
512        let commitment1 = Word::from([0, 0, 0, 24u32]);
513
514        assert_eq!(id0.prefix(), id1.prefix(), "test requires that these ids have the same prefix");
515        [(id0, commitment0), (id1, commitment1)]
516    }
517
518    #[test]
519    fn insert_fails_on_duplicate_prefix() {
520        let mut tree = AccountTree::<Smt>::default();
521        let [(id0, commitment0), (id1, commitment1)] = setup_duplicate_prefix_ids();
522
523        tree.insert(id0, commitment0).unwrap();
524        assert_eq!(tree.get(id0), commitment0);
525
526        let err = tree.insert(id1, commitment1).unwrap_err();
527
528        assert_matches!(err, AccountTreeError::DuplicateIdPrefix {
529          duplicate_prefix
530        } if duplicate_prefix == id0.prefix());
531    }
532
533    #[test]
534    fn insert_succeeds_on_multiple_updates() {
535        let mut tree = AccountTree::<Smt>::default();
536        let [(id0, commitment0), (_, commitment1)] = setup_duplicate_prefix_ids();
537
538        tree.insert(id0, commitment0).unwrap();
539        tree.insert(id0, commitment1).unwrap();
540        assert_eq!(tree.get(id0), commitment1);
541    }
542
543    #[test]
544    fn apply_mutations() {
545        let id0 = AccountIdBuilder::new().build_with_seed([5; 32]);
546        let id1 = AccountIdBuilder::new().build_with_seed([6; 32]);
547        let id2 = AccountIdBuilder::new().build_with_seed([7; 32]);
548
549        let digest0 = Word::from([0, 0, 0, 1u32]);
550        let digest1 = Word::from([0, 0, 0, 2u32]);
551        let digest2 = Word::from([0, 0, 0, 3u32]);
552        let digest3 = Word::from([0, 0, 0, 4u32]);
553
554        let mut tree = AccountTree::with_entries([(id0, digest0), (id1, digest1)]).unwrap();
555
556        let mutations = tree
557            .compute_mutations([(id0, digest1), (id1, digest2), (id2, digest3)])
558            .unwrap();
559
560        tree.apply_mutations(mutations).unwrap();
561
562        assert_eq!(tree.num_accounts(), 3);
563        assert_eq!(tree.get(id0), digest1);
564        assert_eq!(tree.get(id1), digest2);
565        assert_eq!(tree.get(id2), digest3);
566    }
567
568    #[test]
569    fn duplicates_in_compute_mutations() {
570        let [pair0, pair1] = setup_duplicate_prefix_ids();
571        let id2 = AccountIdBuilder::new().build_with_seed([5; 32]);
572        let commitment2 = Word::from([0, 0, 0, 99u32]);
573
574        let tree = AccountTree::with_entries([pair0, (id2, commitment2)]).unwrap();
575        let err = tree.compute_mutations([pair1]).unwrap_err();
576
577        assert_matches!(err, AccountTreeError::DuplicateIdPrefix {
578          duplicate_prefix
579        } if duplicate_prefix == pair1.0.prefix());
580    }
581
582    #[test]
583    fn account_commitments() {
584        let id0 = AccountIdBuilder::new().build_with_seed([5; 32]);
585        let id1 = AccountIdBuilder::new().build_with_seed([6; 32]);
586        let id2 = AccountIdBuilder::new().build_with_seed([7; 32]);
587
588        let digest0 = Word::from([0, 0, 0, 1u32]);
589        let digest1 = Word::from([0, 0, 0, 2u32]);
590        let digest2 = Word::from([0, 0, 0, 3u32]);
591        let empty_digest = Word::empty();
592
593        let mut tree =
594            AccountTree::with_entries([(id0, digest0), (id1, digest1), (id2, digest2)]).unwrap();
595
596        // remove id2
597        tree.insert(id2, empty_digest).unwrap();
598
599        assert_eq!(tree.num_accounts(), 2);
600
601        let accounts: Vec<_> = tree.account_commitments().collect();
602        assert_eq!(accounts.len(), 2);
603        assert!(accounts.contains(&(id0, digest0)));
604        assert!(accounts.contains(&(id1, digest1)));
605    }
606
607    #[test]
608    fn account_witness() {
609        let id0 = AccountIdBuilder::new().build_with_seed([5; 32]);
610        let id1 = AccountIdBuilder::new().build_with_seed([6; 32]);
611
612        let digest0 = Word::from([0, 0, 0, 1u32]);
613        let digest1 = Word::from([0, 0, 0, 2u32]);
614
615        let tree = AccountTree::with_entries([(id0, digest0), (id1, digest1)]).unwrap();
616
617        assert_eq!(tree.num_accounts(), 2);
618
619        for id in [id0, id1] {
620            let proof = tree.smt.open(&AccountIdKey::from(id).as_word());
621            let (control_path, control_leaf) = proof.into_parts();
622            let witness = tree.open(id);
623
624            assert_eq!(witness.leaf(), control_leaf);
625            assert_eq!(witness.path(), &control_path);
626        }
627    }
628
629    #[test]
630    fn contains_account_prefix() {
631        // Create a tree with a single account.
632        let [pair0, pair1] = setup_duplicate_prefix_ids();
633        let tree = AccountTree::with_entries([pair0]).unwrap();
634        assert_eq!(tree.num_accounts(), 1);
635
636        // Validate the leaf for the inserted account exists.
637        assert!(tree.contains_account_id_prefix(pair0.0.prefix()));
638
639        // Validate the leaf for the uninserted account with same prefix exists.
640        assert!(tree.contains_account_id_prefix(pair1.0.prefix()));
641
642        // Validate the unrelated, uninserted account leaf does not exist.
643        let id1 = AccountIdBuilder::new().build_with_seed([7; 32]);
644        assert!(!tree.contains_account_id_prefix(id1.prefix()));
645    }
646
647    #[cfg(feature = "std")]
648    #[test]
649    fn large_smt_backend_basic_operations() {
650        use miden_crypto::merkle::smt::{LargeSmt, MemoryStorage};
651
652        // Create test data
653        let id0 = AccountIdBuilder::new().build_with_seed([5; 32]);
654        let id1 = AccountIdBuilder::new().build_with_seed([6; 32]);
655        let id2 = AccountIdBuilder::new().build_with_seed([7; 32]);
656
657        let digest0 = Word::from([0, 0, 0, 1u32]);
658        let digest1 = Word::from([0, 0, 0, 2u32]);
659        let digest2 = Word::from([0, 0, 0, 3u32]);
660
661        // Create AccountTree with LargeSmt backend
662        let tree = LargeSmt::<MemoryStorage>::with_entries(
663            MemoryStorage::default(),
664            [
665                (AccountIdKey::from(id0).as_word(), digest0),
666                (AccountIdKey::from(id1).as_word(), digest1),
667            ],
668        )
669        .map(AccountTree::new_unchecked)
670        .unwrap();
671
672        // Test basic operations
673        assert_eq!(tree.num_accounts(), 2);
674        assert_eq!(tree.get(id0), digest0);
675        assert_eq!(tree.get(id1), digest1);
676
677        // Test opening
678        let witness0 = tree.open(id0);
679        assert_eq!(witness0.id(), id0);
680
681        // Test mutations
682        let mut tree_mut = LargeSmt::<MemoryStorage>::with_entries(
683            MemoryStorage::default(),
684            [
685                (AccountIdKey::from(id0).as_word(), digest0),
686                (AccountIdKey::from(id1).as_word(), digest1),
687            ],
688        )
689        .map(AccountTree::new_unchecked)
690        .unwrap();
691        tree_mut.insert(id2, digest2).unwrap();
692        assert_eq!(tree_mut.num_accounts(), 3);
693        assert_eq!(tree_mut.get(id2), digest2);
694
695        // Verify original tree unchanged
696        assert_eq!(tree.num_accounts(), 2);
697    }
698
699    #[cfg(feature = "std")]
700    #[test]
701    fn large_smt_backend_duplicate_prefix_check() {
702        use miden_crypto::merkle::smt::{LargeSmt, MemoryStorage};
703
704        let [(id0, commitment0), (id1, commitment1)] = setup_duplicate_prefix_ids();
705
706        let mut tree = AccountTree::new_unchecked(LargeSmt::new(MemoryStorage::default()).unwrap());
707
708        tree.insert(id0, commitment0).unwrap();
709        assert_eq!(tree.get(id0), commitment0);
710
711        let err = tree.insert(id1, commitment1).unwrap_err();
712
713        assert_matches!(
714            err,
715            AccountTreeError::DuplicateIdPrefix { duplicate_prefix }
716            if duplicate_prefix == id0.prefix()
717        );
718    }
719
720    #[cfg(feature = "std")]
721    #[test]
722    fn large_smt_backend_apply_mutations() {
723        use miden_crypto::merkle::smt::{LargeSmt, MemoryStorage};
724
725        let id0 = AccountIdBuilder::new().build_with_seed([5; 32]);
726        let id1 = AccountIdBuilder::new().build_with_seed([6; 32]);
727        let id2 = AccountIdBuilder::new().build_with_seed([7; 32]);
728
729        let digest0 = Word::from([0, 0, 0, 1u32]);
730        let digest1 = Word::from([0, 0, 0, 2u32]);
731        let digest2 = Word::from([0, 0, 0, 3u32]);
732        let digest3 = Word::from([0, 0, 0, 4u32]);
733
734        let mut tree = LargeSmt::with_entries(
735            MemoryStorage::default(),
736            [
737                (AccountIdKey::from(id0).as_word(), digest0),
738                (AccountIdKey::from(id1).as_word(), digest1),
739            ],
740        )
741        .map(AccountTree::new_unchecked)
742        .unwrap();
743
744        let mutations = tree
745            .compute_mutations([(id0, digest1), (id1, digest2), (id2, digest3)])
746            .unwrap();
747
748        tree.apply_mutations(mutations).unwrap();
749
750        assert_eq!(tree.num_accounts(), 3);
751        assert_eq!(tree.get(id0), digest1);
752        assert_eq!(tree.get(id1), digest2);
753        assert_eq!(tree.get(id2), digest3);
754    }
755
756    #[cfg(feature = "std")]
757    #[test]
758    fn large_smt_backend_same_root_as_regular_smt() {
759        use miden_crypto::merkle::smt::{LargeSmt, MemoryStorage};
760
761        let id0 = AccountIdBuilder::new().build_with_seed([5; 32]);
762        let id1 = AccountIdBuilder::new().build_with_seed([6; 32]);
763
764        let digest0 = Word::from([0, 0, 0, 1u32]);
765        let digest1 = Word::from([0, 0, 0, 2u32]);
766
767        // Create tree with LargeSmt backend
768        let large_tree = LargeSmt::with_entries(
769            MemoryStorage::default(),
770            [
771                (AccountIdKey::from(id0).as_word(), digest0),
772                (AccountIdKey::from(id1).as_word(), digest1),
773            ],
774        )
775        .map(AccountTree::new_unchecked)
776        .unwrap();
777
778        // Create tree with regular Smt backend
779        let regular_tree = AccountTree::with_entries([(id0, digest0), (id1, digest1)]).unwrap();
780
781        // Both should have the same root
782        assert_eq!(large_tree.root(), regular_tree.root());
783
784        // Both should have the same account commitments
785        let large_commitments: std::collections::BTreeMap<_, _> =
786            large_tree.account_commitments().collect();
787        let regular_commitments: std::collections::BTreeMap<_, _> =
788            regular_tree.account_commitments().collect();
789
790        assert_eq!(large_commitments, regular_commitments);
791    }
792
793    #[cfg(feature = "std")]
794    #[test]
795    fn with_storage_from_entries_rejects_duplicate_state_commitments() {
796        use miden_crypto::merkle::smt::MemoryStorage;
797
798        let id = AccountIdBuilder::new().build_with_seed([5; 32]);
799        let commitment0 = Word::from([0, 0, 0, 1u32]);
800        let commitment1 = Word::from([0, 0, 0, 2u32]);
801
802        // The same account ID appears twice, which must surface the same structured error as the
803        // `Smt`-backed `with_entries` path.
804        let err = AccountTree::with_storage_from_entries(
805            MemoryStorage::default(),
806            [(id, commitment0), (id, commitment1)],
807        )
808        .unwrap_err();
809
810        assert_matches!(
811            err,
812            AccountTreeError::DuplicateStateCommitments { prefix } if prefix == id.prefix()
813        );
814    }
815}