1use alloc::collections::BTreeMap;
2use alloc::format;
3use alloc::vec::Vec;
4
5use miden_protocol::account::{
6 AccountId,
7 AccountStoragePatch,
8 AccountVaultPatch,
9 StorageMapKey,
10 StorageMapPatch,
11 StorageMapWitness,
12 StorageSlot,
13 StorageSlotContent,
14 StorageSlotName,
15};
16use miden_protocol::asset::{Asset, AssetId, AssetWitness};
17use miden_protocol::crypto::merkle::MerkleError;
18use miden_protocol::crypto::merkle::smt::{
19 Backend,
20 BackendReader,
21 LargeSmtForest,
22 LargeSmtForestError,
23 LineageId,
24 SmtForestUpdateBatch,
25 TreeId,
26 VersionId,
27};
28use miden_protocol::utils::serde::Serializable;
29use miden_protocol::{EMPTY_WORD, Hasher, Word};
30
31use super::StoreError;
32
33fn vault_lineage_id(account_id: AccountId) -> LineageId {
38 let mut bytes = Vec::new();
39 bytes.extend_from_slice(b"miden-client:vault");
40 bytes.extend_from_slice(&account_id.to_bytes());
41 LineageId::new(Hasher::hash(&bytes).as_bytes())
42}
43
44fn storage_map_lineage_id(account_id: AccountId, slot_name: &StorageSlotName) -> LineageId {
46 let mut bytes = Vec::new();
47 bytes.extend_from_slice(b"miden-client:storage-map");
48 bytes.extend_from_slice(&account_id.to_bytes());
49 bytes.extend_from_slice(&(slot_name.as_str().len() as u64).to_le_bytes());
52 bytes.extend_from_slice(slot_name.as_str().as_bytes());
53 LineageId::new(Hasher::hash(&bytes).as_bytes())
54}
55
56#[derive(Default)]
61struct LineageOps {
62 expect_root: Option<Word>,
64 exhaustive: bool,
67 pairs: Vec<(Word, Word)>,
70}
71
72#[derive(Default)]
77pub struct AccountUpdate {
78 ops: BTreeMap<LineageId, LineageOps>,
79}
80
81impl AccountUpdate {
82 pub fn new() -> Self {
84 Self::default()
85 }
86
87 pub fn vault_patch(
95 &mut self,
96 account_id: AccountId,
97 patch: &AccountVaultPatch,
98 expected_root: Word,
99 ) {
100 let vault = self.entry(vault_lineage_id(account_id));
101 vault.expect_root = Some(expected_root);
102 vault
103 .pairs
104 .extend(patch.updated_assets().map(|a| (a.id().hash().into(), a.to_value_word())));
105 vault
106 .pairs
107 .extend(patch.removed_asset_ids().map(|id| (id.hash().into(), EMPTY_WORD)));
108 }
109
110 pub fn storage_patch(&mut self, account_id: AccountId, patch: &AccountStoragePatch) {
117 for (slot_name, map_patch) in patch.maps() {
118 let ops = self.entry(storage_map_lineage_id(account_id, slot_name));
119 ops.pairs.extend(
120 map_patch
121 .entries()
122 .into_iter()
123 .flat_map(|e| e.as_map().iter())
124 .map(|(key, value)| (Word::from(key.hash()), *value)),
125 );
126 if matches!(map_patch, StorageMapPatch::Create { .. } | StorageMapPatch::Remove) {
127 ops.exhaustive = true;
128 }
129 }
130 }
131
132 pub fn full_state<'a>(
137 &mut self,
138 account_id: AccountId,
139 assets: impl Iterator<Item = Asset>,
140 slots: impl Iterator<Item = &'a StorageSlot>,
141 ) {
142 let vault = self.entry(vault_lineage_id(account_id));
143 vault.exhaustive = true;
144 vault.pairs.extend(assets.map(|a| (a.id().hash().into(), a.to_value_word())));
145
146 for slot in slots {
147 if let StorageSlotContent::Map(map) = slot.content() {
148 let ops = self.entry(storage_map_lineage_id(account_id, slot.name()));
149 ops.exhaustive = true;
150 ops.pairs
151 .extend(map.entries().map(|(key, value)| (Word::from(key.hash()), *value)));
152 }
153 }
154 }
155
156 pub fn clear_map(&mut self, account_id: AccountId, slot_name: &StorageSlotName) {
158 self.entry(storage_map_lineage_id(account_id, slot_name)).exhaustive = true;
159 }
160
161 fn entry(&mut self, lineage: LineageId) -> &mut LineageOps {
162 self.ops.entry(lineage).or_default()
163 }
164}
165
166pub struct AccountSmtForest<B: BackendReader> {
182 forest: LargeSmtForest<B>,
183}
184
185impl<B: BackendReader> AccountSmtForest<B> {
186 pub fn new(backend: B) -> Result<Self, StoreError> {
188 Ok(Self {
189 forest: LargeSmtForest::new(backend).map_err(forest_error)?,
190 })
191 }
192
193 pub fn vault_root(&self, account_id: AccountId) -> Option<Word> {
199 self.forest.latest_root(vault_lineage_id(account_id))
200 }
201
202 pub fn map_root(&self, account_id: AccountId, slot_name: &StorageSlotName) -> Option<Word> {
205 self.forest.latest_root(storage_map_lineage_id(account_id, slot_name))
206 }
207
208 pub fn get_asset_and_witness(
215 &self,
216 account_id: AccountId,
217 expected_vault_root: Word,
218 asset_id: AssetId,
219 ) -> Result<(Asset, AssetWitness), StoreError> {
220 let lineage = vault_lineage_id(account_id);
221 let tree = self.verified_latest_tree(lineage, expected_vault_root)?;
222
223 let hashed_key: Word = asset_id.hash().into();
224 let proof = self.forest.open(tree, hashed_key).map_err(forest_error)?;
225 let asset_word = proof
226 .get(&hashed_key)
227 .ok_or(StoreError::VaultKeyNotTracked(asset_id, hashed_key))?;
228 if asset_word == EMPTY_WORD {
229 return Err(StoreError::VaultKeyNotTracked(asset_id, hashed_key));
230 }
231
232 let asset = Asset::new(asset_id, asset_word)?;
233 let witness = AssetWitness::new(proof, [asset_id])?;
234 Ok((asset, witness))
235 }
236
237 pub fn open_vault_asset_witnesses(
246 &self,
247 account_id: AccountId,
248 expected_vault_root: Word,
249 asset_ids: impl IntoIterator<Item = AssetId>,
250 ) -> Result<Vec<AssetWitness>, StoreError> {
251 let lineage = vault_lineage_id(account_id);
252 let tree = self.verified_latest_tree(lineage, expected_vault_root)?;
253
254 asset_ids
255 .into_iter()
256 .map(|asset_id| {
257 let proof = self.forest.open(tree, asset_id.hash().into()).map_err(forest_error)?;
258 Ok(AssetWitness::new(proof, [asset_id])?)
259 })
260 .collect()
261 }
262
263 pub fn get_storage_map_item_witness(
268 &self,
269 account_id: AccountId,
270 slot_name: &StorageSlotName,
271 expected_map_root: Word,
272 key: StorageMapKey,
273 ) -> Result<StorageMapWitness, StoreError> {
274 let lineage = storage_map_lineage_id(account_id, slot_name);
275 let tree = self.verified_latest_tree(lineage, expected_map_root)?;
276
277 let hashed_key = key.hash();
278 let proof = self.forest.open(tree, Word::from(hashed_key)).map_err(forest_error)?;
279 Ok(StorageMapWitness::new(proof, [key])?)
280 }
281}
282
283impl<B: Backend> AccountSmtForest<B> {
287 pub fn apply(
297 &mut self,
298 new_version: VersionId,
299 update: AccountUpdate,
300 ) -> Result<(), StoreError> {
301 let mut batch = SmtForestUpdateBatch::empty();
302 let mut expected_roots = Vec::new();
303
304 for (lineage, ops) in update.ops {
305 if let Some(expected_root) = ops.expect_root {
306 expected_roots.push((lineage, expected_root));
307 }
308
309 let stored_keys = if ops.exhaustive {
312 self.lineage_entry_keys(lineage)?
313 } else {
314 Vec::new()
315 };
316 let batch_ops = batch.operations(lineage);
317 let mut target = BTreeMap::new();
318 for (key, value) in ops.pairs {
319 if value == EMPTY_WORD {
320 target.remove(&key);
321 batch_ops.add_remove(key);
322 } else {
323 target.insert(key, value);
324 }
325 }
326 for key in stored_keys {
327 if !target.contains_key(&key) {
328 batch_ops.add_remove(key);
329 }
330 }
331 for (key, value) in target {
332 batch_ops.add_insert(key, value);
333 }
334 }
335
336 let mutations =
337 self.forest.compute_forest_mutations(new_version, batch).map_err(forest_error)?;
338
339 for (lineage, expected_root) in expected_roots {
340 let actual_root = mutations
341 .roots()
342 .find(|root| root.lineage() == lineage)
343 .map(|root| root.root())
344 .expect("every expected lineage has a computed mutation");
345 if actual_root != expected_root {
346 return Err(StoreError::MerkleStoreError(MerkleError::ConflictingRoots {
347 expected_root,
348 actual_root,
349 }));
350 }
351 }
352
353 self.forest.apply_mutations(mutations).map_err(forest_error)?;
354
355 Ok(())
356 }
357}
358
359impl<B: BackendReader> AccountSmtForest<B> {
360 fn verified_latest_tree(
365 &self,
366 lineage: LineageId,
367 expected_root: Word,
368 ) -> Result<TreeId, StoreError> {
369 let version = self
370 .forest
371 .latest_version(lineage)
372 .ok_or_else(|| StoreError::DatabaseError(format!("unknown lineage {lineage}")))?;
373 let root = self.forest.latest_root(lineage).expect("lineage has a latest version");
374 if root != expected_root {
375 return Err(StoreError::MerkleStoreError(MerkleError::ConflictingRoots {
376 expected_root,
377 actual_root: root,
378 }));
379 }
380 Ok(TreeId::new(lineage, version))
381 }
382
383 fn lineage_entry_keys(&self, lineage: LineageId) -> Result<Vec<Word>, StoreError> {
386 let Some(version) = self.forest.latest_version(lineage) else {
387 return Ok(Vec::new());
388 };
389
390 let entries = self.forest.entries(TreeId::new(lineage, version)).map_err(forest_error)?;
391 let mut keys = Vec::new();
392 for entry in entries {
393 keys.push(entry.map_err(forest_error)?.key);
394 }
395 Ok(keys)
396 }
397}
398
399#[allow(clippy::needless_pass_by_value)]
406fn forest_error(err: LargeSmtForestError) -> StoreError {
407 StoreError::DatabaseError(format!("smt forest error: {err}"))
408}
409
410#[cfg(test)]
414mod tests {
415 use miden_protocol::account::StorageMap;
416 use miden_protocol::asset::{AssetVault, FungibleAsset};
417 use miden_protocol::crypto::merkle::smt::ForestInMemoryBackend;
418 use miden_protocol::testing::account_id::{
419 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
420 ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET,
421 };
422
423 use super::*;
424
425 fn account_a() -> AccountId {
426 AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap()
427 }
428
429 fn account_b() -> AccountId {
430 AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET).unwrap()
431 }
432
433 fn slot(name: &str) -> StorageSlotName {
434 StorageSlotName::new(name).unwrap()
435 }
436
437 fn asset(amount: u64) -> Asset {
438 FungibleAsset::new(account_a(), amount).unwrap().into()
439 }
440
441 fn forest() -> AccountSmtForest<ForestInMemoryBackend> {
442 AccountSmtForest::new(ForestInMemoryBackend::new()).unwrap()
443 }
444
445 fn set_vault(forest: &mut AccountSmtForest<ForestInMemoryBackend>, version: u64, of: &[Asset]) {
446 let mut update = AccountUpdate::new();
447 update.full_state(account_a(), of.iter().copied(), core::iter::empty::<&StorageSlot>());
448 forest.apply(version, update).unwrap();
449 }
450
451 #[test]
452 fn accepts_read_only_backend() {
453 let backend = ForestInMemoryBackend::new();
454 let forest = AccountSmtForest::new(backend.reader().unwrap()).unwrap();
455
456 assert_eq!(forest.vault_root(account_a()), None);
457 }
458
459 #[test]
462 fn lineage_ids_are_distinct() {
463 assert_ne!(vault_lineage_id(account_a()), vault_lineage_id(account_b()));
464 assert_ne!(
465 storage_map_lineage_id(account_a(), &slot("miden::test::map_one")),
466 storage_map_lineage_id(account_a(), &slot("miden::test::map_two")),
467 );
468 assert_ne!(
469 storage_map_lineage_id(account_a(), &slot("miden::test::map")),
470 storage_map_lineage_id(account_b(), &slot("miden::test::map")),
471 );
472 assert_ne!(
473 vault_lineage_id(account_a()),
474 storage_map_lineage_id(account_a(), &slot("miden::test::map")),
475 );
476 }
477
478 #[test]
480 fn full_state_replaces_previous_entries() {
481 let mut forest = forest();
482 let id = account_a();
483 let (old, new) = (asset(100), asset(250));
484
485 set_vault(&mut forest, 1, &[old]);
486 let (read, _) = forest
487 .get_asset_and_witness(id, forest.vault_root(id).unwrap(), old.id())
488 .unwrap();
489 assert_eq!(read, old);
490
491 set_vault(&mut forest, 2, &[new]);
492 let (read, _) = forest
493 .get_asset_and_witness(id, forest.vault_root(id).unwrap(), new.id())
494 .unwrap();
495 assert_eq!(read, new);
496
497 assert_ne!(old.to_value_word(), new.to_value_word());
499 }
500
501 #[test]
503 fn full_state_can_empty_a_vault() {
504 let mut forest = forest();
505 let id = account_a();
506 let held = asset(100);
507
508 set_vault(&mut forest, 1, &[held]);
509 set_vault(&mut forest, 2, &[]);
510
511 let vault_root = forest.vault_root(id).unwrap();
512 assert_eq!(vault_root, StorageMap::default().root());
513 assert!(matches!(
514 forest.get_asset_and_witness(id, vault_root, held.id()),
515 Err(StoreError::VaultKeyNotTracked(..))
516 ));
517 }
518
519 #[test]
521 fn witness_reads_reject_mismatched_roots() {
522 let mut forest = forest();
523 let held = asset(100);
524 set_vault(&mut forest, 1, &[held]);
525
526 let result = forest.get_asset_and_witness(account_a(), EMPTY_WORD, held.id());
527 assert!(matches!(
528 result,
529 Err(StoreError::MerkleStoreError(MerkleError::ConflictingRoots { .. }))
530 ));
531 }
532
533 #[test]
534 fn rejected_update_does_not_advance_forest() {
535 let mut forest = forest();
536 let id = account_a();
537 let (old, new) = (asset(100), asset(250));
538 set_vault(&mut forest, 1, &[old]);
539
540 let old_root = forest.vault_root(id).unwrap();
541 let new_root = AssetVault::new(&[new]).unwrap().root();
542 assert_ne!(new_root, old_root);
543
544 let mut rejected = AccountUpdate::new();
545 rejected.vault_patch(id, &AccountVaultPatch::with_assets([new]), old_root);
546 assert!(matches!(
547 forest.apply(2, rejected),
548 Err(StoreError::MerkleStoreError(MerkleError::ConflictingRoots {
549 expected_root,
550 actual_root,
551 })) if expected_root == old_root && actual_root == new_root
552 ));
553 assert_eq!(forest.vault_root(id), Some(old_root));
554
555 let mut accepted = AccountUpdate::new();
556 accepted.vault_patch(id, &AccountVaultPatch::with_assets([new]), new_root);
557 forest.apply(2, accepted).unwrap();
558 assert_eq!(forest.vault_root(id), Some(new_root));
559 }
560}