Skip to main content

miden_protocol/account/storage/
partial.rs

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