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> {
183 forest: LargeSmtForest<B>,
184}
185
186impl<B: BackendReader> AccountSmtForest<B> {
187 pub fn new(backend: B) -> Result<Self, StoreError> {
189 Ok(Self {
190 forest: LargeSmtForest::new(backend).map_err(forest_error)?,
191 })
192 }
193
194 pub fn vault_root(&self, account_id: AccountId) -> Option<Word> {
200 self.forest.latest_root(vault_lineage_id(account_id))
201 }
202
203 pub fn map_root(&self, account_id: AccountId, slot_name: &StorageSlotName) -> Option<Word> {
206 self.forest.latest_root(storage_map_lineage_id(account_id, slot_name))
207 }
208
209 pub fn get_asset_and_witness(
216 &self,
217 account_id: AccountId,
218 expected_vault_root: Word,
219 asset_id: AssetId,
220 ) -> Result<(Asset, AssetWitness), StoreError> {
221 let lineage = vault_lineage_id(account_id);
222 let tree = self.verified_latest_tree(lineage, expected_vault_root)?;
223
224 let hashed_key: Word = asset_id.hash().into();
225 let proof = self.forest.open(tree, hashed_key).map_err(forest_error)?;
226 let asset_word = proof
227 .get(&hashed_key)
228 .ok_or(StoreError::VaultKeyNotTracked(asset_id, hashed_key))?;
229 if asset_word == EMPTY_WORD {
230 return Err(StoreError::VaultKeyNotTracked(asset_id, hashed_key));
231 }
232
233 let asset = Asset::from_id_and_value(asset_id, asset_word)?;
234 let witness = AssetWitness::new(proof, [asset_id])?;
235 Ok((asset, witness))
236 }
237
238 pub fn open_vault_asset_witnesses(
247 &self,
248 account_id: AccountId,
249 expected_vault_root: Word,
250 asset_ids: impl IntoIterator<Item = AssetId>,
251 ) -> Result<Vec<AssetWitness>, StoreError> {
252 let lineage = vault_lineage_id(account_id);
253 let tree = self.verified_latest_tree(lineage, expected_vault_root)?;
254
255 asset_ids
256 .into_iter()
257 .map(|asset_id| {
258 let proof = self.forest.open(tree, asset_id.hash().into()).map_err(forest_error)?;
259 Ok(AssetWitness::new(proof, [asset_id])?)
260 })
261 .collect()
262 }
263
264 pub fn get_storage_map_item_witness(
269 &self,
270 account_id: AccountId,
271 slot_name: &StorageSlotName,
272 expected_map_root: Word,
273 key: StorageMapKey,
274 ) -> Result<StorageMapWitness, StoreError> {
275 let lineage = storage_map_lineage_id(account_id, slot_name);
276 let tree = self.verified_latest_tree(lineage, expected_map_root)?;
277
278 let hashed_key = key.hash();
279 let proof = self.forest.open(tree, Word::from(hashed_key)).map_err(forest_error)?;
280 Ok(StorageMapWitness::new(proof, [key])?)
281 }
282}
283
284impl<B: Backend> AccountSmtForest<B> {
288 pub fn apply(
298 &mut self,
299 new_version: VersionId,
300 update: AccountUpdate,
301 ) -> Result<(), StoreError> {
302 let mut batch = SmtForestUpdateBatch::empty();
303 let mut expected_roots = Vec::new();
304
305 for (lineage, ops) in update.ops {
306 if let Some(expected_root) = ops.expect_root {
307 expected_roots.push((lineage, expected_root));
308 }
309
310 let stored_keys = if ops.exhaustive {
313 self.lineage_entry_keys(lineage)?
314 } else {
315 Vec::new()
316 };
317 let batch_ops = batch.operations(lineage);
318 let mut target = BTreeMap::new();
319 for (key, value) in ops.pairs {
320 if value == EMPTY_WORD {
321 target.remove(&key);
322 batch_ops.add_remove(key);
323 } else {
324 target.insert(key, value);
325 }
326 }
327 for key in stored_keys {
328 if !target.contains_key(&key) {
329 batch_ops.add_remove(key);
330 }
331 }
332 for (key, value) in target {
333 batch_ops.add_insert(key, value);
334 }
335 }
336
337 let mutations =
338 self.forest.compute_forest_mutations(new_version, batch).map_err(forest_error)?;
339
340 for (lineage, expected_root) in expected_roots {
341 let actual_root = mutations
342 .roots()
343 .find(|root| root.lineage() == lineage)
344 .map(|root| root.root())
345 .expect("every expected lineage has a computed mutation");
346 if actual_root != expected_root {
347 return Err(StoreError::MerkleStoreError(MerkleError::ConflictingRoots {
348 expected_root,
349 actual_root,
350 }));
351 }
352 }
353
354 self.forest.apply_mutations(mutations).map_err(forest_error)?;
355
356 Ok(())
357 }
358}
359
360impl<B: BackendReader> AccountSmtForest<B> {
361 fn verified_latest_tree(
366 &self,
367 lineage: LineageId,
368 expected_root: Word,
369 ) -> Result<TreeId, StoreError> {
370 let version = self
371 .forest
372 .latest_version(lineage)
373 .ok_or_else(|| StoreError::DatabaseError(format!("unknown lineage {lineage}")))?;
374 let root = self.forest.latest_root(lineage).expect("lineage has a latest version");
375 if root != expected_root {
376 return Err(StoreError::MerkleStoreError(MerkleError::ConflictingRoots {
377 expected_root,
378 actual_root: root,
379 }));
380 }
381 Ok(TreeId::new(lineage, version))
382 }
383
384 fn lineage_entry_keys(&self, lineage: LineageId) -> Result<Vec<Word>, StoreError> {
387 let Some(version) = self.forest.latest_version(lineage) else {
388 return Ok(Vec::new());
389 };
390
391 let entries = self.forest.entries(TreeId::new(lineage, version)).map_err(forest_error)?;
392 let mut keys = Vec::new();
393 for entry in entries {
394 keys.push(entry.map_err(forest_error)?.key);
395 }
396 Ok(keys)
397 }
398}
399
400#[allow(clippy::needless_pass_by_value)]
407fn forest_error(err: LargeSmtForestError) -> StoreError {
408 StoreError::DatabaseError(format!("smt forest error: {err}"))
409}
410
411#[cfg(test)]
415mod tests {
416 use miden_protocol::account::StorageMap;
417 use miden_protocol::asset::{AssetVault, FungibleAsset};
418 use miden_protocol::crypto::merkle::smt::ForestInMemoryBackend;
419 use miden_protocol::testing::account_id::{
420 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
421 ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET,
422 };
423
424 use super::*;
425
426 fn account_a() -> AccountId {
427 AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap()
428 }
429
430 fn account_b() -> AccountId {
431 AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET).unwrap()
432 }
433
434 fn slot(name: &str) -> StorageSlotName {
435 StorageSlotName::new(name).unwrap()
436 }
437
438 fn asset(amount: u64) -> Asset {
439 FungibleAsset::new(account_a(), amount).unwrap().into()
440 }
441
442 fn forest() -> AccountSmtForest<ForestInMemoryBackend> {
443 AccountSmtForest::new(ForestInMemoryBackend::new()).unwrap()
444 }
445
446 fn set_vault(forest: &mut AccountSmtForest<ForestInMemoryBackend>, version: u64, of: &[Asset]) {
447 let mut update = AccountUpdate::new();
448 update.full_state(account_a(), of.iter().copied(), core::iter::empty::<&StorageSlot>());
449 forest.apply(version, update).unwrap();
450 }
451
452 #[test]
453 fn accepts_read_only_backend() {
454 let backend = ForestInMemoryBackend::new();
455 let forest = AccountSmtForest::new(backend.reader().unwrap()).unwrap();
456
457 assert_eq!(forest.vault_root(account_a()), None);
458 }
459
460 #[test]
463 fn lineage_ids_are_distinct() {
464 assert_ne!(vault_lineage_id(account_a()), vault_lineage_id(account_b()));
465 assert_ne!(
466 storage_map_lineage_id(account_a(), &slot("miden::test::map_one")),
467 storage_map_lineage_id(account_a(), &slot("miden::test::map_two")),
468 );
469 assert_ne!(
470 storage_map_lineage_id(account_a(), &slot("miden::test::map")),
471 storage_map_lineage_id(account_b(), &slot("miden::test::map")),
472 );
473 assert_ne!(
474 vault_lineage_id(account_a()),
475 storage_map_lineage_id(account_a(), &slot("miden::test::map")),
476 );
477 }
478
479 #[test]
481 fn full_state_replaces_previous_entries() {
482 let mut forest = forest();
483 let id = account_a();
484 let (old, new) = (asset(100), asset(250));
485
486 set_vault(&mut forest, 1, &[old]);
487 let (read, _) = forest
488 .get_asset_and_witness(id, forest.vault_root(id).unwrap(), old.id())
489 .unwrap();
490 assert_eq!(read, old);
491
492 set_vault(&mut forest, 2, &[new]);
493 let (read, _) = forest
494 .get_asset_and_witness(id, forest.vault_root(id).unwrap(), new.id())
495 .unwrap();
496 assert_eq!(read, new);
497
498 assert_ne!(old.to_value_word(), new.to_value_word());
500 }
501
502 #[test]
504 fn full_state_can_empty_a_vault() {
505 let mut forest = forest();
506 let id = account_a();
507 let held = asset(100);
508
509 set_vault(&mut forest, 1, &[held]);
510 set_vault(&mut forest, 2, &[]);
511
512 let vault_root = forest.vault_root(id).unwrap();
513 assert_eq!(vault_root, StorageMap::default().root());
514 assert!(matches!(
515 forest.get_asset_and_witness(id, vault_root, held.id()),
516 Err(StoreError::VaultKeyNotTracked(..))
517 ));
518 }
519
520 #[test]
522 fn witness_reads_reject_mismatched_roots() {
523 let mut forest = forest();
524 let held = asset(100);
525 set_vault(&mut forest, 1, &[held]);
526
527 let result = forest.get_asset_and_witness(account_a(), EMPTY_WORD, held.id());
528 assert!(matches!(
529 result,
530 Err(StoreError::MerkleStoreError(MerkleError::ConflictingRoots { .. }))
531 ));
532 }
533
534 #[test]
535 fn rejected_update_does_not_advance_forest() {
536 let mut forest = forest();
537 let id = account_a();
538 let (old, new) = (asset(100), asset(250));
539 set_vault(&mut forest, 1, &[old]);
540
541 let old_root = forest.vault_root(id).unwrap();
542 let new_root = AssetVault::new(&[new]).unwrap().root();
543 assert_ne!(new_root, old_root);
544
545 let mut rejected = AccountUpdate::new();
546 rejected.vault_patch(id, &AccountVaultPatch::with_assets([new]), old_root);
547 assert!(matches!(
548 forest.apply(2, rejected),
549 Err(StoreError::MerkleStoreError(MerkleError::ConflictingRoots {
550 expected_root,
551 actual_root,
552 })) if expected_root == old_root && actual_root == new_root
553 ));
554 assert_eq!(forest.vault_root(id), Some(old_root));
555
556 let mut accepted = AccountUpdate::new();
557 accepted.vault_patch(id, &AccountVaultPatch::with_assets([new]), new_root);
558 forest.apply(2, accepted).unwrap();
559 assert_eq!(forest.vault_root(id), Some(new_root));
560 }
561}