miden_protocol/block/account_tree/
partial.rs1use miden_crypto::merkle::smt::{PartialSmt, SmtLeaf};
2
3use super::{AccountIdKey, AccountWitness};
4use crate::Word;
5use crate::account::AccountId;
6use crate::errors::AccountTreeError;
7
8#[derive(Debug, Clone, Default, PartialEq, Eq)]
12pub struct PartialAccountTree {
13 smt: PartialSmt,
14}
15
16impl PartialAccountTree {
17 pub fn new(root: Word) -> Self {
23 PartialAccountTree { smt: PartialSmt::new(root) }
24 }
25
26 pub fn with_witnesses(
34 witnesses: impl IntoIterator<Item = AccountWitness>,
35 ) -> Result<Self, AccountTreeError> {
36 let mut witnesses = witnesses.into_iter();
37
38 let Some(first_witness) = witnesses.next() else {
39 return Ok(Self::default());
40 };
41
42 let partial_smt = PartialSmt::from_proofs([first_witness.into_proof()])
46 .map_err(AccountTreeError::TreeRootConflict)?;
47 let mut tree = PartialAccountTree { smt: partial_smt };
48
49 for witness in witnesses {
52 tree.track_account(witness)?;
53 }
54
55 Ok(tree)
56 }
57
58 pub fn open(&self, account_id: AccountId) -> Result<AccountWitness, AccountTreeError> {
71 let key = AccountIdKey::from(account_id).as_word();
72
73 self.smt
74 .open(&key)
75 .map(|proof| AccountWitness::from_smt_proof(account_id, proof))
76 .map_err(|source| AccountTreeError::UntrackedAccountId { id: account_id, source })
77 }
78
79 pub fn get(&self, account_id: AccountId) -> Result<Word, AccountTreeError> {
86 let key = AccountIdKey::from(account_id).as_word();
87 self.smt
88 .get_value(&key)
89 .map_err(|source| AccountTreeError::UntrackedAccountId { id: account_id, source })
90 }
91
92 pub fn root(&self) -> Word {
94 self.smt.root()
95 }
96
97 pub fn track_account(&mut self, witness: AccountWitness) -> Result<(), AccountTreeError> {
111 let id_prefix = witness.id().prefix();
112 let id_key = AccountIdKey::from(witness.id()).as_word();
113
114 if let Ok(SmtLeaf::Single((existing_key, _))) = self.smt.get_leaf(&id_key)
126 && id_key != existing_key
127 {
128 return Err(AccountTreeError::DuplicateIdPrefix { duplicate_prefix: id_prefix });
129 }
130
131 self.smt
132 .add_proof(witness.into_proof())
133 .map_err(AccountTreeError::TreeRootConflict)?;
134
135 Ok(())
136 }
137
138 pub fn upsert_state_commitments(
147 &mut self,
148 updates: impl IntoIterator<Item = (AccountId, Word)>,
149 ) -> Result<(), AccountTreeError> {
150 for (account_id, state_commitment) in updates {
151 self.insert(account_id, state_commitment)?;
152 }
153
154 Ok(())
155 }
156
157 fn insert(
169 &mut self,
170 account_id: AccountId,
171 state_commitment: Word,
172 ) -> Result<Word, AccountTreeError> {
173 let key = AccountIdKey::from(account_id).as_word();
174
175 if let Ok(SmtLeaf::Single((existing_key, _))) = self.smt.get_leaf(&key)
182 && key != existing_key
183 {
184 return Err(AccountTreeError::DuplicateIdPrefix {
185 duplicate_prefix: account_id.prefix(),
186 });
187 }
188
189 self.smt
190 .insert(key, state_commitment)
191 .map_err(|source| AccountTreeError::UntrackedAccountId { id: account_id, source })
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use assert_matches::assert_matches;
198 use miden_crypto::merkle::smt::Smt;
199
200 use super::*;
201 use crate::block::account_tree::AccountTree;
202 use crate::block::account_tree::tests::setup_duplicate_prefix_ids;
203 use crate::testing::account_id::AccountIdBuilder;
204
205 #[test]
206 fn insert_fails_on_duplicate_prefix() -> anyhow::Result<()> {
207 let mut full_tree = AccountTree::<Smt>::default();
208
209 let [(id0, commitment0), (id1, commitment1)] = setup_duplicate_prefix_ids();
210
211 full_tree.insert(id0, commitment0).unwrap();
212 let witness = full_tree.open(id0);
213
214 let mut partial_tree = PartialAccountTree::with_witnesses([witness])?;
215
216 partial_tree.insert(id0, commitment0).unwrap();
217 assert_eq!(partial_tree.get(id0).unwrap(), commitment0);
218
219 let err = partial_tree.insert(id1, commitment1).unwrap_err();
220
221 assert_matches!(err, AccountTreeError::DuplicateIdPrefix {
222 duplicate_prefix
223 } if duplicate_prefix == id0.prefix());
224
225 partial_tree.upsert_state_commitments([(id1, commitment1)]).unwrap_err();
226
227 assert_matches!(err, AccountTreeError::DuplicateIdPrefix {
228 duplicate_prefix
229 } if duplicate_prefix == id0.prefix());
230
231 Ok(())
232 }
233
234 #[test]
235 fn insert_succeeds_on_multiple_updates() {
236 let mut full_tree = AccountTree::<Smt>::default();
237 let [(id0, commitment0), (_, commitment1)] = setup_duplicate_prefix_ids();
238
239 full_tree.insert(id0, commitment0).unwrap();
240 let witness = full_tree.open(id0);
241
242 let mut partial_tree = PartialAccountTree::new(full_tree.root());
243
244 partial_tree.track_account(witness.clone()).unwrap();
245 assert_eq!(
246 partial_tree.open(id0).unwrap(),
247 witness,
248 "full tree witness and partial tree witness should be the same"
249 );
250 assert_eq!(
251 partial_tree.root(),
252 full_tree.root(),
253 "full tree root and partial tree root should be the same"
254 );
255
256 partial_tree.insert(id0, commitment0).unwrap();
257 partial_tree.insert(id0, commitment1).unwrap();
258 assert_eq!(partial_tree.get(id0).unwrap(), commitment1);
259 }
260
261 #[test]
264 fn upsert_state_commitments_fails_on_untracked_key() -> anyhow::Result<()> {
265 let id0 = AccountIdBuilder::default().build_with_seed([5; 32]);
266 let id2 = AccountIdBuilder::default().build_with_seed([6; 32]);
267
268 let commitment0 = Word::from([1, 2, 3, 4u32]);
269 let commitment2 = Word::from([2, 3, 4, 5u32]);
270
271 let account_tree = AccountTree::with_entries([(id0, commitment0), (id2, commitment2)])?;
272 let mut partial_tree = PartialAccountTree::with_witnesses([account_tree.open(id0)])?;
274
275 let err = partial_tree.upsert_state_commitments([(id2, commitment0)]).unwrap_err();
276 assert_matches!(err, AccountTreeError::UntrackedAccountId { id, .. }
277 if id == id2
278 );
279
280 Ok(())
281 }
282
283 #[test]
287 fn track_succeeds_for_multiple_witnesses_in_sparse_tree() -> anyhow::Result<()> {
288 let id0 = AccountIdBuilder::default().build_with_seed([10; 32]);
289 let id1 = AccountIdBuilder::default().build_with_seed([11; 32]);
290 let id2 = AccountIdBuilder::default().build_with_seed([12; 32]);
291
292 let commitment0 = Word::from([1, 2, 3, 4u32]);
293 let commitment1 = Word::from([5, 6, 7, 8u32]);
294
295 let account_tree = AccountTree::with_entries([(id0, commitment0)])?;
297
298 let witness0 = account_tree.open(id0);
300 let witness1 = account_tree.open(id1);
301 let witness2 = account_tree.open(id2);
302
303 let mut partial_tree =
307 PartialAccountTree::with_witnesses([witness0, witness1.clone(), witness2])?;
308
309 partial_tree.track_account(witness1)?;
311
312 assert_eq!(partial_tree.get(id0)?, commitment0);
314
315 partial_tree.upsert_state_commitments([(id1, commitment1)])?;
317 assert_eq!(partial_tree.get(id1)?, commitment1);
318
319 Ok(())
320 }
321
322 #[test]
323 fn track_fails_on_duplicate_prefix() {
324 let full_tree = Smt::with_entries(
327 setup_duplicate_prefix_ids()
328 .map(|(id, commitment)| (AccountIdKey::from(id).as_word(), commitment)),
329 )
330 .unwrap();
331
332 let [(id0, _), (id1, _)] = setup_duplicate_prefix_ids();
333
334 let key0 = AccountIdKey::from(id0).as_word();
335 let key1 = AccountIdKey::from(id1).as_word();
336 let proof0 = full_tree.open(&key0);
337 let proof1 = full_tree.open(&key1);
338 assert_eq!(proof0.leaf(), proof1.leaf());
339
340 let witness0 =
341 AccountWitness::new(id0, proof0.get(&key0).unwrap(), proof0.into_parts().0).unwrap();
342 let witness1 =
343 AccountWitness::new(id1, proof1.get(&key1).unwrap(), proof1.into_parts().0).unwrap();
344
345 let mut partial_tree = PartialAccountTree::with_witnesses([witness0]).unwrap();
346 let err = partial_tree.track_account(witness1).unwrap_err();
347
348 assert_matches!(err, AccountTreeError::DuplicateIdPrefix { duplicate_prefix, .. }
349 if duplicate_prefix == id1.prefix()
350 )
351 }
352}