miden_objects/block/
partial_account_tree.rs1use miden_crypto::merkle::SmtLeaf;
2
3use crate::Word;
4use crate::account::AccountId;
5use crate::block::AccountWitness;
6use crate::block::account_tree::account_id_to_smt_key;
7use crate::crypto::merkle::PartialSmt;
8use crate::errors::AccountTreeError;
9
10#[derive(Debug, Clone, Default, PartialEq, Eq)]
14pub struct PartialAccountTree {
15 smt: PartialSmt,
16}
17
18impl PartialAccountTree {
19 pub fn new(root: Word) -> Self {
25 PartialAccountTree { smt: PartialSmt::new(root) }
26 }
27
28 pub fn with_witnesses(
36 witnesses: impl IntoIterator<Item = AccountWitness>,
37 ) -> Result<Self, AccountTreeError> {
38 let mut witnesses = witnesses.into_iter();
39
40 let Some(first_witness) = witnesses.next() else {
41 return Ok(Self::default());
42 };
43
44 let partial_smt = PartialSmt::from_proofs([first_witness.into_proof()])
48 .map_err(AccountTreeError::TreeRootConflict)?;
49 let mut tree = PartialAccountTree { smt: partial_smt };
50
51 for witness in witnesses {
54 tree.track_account(witness)?;
55 }
56
57 Ok(tree)
58 }
59
60 pub fn open(&self, account_id: AccountId) -> Result<AccountWitness, AccountTreeError> {
73 let key = account_id_to_smt_key(account_id);
74
75 self.smt
76 .open(&key)
77 .map(|proof| AccountWitness::from_smt_proof(account_id, proof))
78 .map_err(|source| AccountTreeError::UntrackedAccountId { id: account_id, source })
79 }
80
81 pub fn get(&self, account_id: AccountId) -> Result<Word, AccountTreeError> {
88 let key = account_id_to_smt_key(account_id);
89 self.smt
90 .get_value(&key)
91 .map_err(|source| AccountTreeError::UntrackedAccountId { id: account_id, source })
92 }
93
94 pub fn root(&self) -> Word {
96 self.smt.root()
97 }
98
99 pub fn track_account(&mut self, witness: AccountWitness) -> Result<(), AccountTreeError> {
113 let id_prefix = witness.id().prefix();
114 let id_key = account_id_to_smt_key(witness.id());
115
116 if self.smt.get_leaf(&id_key).is_ok() {
125 return Err(AccountTreeError::DuplicateIdPrefix { duplicate_prefix: id_prefix });
126 }
127
128 self.smt
129 .add_proof(witness.into_proof())
130 .map_err(AccountTreeError::TreeRootConflict)?;
131
132 Ok(())
133 }
134
135 pub fn upsert_state_commitments(
144 &mut self,
145 updates: impl IntoIterator<Item = (AccountId, Word)>,
146 ) -> Result<(), AccountTreeError> {
147 for (account_id, state_commitment) in updates {
148 self.insert(account_id, state_commitment)?;
149 }
150
151 Ok(())
152 }
153
154 fn insert(
166 &mut self,
167 account_id: AccountId,
168 state_commitment: Word,
169 ) -> Result<Word, AccountTreeError> {
170 let key = account_id_to_smt_key(account_id);
171
172 if let Ok(SmtLeaf::Single((existing_key, _))) = self.smt.get_leaf(&key)
179 && key != existing_key
180 {
181 return Err(AccountTreeError::DuplicateIdPrefix {
182 duplicate_prefix: account_id.prefix(),
183 });
184 }
185
186 self.smt
187 .insert(key, state_commitment)
188 .map_err(|source| AccountTreeError::UntrackedAccountId { id: account_id, source })
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use assert_matches::assert_matches;
195 use miden_crypto::merkle::Smt;
196
197 use super::*;
198 use crate::block::account_tree::AccountTree;
199 use crate::block::account_tree::tests::setup_duplicate_prefix_ids;
200
201 #[test]
202 fn insert_fails_on_duplicate_prefix() -> anyhow::Result<()> {
203 let mut full_tree = AccountTree::<Smt>::default();
204
205 let [(id0, commitment0), (id1, commitment1)] = setup_duplicate_prefix_ids();
206
207 full_tree.insert(id0, commitment0).unwrap();
208 let witness = full_tree.open(id0);
209
210 let mut partial_tree = PartialAccountTree::with_witnesses([witness])?;
211
212 partial_tree.insert(id0, commitment0).unwrap();
213 assert_eq!(partial_tree.get(id0).unwrap(), commitment0);
214
215 let err = partial_tree.insert(id1, commitment1).unwrap_err();
216
217 assert_matches!(err, AccountTreeError::DuplicateIdPrefix {
218 duplicate_prefix
219 } if duplicate_prefix == id0.prefix());
220
221 partial_tree.upsert_state_commitments([(id1, commitment1)]).unwrap_err();
222
223 assert_matches!(err, AccountTreeError::DuplicateIdPrefix {
224 duplicate_prefix
225 } if duplicate_prefix == id0.prefix());
226
227 Ok(())
228 }
229
230 #[test]
231 fn insert_succeeds_on_multiple_updates() {
232 let mut full_tree = AccountTree::<Smt>::default();
233 let [(id0, commitment0), (_, commitment1)] = setup_duplicate_prefix_ids();
234
235 full_tree.insert(id0, commitment0).unwrap();
236 let witness = full_tree.open(id0);
237
238 let mut partial_tree = PartialAccountTree::new(full_tree.root());
239
240 partial_tree.track_account(witness.clone()).unwrap();
241 assert_eq!(
242 partial_tree.open(id0).unwrap(),
243 witness,
244 "full tree witness and partial tree witness should be the same"
245 );
246 assert_eq!(
247 partial_tree.root(),
248 full_tree.root(),
249 "full tree root and partial tree root should be the same"
250 );
251
252 partial_tree.insert(id0, commitment0).unwrap();
253 partial_tree.insert(id0, commitment1).unwrap();
254 assert_eq!(partial_tree.get(id0).unwrap(), commitment1);
255 }
256
257 #[test]
258 fn upsert_state_commitments_fails_on_untracked_key() {
259 let mut partial_tree = PartialAccountTree::default();
260 let [update, _] = setup_duplicate_prefix_ids();
261
262 let err = partial_tree.upsert_state_commitments([update]).unwrap_err();
263 assert_matches!(err, AccountTreeError::UntrackedAccountId { id, .. }
264 if id == update.0
265 )
266 }
267
268 #[test]
269 fn track_fails_on_duplicate_prefix() {
270 let full_tree = Smt::with_entries(
273 setup_duplicate_prefix_ids()
274 .map(|(id, commitment)| (account_id_to_smt_key(id), commitment)),
275 )
276 .unwrap();
277
278 let [(id0, _), (id1, _)] = setup_duplicate_prefix_ids();
279
280 let key0 = account_id_to_smt_key(id0);
281 let key1 = account_id_to_smt_key(id1);
282 let proof0 = full_tree.open(&key0);
283 let proof1 = full_tree.open(&key1);
284 assert_eq!(proof0.leaf(), proof1.leaf());
285
286 let witness0 =
287 AccountWitness::new(id0, proof0.get(&key0).unwrap(), proof0.into_parts().0).unwrap();
288 let witness1 =
289 AccountWitness::new(id1, proof1.get(&key1).unwrap(), proof1.into_parts().0).unwrap();
290
291 let mut partial_tree = PartialAccountTree::with_witnesses([witness0]).unwrap();
292 let err = partial_tree.track_account(witness1).unwrap_err();
293
294 assert_matches!(err, AccountTreeError::DuplicateIdPrefix { duplicate_prefix, .. }
295 if duplicate_prefix == id1.prefix()
296 )
297 }
298}