miden_protocol/block/account_tree/
partial.rs1use miden_crypto::merkle::smt::{PartialSmt, SmtLeaf};
2
3use super::{AccountWitness, account_id_to_smt_key};
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 = account_id_to_smt_key(account_id);
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 = account_id_to_smt_key(account_id);
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 = account_id_to_smt_key(witness.id());
113
114 if self.smt.get_leaf(&id_key).is_ok() {
123 return Err(AccountTreeError::DuplicateIdPrefix { duplicate_prefix: id_prefix });
124 }
125
126 self.smt
127 .add_proof(witness.into_proof())
128 .map_err(AccountTreeError::TreeRootConflict)?;
129
130 Ok(())
131 }
132
133 pub fn upsert_state_commitments(
142 &mut self,
143 updates: impl IntoIterator<Item = (AccountId, Word)>,
144 ) -> Result<(), AccountTreeError> {
145 for (account_id, state_commitment) in updates {
146 self.insert(account_id, state_commitment)?;
147 }
148
149 Ok(())
150 }
151
152 fn insert(
164 &mut self,
165 account_id: AccountId,
166 state_commitment: Word,
167 ) -> Result<Word, AccountTreeError> {
168 let key = account_id_to_smt_key(account_id);
169
170 if let Ok(SmtLeaf::Single((existing_key, _))) = self.smt.get_leaf(&key)
177 && key != existing_key
178 {
179 return Err(AccountTreeError::DuplicateIdPrefix {
180 duplicate_prefix: account_id.prefix(),
181 });
182 }
183
184 self.smt
185 .insert(key, state_commitment)
186 .map_err(|source| AccountTreeError::UntrackedAccountId { id: account_id, source })
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use assert_matches::assert_matches;
193 use miden_crypto::merkle::smt::Smt;
194
195 use super::*;
196 use crate::block::account_tree::AccountTree;
197 use crate::block::account_tree::tests::setup_duplicate_prefix_ids;
198
199 #[test]
200 fn insert_fails_on_duplicate_prefix() -> anyhow::Result<()> {
201 let mut full_tree = AccountTree::<Smt>::default();
202
203 let [(id0, commitment0), (id1, commitment1)] = setup_duplicate_prefix_ids();
204
205 full_tree.insert(id0, commitment0).unwrap();
206 let witness = full_tree.open(id0);
207
208 let mut partial_tree = PartialAccountTree::with_witnesses([witness])?;
209
210 partial_tree.insert(id0, commitment0).unwrap();
211 assert_eq!(partial_tree.get(id0).unwrap(), commitment0);
212
213 let err = partial_tree.insert(id1, commitment1).unwrap_err();
214
215 assert_matches!(err, AccountTreeError::DuplicateIdPrefix {
216 duplicate_prefix
217 } if duplicate_prefix == id0.prefix());
218
219 partial_tree.upsert_state_commitments([(id1, commitment1)]).unwrap_err();
220
221 assert_matches!(err, AccountTreeError::DuplicateIdPrefix {
222 duplicate_prefix
223 } if duplicate_prefix == id0.prefix());
224
225 Ok(())
226 }
227
228 #[test]
229 fn insert_succeeds_on_multiple_updates() {
230 let mut full_tree = AccountTree::<Smt>::default();
231 let [(id0, commitment0), (_, commitment1)] = setup_duplicate_prefix_ids();
232
233 full_tree.insert(id0, commitment0).unwrap();
234 let witness = full_tree.open(id0);
235
236 let mut partial_tree = PartialAccountTree::new(full_tree.root());
237
238 partial_tree.track_account(witness.clone()).unwrap();
239 assert_eq!(
240 partial_tree.open(id0).unwrap(),
241 witness,
242 "full tree witness and partial tree witness should be the same"
243 );
244 assert_eq!(
245 partial_tree.root(),
246 full_tree.root(),
247 "full tree root and partial tree root should be the same"
248 );
249
250 partial_tree.insert(id0, commitment0).unwrap();
251 partial_tree.insert(id0, commitment1).unwrap();
252 assert_eq!(partial_tree.get(id0).unwrap(), commitment1);
253 }
254
255 #[test]
256 fn upsert_state_commitments_fails_on_untracked_key() {
257 let mut partial_tree = PartialAccountTree::default();
258 let [update, _] = setup_duplicate_prefix_ids();
259
260 let err = partial_tree.upsert_state_commitments([update]).unwrap_err();
261 assert_matches!(err, AccountTreeError::UntrackedAccountId { id, .. }
262 if id == update.0
263 )
264 }
265
266 #[test]
267 fn track_fails_on_duplicate_prefix() {
268 let full_tree = Smt::with_entries(
271 setup_duplicate_prefix_ids()
272 .map(|(id, commitment)| (account_id_to_smt_key(id), commitment)),
273 )
274 .unwrap();
275
276 let [(id0, _), (id1, _)] = setup_duplicate_prefix_ids();
277
278 let key0 = account_id_to_smt_key(id0);
279 let key1 = account_id_to_smt_key(id1);
280 let proof0 = full_tree.open(&key0);
281 let proof1 = full_tree.open(&key1);
282 assert_eq!(proof0.leaf(), proof1.leaf());
283
284 let witness0 =
285 AccountWitness::new(id0, proof0.get(&key0).unwrap(), proof0.into_parts().0).unwrap();
286 let witness1 =
287 AccountWitness::new(id1, proof1.get(&key1).unwrap(), proof1.into_parts().0).unwrap();
288
289 let mut partial_tree = PartialAccountTree::with_witnesses([witness0]).unwrap();
290 let err = partial_tree.track_account(witness1).unwrap_err();
291
292 assert_matches!(err, AccountTreeError::DuplicateIdPrefix { duplicate_prefix, .. }
293 if duplicate_prefix == id1.prefix()
294 )
295 }
296}