Skip to main content

miden_protocol/account/storage/
partial.rs

1use alloc::collections::{BTreeMap, BTreeSet};
2
3use miden_crypto::Word;
4use miden_crypto::merkle::InnerNodeInfo;
5use miden_crypto::merkle::smt::SmtLeaf;
6
7use super::{AccountStorage, AccountStorageHeader, StorageSlotContent};
8use crate::account::PartialStorageMap;
9use crate::errors::AccountError;
10use crate::utils::serde::{
11    ByteReader,
12    ByteWriter,
13    Deserializable,
14    DeserializationError,
15    Serializable,
16};
17
18/// A partial representation of an account storage, containing only a subset of the storage data.
19///
20/// Partial storage is used to provide verifiable access to specific segments of account storage
21/// without the need to provide the full storage data. It contains all needed parts for loading
22/// account storage data into the transaction kernel.
23#[derive(Clone, Debug, PartialEq, Eq)]
24pub struct PartialStorage {
25    /// Commitment of the account's storage slots.
26    commitment: Word,
27    /// Account storage header.
28    header: AccountStorageHeader,
29    /// Storage partial storage maps indexed by their root, containing a subset of the elements
30    /// from the complete storage map.
31    maps: BTreeMap<Word, PartialStorageMap>,
32}
33
34impl PartialStorage {
35    // CONSTRUCTORS
36    // --------------------------------------------------------------------------------------------
37
38    /// Returns a new instance of partial storage with the specified header and storage map SMTs.
39    ///
40    /// The storage commitment is computed during instantiation based on the provided header.
41    /// Additionally, this function validates that the passed SMTs correspond to one of the map
42    /// roots in the storage header.
43    pub fn new(
44        storage_header: AccountStorageHeader,
45        storage_maps: impl IntoIterator<Item = PartialStorageMap>,
46    ) -> Result<Self, AccountError> {
47        let storage_map_roots: BTreeSet<_> = storage_header.map_slot_roots().collect();
48        let mut maps = BTreeMap::new();
49        for smt in storage_maps {
50            // Check that the passed storage map partial SMT has a matching map slot root
51            if !storage_map_roots.contains(&smt.root()) {
52                return Err(AccountError::StorageMapRootNotFound(smt.root()));
53            }
54            maps.insert(smt.root(), smt);
55        }
56
57        let commitment = storage_header.to_commitment();
58        Ok(Self { commitment, header: storage_header, maps })
59    }
60
61    /// Converts an [`AccountStorage`] into a partial storage representation.
62    ///
63    /// This creates a partial storage that contains the _full_ proofs for all key-value pairs
64    /// in all map slots of the account storage.
65    pub fn new_full(account_storage: AccountStorage) -> Self {
66        let header: AccountStorageHeader = account_storage.to_header();
67        let commitment = header.to_commitment();
68
69        let mut maps = BTreeMap::new();
70        for slot in account_storage {
71            if let StorageSlotContent::Map(storage_map) = slot.into_parts().1 {
72                let partial_map = PartialStorageMap::new_full(storage_map);
73                maps.insert(partial_map.root(), partial_map);
74            }
75        }
76
77        PartialStorage { header, maps, commitment }
78    }
79
80    /// Converts an [`AccountStorage`] into a partial storage representation.
81    ///
82    /// For every storage map, a single unspecified key-value pair is tracked so that the
83    /// [`PartialStorageMap`] represents the correct root.
84    pub fn new_minimal(account_storage: &AccountStorage) -> Self {
85        let header: AccountStorageHeader = account_storage.to_header();
86        let commitment = header.to_commitment();
87
88        let mut maps = BTreeMap::new();
89        for slot in account_storage.slots() {
90            if let StorageSlotContent::Map(storage_map) = slot.content() {
91                let partial_map = PartialStorageMap::new_minimal(storage_map);
92                maps.insert(partial_map.root(), partial_map);
93            }
94        }
95
96        PartialStorage { header, maps, commitment }
97    }
98
99    // ACCESSORS
100    // --------------------------------------------------------------------------------------------
101
102    /// Returns a reference to the header of this partial storage.
103    pub fn header(&self) -> &AccountStorageHeader {
104        &self.header
105    }
106
107    /// Returns the commitment of this partial storage.
108    pub fn commitment(&self) -> Word {
109        self.commitment
110    }
111
112    // TODO: Consider removing once no longer needed so we don't commit to the underlying BTreeMap
113    // type.
114    /// Consumes self and returns the underlying parts.
115    pub fn into_parts(self) -> (Word, AccountStorageHeader, BTreeMap<Word, PartialStorageMap>) {
116        (self.commitment, self.header, self.maps)
117    }
118
119    // TODO: Add from account storage with (slot/[key])?
120
121    // ITERATORS
122    // --------------------------------------------------------------------------------------------
123
124    /// Returns an iterator over inner nodes of all storage map proofs contained in this
125    /// partial storage.
126    pub fn inner_nodes(&self) -> impl Iterator<Item = InnerNodeInfo> {
127        self.maps.iter().flat_map(|(_, map)| map.inner_nodes())
128    }
129
130    /// Iterator over every [`PartialStorageMap`] in this partial storage.
131    pub fn maps(&self) -> impl Iterator<Item = &PartialStorageMap> + '_ {
132        self.maps.values()
133    }
134
135    /// Iterator over all tracked, non‑empty leaves across every map.
136    pub fn leaves(&self) -> impl Iterator<Item = &SmtLeaf> + '_ {
137        self.maps().flat_map(|map| map.leaves()).map(|(_, leaf)| leaf)
138    }
139}
140
141impl Serializable for PartialStorage {
142    fn write_into<W: ByteWriter>(&self, target: &mut W) {
143        target.write(&self.header);
144        target.write(&self.maps);
145    }
146}
147
148impl Deserializable for PartialStorage {
149    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
150        let header: AccountStorageHeader = source.read()?;
151        let map_smts: BTreeMap<Word, PartialStorageMap> = source.read()?;
152
153        let commitment = header.to_commitment();
154
155        Ok(PartialStorage { header, maps: map_smts, commitment })
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use anyhow::Context;
162    use miden_core::Word;
163
164    use crate::account::{
165        AccountStorage,
166        AccountStorageHeader,
167        PartialStorage,
168        PartialStorageMap,
169        StorageMap,
170        StorageMapKey,
171        StorageSlot,
172        StorageSlotName,
173    };
174
175    #[test]
176    pub fn new_partial_storage() -> anyhow::Result<()> {
177        let map_key_present = StorageMapKey::from_array([1, 2, 3, 4]);
178        let map_key_absent = StorageMapKey::from_array([9, 12, 18, 3]);
179
180        let mut map_1 = StorageMap::new();
181        map_1.insert(map_key_absent, Word::try_from([1u64, 2, 3, 2])?).unwrap();
182        map_1.insert(map_key_present, Word::try_from([5u64, 4, 3, 2])?).unwrap();
183        assert_eq!(map_1.get(&map_key_present), [5u64, 4, 3, 2].try_into()?);
184
185        let slot_name = StorageSlotName::new("miden::test_map")?;
186
187        let storage =
188            AccountStorage::new(vec![StorageSlot::with_map(slot_name.clone(), map_1.clone())])
189                .unwrap();
190
191        // Create partial storage with validation of one map key
192        let storage_header = AccountStorageHeader::from(&storage);
193        let witness = map_1.open(&map_key_present);
194
195        let partial_storage =
196            PartialStorage::new(storage_header, [PartialStorageMap::with_witnesses([witness])?])
197                .context("creating partial storage")?;
198
199        let slot_header = partial_storage.header.find_slot_header_by_name(&slot_name).unwrap();
200        let retrieved_map = partial_storage.maps.get(&slot_header.value()).unwrap();
201        assert!(retrieved_map.open(&map_key_absent).is_err());
202        assert!(retrieved_map.open(&map_key_present).is_ok());
203        Ok(())
204    }
205}