miden_objects/block/
account_tree.rs

1use miden_crypto::merkle::{MerkleError, MutationSet, Smt, SmtLeaf};
2use vm_processor::SMT_DEPTH;
3
4use crate::{
5    Digest, Felt, FieldElement, Word,
6    account::{AccountId, AccountIdPrefix},
7    block::AccountWitness,
8    errors::AccountTreeError,
9};
10
11// ACCOUNT TREE
12// ================================================================================================
13
14/// The sparse merkle tree of all accounts in the blockchain.
15///
16/// The key is the [`AccountId`] while the value is the current state commitment of the account,
17/// i.e. [`Account::commitment`](crate::account::Account::commitment). If the account is new, then
18/// the commitment is the [`EMPTY_WORD`](crate::EMPTY_WORD).
19///
20/// Each account ID occupies exactly one leaf in the tree, which is identified by its
21/// [`AccountId::prefix`]. In other words, account ID prefixes are unique in the blockchain.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct AccountTree {
24    smt: Smt,
25}
26
27impl AccountTree {
28    // CONSTANTS
29    // --------------------------------------------------------------------------------------------
30
31    /// The depth of the account tree.
32    pub const DEPTH: u8 = SMT_DEPTH;
33
34    /// The index of the account ID suffix in the SMT key.
35    pub(super) const KEY_SUFFIX_IDX: usize = 2;
36    /// The index of the account ID prefix in the SMT key.
37    pub(super) const KEY_PREFIX_IDX: usize = 3;
38
39    // CONSTRUCTORS
40    // --------------------------------------------------------------------------------------------
41
42    /// Creates a new, empty account tree.
43    pub fn new() -> Self {
44        AccountTree { smt: Smt::new() }
45    }
46
47    /// Returns a new [`Smt`] instantiated with the provided entries.
48    ///
49    /// If the `concurrent` feature of `miden-crypto` is enabled, this function uses a parallel
50    /// implementation to process the entries efficiently, otherwise it defaults to the
51    /// sequential implementation.
52    ///
53    /// # Errors
54    ///
55    /// Returns an error if:
56    /// - the provided entries contain multiple commitments for the same account ID.
57    /// - multiple account IDs share the same prefix.
58    pub fn with_entries<I>(
59        entries: impl IntoIterator<Item = (AccountId, Digest), IntoIter = I>,
60    ) -> Result<Self, AccountTreeError>
61    where
62        I: ExactSizeIterator<Item = (AccountId, Digest)>,
63    {
64        let entries = entries.into_iter();
65        let num_accounts = entries.len();
66
67        let smt = Smt::with_entries(
68            entries.map(|(id, commitment)| (Self::id_to_smt_key(id), Word::from(commitment))),
69        )
70        .map_err(|err| {
71            let MerkleError::DuplicateValuesForIndex(leaf_idx) = err else {
72                unreachable!("the only error returned by Smt::with_entries is of this type");
73            };
74
75            // SAFETY: Since we only inserted account IDs into the SMT, it is guaranteed that
76            // the leaf_idx is a valid Felt as well as a valid account ID prefix.
77            AccountTreeError::DuplicateStateCommitments {
78                prefix: AccountIdPrefix::new_unchecked(
79                    Felt::try_from(leaf_idx).expect("leaf index should be a valid felt"),
80                ),
81            }
82        })?;
83
84        // If the number of leaves in the SMT is smaller than the number of accounts that were
85        // passed in, it means that at least one account ID pair ended up in the same leaf. If this
86        // is the case, we iterate the SMT entries to find the duplicated account ID prefix.
87        if smt.num_leaves() < num_accounts {
88            for (leaf_idx, leaf) in smt.leaves() {
89                if leaf.num_entries() >= 2 {
90                    // SAFETY: Since we only inserted account IDs into the SMT, it is guaranteed
91                    // that the leaf_idx is a valid Felt as well as a valid
92                    // account ID prefix.
93                    return Err(AccountTreeError::DuplicateIdPrefix {
94                        duplicate_prefix: AccountIdPrefix::new_unchecked(
95                            Felt::try_from(leaf_idx.value())
96                                .expect("leaf index should be a valid felt"),
97                        ),
98                    });
99                }
100            }
101        }
102
103        Ok(AccountTree { smt })
104    }
105
106    // PUBLIC ACCESSORS
107    // --------------------------------------------------------------------------------------------
108
109    /// Returns an opening of the leaf associated with the `account_id`. This is a proof of the
110    /// current state commitment of the given account ID.
111    ///
112    /// Conceptually, an opening is a Merkle path to the leaf, as well as the leaf itself.
113    pub fn open(&self, account_id: AccountId) -> AccountWitness {
114        let key = Self::id_to_smt_key(account_id);
115        let proof = self.smt.open(&key);
116
117        AccountWitness::from_smt_proof(account_id, proof)
118    }
119
120    /// Returns the current state commitment of the given account ID.
121    pub fn get(&self, account_id: AccountId) -> Digest {
122        let key = Self::id_to_smt_key(account_id);
123        Digest::from(self.smt.get_value(&key))
124    }
125
126    /// Returns the root of the tree.
127    pub fn root(&self) -> Digest {
128        self.smt.root()
129    }
130
131    /// Returns the number of account IDs in this tree.
132    pub fn num_accounts(&self) -> usize {
133        // Because each ID's prefix is unique in the tree and occupies a single leaf, the number of
134        // IDs in the tree is equivalent to the number of leaves in the tree.
135        self.smt.num_leaves()
136    }
137
138    /// Returns an iterator over the account ID state commitment pairs in the tree.
139    pub fn account_commitments(&self) -> impl Iterator<Item = (AccountId, Digest)> {
140        self.smt.leaves().map(|(_leaf_idx, leaf)| {
141            // SAFETY: By construction no Multiple variant is ever present in the tree.
142            // The Empty variant is not returned by Smt::leaves, because it only returns leaves that
143            // are actually present.
144            let SmtLeaf::Single((key, commitment)) = leaf else {
145                unreachable!("empty and multiple variant should never be encountered")
146            };
147
148            (
149                // SAFETY: By construction, the tree only contains valid IDs.
150                AccountId::try_from([key[Self::KEY_PREFIX_IDX], key[Self::KEY_SUFFIX_IDX]])
151                    .expect("account tree should only contain valid IDs"),
152                Digest::from(commitment),
153            )
154        })
155    }
156
157    /// Computes the necessary changes to insert the specified (account ID, state commitment) pairs
158    /// into this tree, allowing for validation before applying those changes.
159    ///
160    /// [`Self::apply_mutations`] can be used in order to commit these changes to the tree.
161    ///
162    /// If the `concurrent` feature of `miden-crypto` is enabled, this function uses a parallel
163    /// implementation to compute the mutations, otherwise it defaults to the sequential
164    /// implementation.
165    ///
166    /// This is a thin wrapper around [`Smt::compute_mutations`]. See its documentation for more
167    /// details.
168    ///
169    /// # Errors
170    ///
171    /// Returns an error if:
172    /// - an insertion of an account ID would violate the uniqueness of account ID prefixes in the
173    ///   tree.
174    pub fn compute_mutations(
175        &self,
176        account_commitments: impl IntoIterator<Item = (AccountId, Digest)>,
177    ) -> Result<AccountMutationSet, AccountTreeError> {
178        let mutation_set = self.smt.compute_mutations(
179            account_commitments
180                .into_iter()
181                .map(|(id, commitment)| (Self::id_to_smt_key(id), Word::from(commitment))),
182        );
183
184        for id_key in mutation_set.new_pairs().keys() {
185            // Check if the insertion would be valid.
186            match self.smt.get_leaf(id_key) {
187                // Inserting into an empty leaf is valid.
188                SmtLeaf::Empty(_) => (),
189                SmtLeaf::Single((existing_key, _)) => {
190                    // If the key matches the existing one, then we're updating the leaf, which is
191                    // valid. If it does not match, then we would insert a duplicate.
192                    if existing_key != *id_key {
193                        return Err(AccountTreeError::DuplicateIdPrefix {
194                            duplicate_prefix: Self::smt_key_to_id(*id_key).prefix(),
195                        });
196                    }
197                },
198                SmtLeaf::Multiple(_) => {
199                    unreachable!(
200                        "account tree should never contain duplicate ID prefixes and therefore never a multiple leaf"
201                    )
202                },
203            }
204        }
205
206        Ok(AccountMutationSet::new(mutation_set))
207    }
208
209    // PUBLIC MUTATORS
210    // --------------------------------------------------------------------------------------------
211
212    /// Inserts the state commitment for the given account ID, returning the previous state
213    /// commitment associated with that ID.
214    ///
215    /// This also recomputes all hashes between the leaf (associated with the key) and the root,
216    /// updating the root itself.
217    ///
218    /// # Errors
219    ///
220    /// Returns an error if:
221    /// - the prefix of the account ID already exists in the tree.
222    pub fn insert(
223        &mut self,
224        account_id: AccountId,
225        state_commitment: Digest,
226    ) -> Result<Digest, AccountTreeError> {
227        let key = Self::id_to_smt_key(account_id);
228        let prev_value = Digest::from(self.smt.insert(key, Word::from(state_commitment)));
229
230        // If the leaf of the account ID now has two or more entries, we've inserted a duplicate
231        // prefix.
232        if self.smt.get_leaf(&key).num_entries() >= 2 {
233            return Err(AccountTreeError::DuplicateIdPrefix {
234                duplicate_prefix: account_id.prefix(),
235            });
236        }
237
238        Ok(prev_value)
239    }
240
241    /// Applies the prospective mutations computed with [`Self::compute_mutations`] to this tree.
242    ///
243    /// # Errors
244    ///
245    /// Returns an error if:
246    /// - `mutations` was computed on a tree with a different root than this one.
247    pub fn apply_mutations(
248        &mut self,
249        mutations: AccountMutationSet,
250    ) -> Result<(), AccountTreeError> {
251        self.smt
252            .apply_mutations(mutations.into_mutation_set())
253            .map_err(AccountTreeError::ApplyMutations)
254    }
255
256    // HELPERS
257    // --------------------------------------------------------------------------------------------
258
259    /// Returns the SMT key of the given account ID.
260    pub(super) fn id_to_smt_key(account_id: AccountId) -> Digest {
261        // We construct this in such a way that we're forced to use the constants, so that when
262        // they're updated, the other usages of the constants are also updated.
263        let mut key = [Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ZERO];
264        key[Self::KEY_SUFFIX_IDX] = account_id.suffix();
265        key[Self::KEY_PREFIX_IDX] = account_id.prefix().as_felt();
266
267        Digest::from(key)
268    }
269
270    /// Returns the [`AccountId`] recovered from the given SMT key.
271    ///
272    /// # Panics
273    ///
274    /// Panics if:
275    /// - the key is not a valid account ID. This should not happen when used on keys from (partial)
276    ///   account tree.
277    pub(super) fn smt_key_to_id(key: Digest) -> AccountId {
278        AccountId::try_from([key[Self::KEY_PREFIX_IDX], key[Self::KEY_SUFFIX_IDX]])
279            .expect("account tree should only contain valid IDs")
280    }
281}
282
283impl Default for AccountTree {
284    fn default() -> Self {
285        Self::new()
286    }
287}
288
289// ACCOUNT MUTATION SET
290// ================================================================================================
291
292/// A newtype wrapper around a [`MutationSet`] for use in the [`AccountTree`].
293///
294/// It guarantees that applying the contained mutations will result in an account tree with unique
295/// account ID prefixes.
296///
297/// It is returned by and used in methods on the [`AccountTree`].
298#[derive(Debug, Clone, PartialEq, Eq)]
299pub struct AccountMutationSet {
300    mutation_set: MutationSet<{ AccountTree::DEPTH }, Digest, Word>,
301}
302
303impl AccountMutationSet {
304    // CONSTRUCTORS
305    // --------------------------------------------------------------------------------------------
306
307    /// Creates a new [`AccountMutationSet`] from the provided raw mutation set.
308    fn new(mutation_set: MutationSet<{ AccountTree::DEPTH }, Digest, Word>) -> Self {
309        Self { mutation_set }
310    }
311
312    // PUBLIC ACCESSORS
313    // --------------------------------------------------------------------------------------------
314
315    /// Returns a reference to the underlying [`MutationSet`].
316    pub fn as_mutation_set(&self) -> &MutationSet<{ AccountTree::DEPTH }, Digest, Word> {
317        &self.mutation_set
318    }
319
320    // PUBLIC MUTATORS
321    // --------------------------------------------------------------------------------------------
322
323    /// Consumes self and returns the underlying [`MutationSet`].
324    pub fn into_mutation_set(self) -> MutationSet<{ AccountTree::DEPTH }, Digest, Word> {
325        self.mutation_set
326    }
327}
328
329// TESTS
330// ================================================================================================
331
332#[cfg(test)]
333pub(super) mod tests {
334    use std::vec::Vec;
335
336    use assert_matches::assert_matches;
337    use vm_core::EMPTY_WORD;
338
339    use super::*;
340    use crate::{
341        account::{AccountStorageMode, AccountType},
342        testing::account_id::{AccountIdBuilder, account_id},
343    };
344
345    pub(crate) fn setup_duplicate_prefix_ids() -> [(AccountId, Digest); 2] {
346        let id0 = AccountId::try_from(account_id(
347            AccountType::FungibleFaucet,
348            AccountStorageMode::Public,
349            0xaabb_ccdd,
350        ))
351        .unwrap();
352        let id1 = AccountId::try_from(account_id(
353            AccountType::FungibleFaucet,
354            AccountStorageMode::Public,
355            0xaabb_ccff,
356        ))
357        .unwrap();
358        assert_eq!(id0.prefix(), id1.prefix(), "test requires that these ids have the same prefix");
359
360        let commitment0 = Digest::from([Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::new(42)]);
361        let commitment1 = Digest::from([Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::new(24)]);
362
363        assert_eq!(id0.prefix(), id1.prefix(), "test requires that these ids have the same prefix");
364        [(id0, commitment0), (id1, commitment1)]
365    }
366
367    #[test]
368    fn insert_fails_on_duplicate_prefix() {
369        let mut tree = AccountTree::new();
370        let [(id0, commitment0), (id1, commitment1)] = setup_duplicate_prefix_ids();
371
372        tree.insert(id0, commitment0).unwrap();
373        assert_eq!(tree.get(id0), commitment0);
374
375        let err = tree.insert(id1, commitment1).unwrap_err();
376
377        assert_matches!(err, AccountTreeError::DuplicateIdPrefix {
378          duplicate_prefix
379        } if duplicate_prefix == id0.prefix());
380    }
381
382    #[test]
383    fn with_entries_fails_on_duplicate_prefix() {
384        let entries = setup_duplicate_prefix_ids();
385
386        let err = AccountTree::with_entries(entries.iter().copied()).unwrap_err();
387
388        assert_matches!(err, AccountTreeError::DuplicateIdPrefix {
389          duplicate_prefix
390        } if duplicate_prefix == entries[0].0.prefix());
391    }
392
393    #[test]
394    fn insert_succeeds_on_multiple_updates() {
395        let mut tree = AccountTree::new();
396        let [(id0, commitment0), (_, commitment1)] = setup_duplicate_prefix_ids();
397
398        tree.insert(id0, commitment0).unwrap();
399        tree.insert(id0, commitment1).unwrap();
400        assert_eq!(tree.get(id0), commitment1);
401    }
402
403    #[test]
404    fn apply_mutations() {
405        let id0 = AccountIdBuilder::new().build_with_seed([5; 32]);
406        let id1 = AccountIdBuilder::new().build_with_seed([6; 32]);
407        let id2 = AccountIdBuilder::new().build_with_seed([7; 32]);
408
409        let digest0 = Digest::from([0, 0, 0, 1u32]);
410        let digest1 = Digest::from([0, 0, 0, 2u32]);
411        let digest2 = Digest::from([0, 0, 0, 3u32]);
412        let digest3 = Digest::from([0, 0, 0, 4u32]);
413
414        let mut tree = AccountTree::with_entries([(id0, digest0), (id1, digest1)]).unwrap();
415
416        let mutations = tree
417            .compute_mutations([(id0, digest1), (id1, digest2), (id2, digest3)])
418            .unwrap();
419
420        tree.apply_mutations(mutations).unwrap();
421
422        assert_eq!(tree.num_accounts(), 3);
423        assert_eq!(tree.get(id0), digest1);
424        assert_eq!(tree.get(id1), digest2);
425        assert_eq!(tree.get(id2), digest3);
426    }
427
428    #[test]
429    fn duplicates_in_compute_mutations() {
430        let [pair0, pair1] = setup_duplicate_prefix_ids();
431        let id2 = AccountIdBuilder::new().build_with_seed([5; 32]);
432        let commitment2 = Digest::from([0, 0, 0, 99u32]);
433
434        let tree = AccountTree::with_entries([pair0, (id2, commitment2)]).unwrap();
435
436        let err = tree.compute_mutations([pair1]).unwrap_err();
437
438        assert_matches!(err, AccountTreeError::DuplicateIdPrefix {
439          duplicate_prefix
440        } if duplicate_prefix == pair1.0.prefix());
441    }
442
443    #[test]
444    fn account_commitments() {
445        let id0 = AccountIdBuilder::new().build_with_seed([5; 32]);
446        let id1 = AccountIdBuilder::new().build_with_seed([6; 32]);
447        let id2 = AccountIdBuilder::new().build_with_seed([7; 32]);
448
449        let digest0 = Digest::from([0, 0, 0, 1u32]);
450        let digest1 = Digest::from([0, 0, 0, 2u32]);
451        let digest2 = Digest::from([0, 0, 0, 3u32]);
452        let empty_digest = Digest::from(EMPTY_WORD);
453
454        let mut tree =
455            AccountTree::with_entries([(id0, digest0), (id1, digest1), (id2, digest2)]).unwrap();
456
457        // remove id2
458        tree.insert(id2, empty_digest).unwrap();
459
460        assert_eq!(tree.num_accounts(), 2);
461
462        let accounts: Vec<_> = tree.account_commitments().collect();
463        assert_eq!(accounts.len(), 2);
464        assert!(accounts.contains(&(id0, digest0)));
465        assert!(accounts.contains(&(id1, digest1)));
466    }
467
468    #[test]
469    fn account_witness() {
470        let id0 = AccountIdBuilder::new().build_with_seed([5; 32]);
471        let id1 = AccountIdBuilder::new().build_with_seed([6; 32]);
472
473        let digest0 = Digest::from([0, 0, 0, 1u32]);
474        let digest1 = Digest::from([0, 0, 0, 2u32]);
475
476        let tree = AccountTree::with_entries([(id0, digest0), (id1, digest1)]).unwrap();
477
478        assert_eq!(tree.num_accounts(), 2);
479
480        for id in [id0, id1] {
481            let (control_path, control_leaf) =
482                tree.smt.open(&AccountTree::id_to_smt_key(id)).into_parts();
483            let witness = tree.open(id);
484
485            assert_eq!(witness.leaf(), control_leaf);
486            assert_eq!(witness.path(), &control_path);
487        }
488    }
489}