1use alloc::boxed::Box;
2use alloc::collections::BTreeSet;
3use alloc::sync::Arc;
4use alloc::vec::Vec;
5
6use miden_protocol::account::{
7 Account,
8 AccountCode,
9 AccountId,
10 PartialAccount,
11 StorageMapKey,
12 StorageMapWitness,
13 StorageSlot,
14 StorageSlotContent,
15 StorageSlotName,
16};
17use miden_protocol::asset::{AssetId, AssetVault, AssetWitness};
18use miden_protocol::block::{BlockHeader, BlockNumber};
19use miden_protocol::crypto::merkle::MerklePath;
20use miden_protocol::crypto::merkle::mmr::{InOrderIndex, MmrPeaks, PartialMmr};
21use miden_protocol::note::{NoteScript, NoteScriptRoot};
22use miden_protocol::transaction::{AccountInputs, PartialBlockchain};
23use miden_protocol::vm::FutureMaybeSend;
24use miden_protocol::{Word, ZERO};
25use miden_tx::{
26 DataStore,
27 DataStoreError,
28 LoadedMastForest,
29 MastForestStore,
30 TransactionMastStore,
31};
32
33use super::{AccountStorageFilter, PartialBlockchainFilter, Store};
34use crate::rpc::domain::account::{
35 AccountStorageRequirements,
36 GetAccountRequest,
37 StorageMapEntries,
38 StorageMapFetch,
39 VaultFetch,
40};
41use crate::rpc::{AccountStateAt, NodeRpcClient};
42use crate::store::StoreError;
43use crate::transaction::{ChainAnchor, ChainAnchorError, fetch_public_account_inputs};
44
45mod cache;
46use cache::DataStoreCache;
47
48pub struct ClientDataStore {
53 store: alloc::sync::Arc<dyn Store>,
55 cache: DataStoreCache,
57 rpc_api: Arc<dyn NodeRpcClient>,
59 anchor: Option<Box<ChainAnchor>>,
63}
64
65impl ClientDataStore {
66 pub fn new(store: alloc::sync::Arc<dyn Store>, rpc_api: Arc<dyn NodeRpcClient>) -> Self {
67 Self {
68 store,
69 cache: DataStoreCache::new(),
70 rpc_api,
71 anchor: None,
72 }
73 }
74
75 #[must_use]
83 pub fn with_chain_anchor(mut self, anchor: ChainAnchor) -> Self {
84 self.anchor = Some(Box::new(anchor));
85 self
86 }
87
88 #[must_use]
96 pub fn with_execution_input_cache(mut self) -> Self {
97 self.cache.enable_execution_input_cache();
98 self
99 }
100
101 pub fn mast_store(&self) -> Arc<TransactionMastStore> {
102 self.cache.mast_store.clone()
103 }
104
105 pub fn register_foreign_account_inputs(
108 &self,
109 foreign_accounts: impl IntoIterator<Item = AccountInputs>,
110 ) {
111 self.cache.replace_foreign_account_inputs(foreign_accounts);
112 }
113
114 pub fn register_note_scripts(&self, note_scripts: impl IntoIterator<Item = NoteScript>) {
120 self.cache.insert_note_scripts(note_scripts);
121 }
122
123 async fn get_local_storage_map_witness(
130 &self,
131 account_id: AccountId,
132 map_root: Word,
133 map_key: StorageMapKey,
134 ) -> Result<Option<StorageMapWitness>, DataStoreError> {
135 match self
136 .store
137 .get_account_storage(account_id, AccountStorageFilter::Root(map_root))
138 .await
139 {
140 Ok(account_storage) => {
141 match account_storage.slots().first().map(StorageSlot::content) {
142 Some(StorageSlotContent::Map(map)) => Ok(Some(map.open(&map_key))),
143 Some(StorageSlotContent::Value(value)) => Err(DataStoreError::other(format!(
144 "found StorageSlotContent::Value with {value} as its value."
145 ))),
146 _ => Ok(None),
147 }
148 },
149 Err(err) => {
150 tracing::debug!(
151 %account_id,
152 %err,
153 "storage map not found locally, will try remote fetch"
154 );
155 Ok(None)
156 },
157 }
158 }
159
160 async fn fetch_and_cache_foreign_account(
163 &self,
164 account_id: AccountId,
165 account_state_at: AccountStateAt,
166 ) -> Result<AccountInputs, DataStoreError> {
167 let account_inputs = fetch_public_account_inputs(
168 &self.store,
169 &self.rpc_api,
170 account_id,
171 AccountStorageRequirements::default(),
172 account_state_at,
173 )
174 .await
175 .map_err(|err| {
176 DataStoreError::other_with_source("failed to fetch foreign account inputs", err)
177 })?;
178
179 self.cache.mast_store.load_account_code(account_inputs.code());
180 self.cache.insert_foreign_account_inputs(account_inputs.clone());
181
182 Ok(account_inputs)
183 }
184
185 async fn fetch_and_cache_storage_map_witness(
190 &self,
191 account_id: AccountId,
192 map_root: Word,
193 slot_name: StorageSlotName,
194 map_key: StorageMapKey,
195 known_code: AccountCode,
196 ) -> Result<StorageMapWitness, DataStoreError> {
197 let account_state_at =
198 self.cache.ref_block().map_or(AccountStateAt::ChainTip, AccountStateAt::Block);
199
200 let storage_requirements = AccountStorageRequirements::new([(slot_name, &[map_key])]);
201 let (_, account_proof): (BlockNumber, _) = self
202 .rpc_api
203 .get_account(
204 account_id,
205 GetAccountRequest::new()
206 .with_storage(StorageMapFetch::Slots(storage_requirements))
207 .with_known_code(Some(known_code))
208 .at(account_state_at),
209 )
210 .await
211 .map_err(|err| {
212 DataStoreError::other_with_source("failed to fetch storage map via RPC", err)
213 })?;
214
215 let (_, account_details) = account_proof.into_parts();
216 let details = account_details.ok_or_else(|| {
217 DataStoreError::other(format!(
218 "RPC returned no account details for account {account_id}"
219 ))
220 })?;
221
222 let map_detail =
223 details.storage_details.map_details.into_iter().next().ok_or_else(|| {
224 DataStoreError::other(format!(
225 "RPC returned no storage map details for account {account_id}"
226 ))
227 })?;
228
229 let StorageMapEntries::PartialMap { partial_smt, .. } = map_detail.entries else {
230 return Err(DataStoreError::other(
231 "expected a partial storage map in response to a specific-key request",
232 ));
233 };
234
235 let map_detail_root = partial_smt.root();
238 if map_detail_root != map_root {
239 return Err(DataStoreError::other(format!(
240 "storage map fetched for account {account_id} verifies against root \
241 {map_detail_root} but the executor requires root {map_root}"
242 )));
243 }
244
245 let proof = partial_smt.open(&map_key.hash().as_word()).map_err(|err| {
246 DataStoreError::other_with_source("failed to open the requested storage map key", err)
247 })?;
248
249 let witness = StorageMapWitness::new(proof, [map_key]).map_err(|err| {
250 DataStoreError::other_with_source("failed to create storage map witness", err)
251 })?;
252 self.cache.insert_storage_map_witness(map_root, map_key, witness.clone());
253 Ok(witness)
254 }
255
256 async fn fetch_vault_via_rpc(
261 &self,
262 account_id: AccountId,
263 vault_root: Word,
264 ) -> Result<AssetVault, DataStoreError> {
265 let account_state_at =
266 self.cache.ref_block().map_or(AccountStateAt::ChainTip, AccountStateAt::Block);
267
268 let known_code = self
270 .cache
271 .with_foreign_account_inputs(account_id, |inputs| inputs.code().clone());
272
273 let (block_num, mut account_proof) = self
274 .rpc_api
275 .get_account(
276 account_id,
277 GetAccountRequest::new()
278 .at(account_state_at)
279 .with_known_code(known_code)
280 .with_vault(VaultFetch::Always),
281 )
282 .await
283 .map_err(|err| {
284 DataStoreError::other_with_source("failed to fetch account vault via RPC", err)
285 })?;
286
287 let details = account_proof.details_mut().ok_or_else(|| {
288 DataStoreError::other(format!(
289 "RPC returned no account details for account {account_id}"
290 ))
291 })?;
292
293 self.rpc_api
294 .resolve_oversize_vault(account_id, block_num, details)
295 .await
296 .map_err(|err| {
297 DataStoreError::other_with_source("failed to resolve oversize vault via RPC", err)
298 })?;
299
300 let vault = AssetVault::new(&details.vault_details.assets).map_err(|err| {
301 DataStoreError::other_with_source("failed to build the fetched vault", err)
302 })?;
303
304 if vault.root() != vault_root {
305 return Err(DataStoreError::other(format!(
306 "vault fetched for account {account_id} has root {} but the executor requires \
307 root {vault_root}",
308 vault.root()
309 )));
310 }
311
312 Ok(vault)
313 }
314}
315
316impl DataStore for ClientDataStore {
317 async fn get_transaction_inputs(
318 &self,
319 account_id: AccountId,
320 mut block_refs: BTreeSet<BlockNumber>,
321 ) -> Result<(PartialAccount, BlockHeader, PartialBlockchain), DataStoreError> {
322 let ref_block = *block_refs.last().ok_or(DataStoreError::other("block set is empty"))?;
324
325 self.cache.set_ref_block(ref_block);
327
328 let partial_account =
329 if let Some(partial_account) = self.cache.get_partial_account(account_id) {
330 partial_account
331 } else {
332 let partial_account_record = self
333 .store
334 .get_minimal_partial_account(account_id)
335 .await?
336 .ok_or(DataStoreError::AccountNotFound(account_id))?;
337
338 let partial_account: PartialAccount = if partial_account_record.nonce() == ZERO {
343 let full_record = self
344 .store
345 .get_account(account_id)
346 .await?
347 .ok_or(DataStoreError::AccountNotFound(account_id))?;
348 let account: Account = full_record
349 .try_into()
350 .map_err(|_| DataStoreError::AccountNotFound(account_id))?;
351 PartialAccount::from(&account)
352 } else {
353 partial_account_record
354 .try_into()
355 .map_err(|_| DataStoreError::AccountNotFound(account_id))?
356 };
357
358 self.cache.insert_partial_account(&partial_account);
359 partial_account
360 };
361
362 let (block_header, partial_blockchain) = if let Some(anchor) = &self.anchor {
363 if ref_block != anchor.block_num() {
367 return Err(DataStoreError::other_with_source(
368 "anchored data store cannot serve the requested reference block",
369 ChainAnchorError::ReferenceBlockMismatch {
370 requested: ref_block,
371 anchor: anchor.block_num(),
372 },
373 ));
374 }
375
376 for block_num in block_refs.iter().filter(|block_num| **block_num != ref_block) {
377 if !anchor.partial_blockchain().contains_block(*block_num) {
378 return Err(DataStoreError::other_with_source(
379 "anchored data store cannot serve an untracked block",
380 ChainAnchorError::BlockNotTracked { block_num: *block_num },
381 ));
382 }
383 }
384
385 (anchor.header().clone(), anchor.partial_blockchain().clone())
386 } else if let Some((block_header, partial_blockchain)) =
387 self.cache.get_blockchain(&block_refs)
388 {
389 (block_header, partial_blockchain)
390 } else {
391 let cache_key = block_refs.clone();
394 block_refs.remove(&ref_block);
395
396 let current_peaks = self.store.get_current_blockchain_peaks().await?;
397
398 let (block_header, _had_notes) = self
400 .store
401 .get_block_header_by_num(ref_block)
402 .await?
403 .ok_or(DataStoreError::BlockNotFound(ref_block))?;
404
405 let block_headers: Vec<BlockHeader> = self
406 .store
407 .get_block_headers(&block_refs)
408 .await?
409 .into_iter()
410 .map(|(header, _has_notes)| header)
411 .collect();
412
413 let partial_mmr =
417 build_partial_mmr_with_paths(&self.store, current_peaks, &block_headers).await?;
418
419 let partial_blockchain =
420 PartialBlockchain::new(partial_mmr, block_headers).map_err(|err| {
421 DataStoreError::other_with_source(
422 "error creating PartialBlockchain from internal data",
423 err,
424 )
425 })?;
426
427 self.cache.insert_blockchain(cache_key, &block_header, &partial_blockchain);
428 (block_header, partial_blockchain)
429 };
430
431 Ok((partial_account, block_header, partial_blockchain))
432 }
433
434 async fn get_vault_asset_witnesses(
440 &self,
441 account_id: AccountId,
442 vault_root: Word,
443 asset_ids: BTreeSet<AssetId>,
444 ) -> Result<Vec<AssetWitness>, DataStoreError> {
445 if let Some(witnesses) = self.cache.get_vault_asset_witnesses(vault_root, &asset_ids) {
446 return Ok(witnesses);
447 }
448
449 let asset_witnesses = match self
450 .store
451 .get_vault_asset_witnesses(account_id, vault_root, asset_ids.clone())
452 .await
453 {
454 Ok(witnesses) => witnesses,
455 Err(err) => {
456 tracing::debug!(
457 %account_id,
458 requested_root = %vault_root,
459 %err,
460 "local store cannot serve the requested vault root, will fetch it via RPC"
461 );
462 let vault = self.fetch_vault_via_rpc(account_id, vault_root).await?;
463 asset_ids.iter().copied().map(|asset_id| vault.open(asset_id)).collect()
464 },
465 };
466
467 self.cache
468 .insert_vault_asset_witnesses(vault_root, &asset_ids, &asset_witnesses);
469 Ok(asset_witnesses)
470 }
471
472 async fn get_storage_map_witness(
477 &self,
478 account_id: AccountId,
479 map_root: Word,
480 map_key: StorageMapKey,
481 ) -> Result<StorageMapWitness, DataStoreError> {
482 if let Some(witness) = self.cache.get_storage_map_witness(map_root, map_key) {
484 return Ok(witness);
485 }
486
487 if let Some(witness) =
489 self.get_local_storage_map_witness(account_id, map_root, map_key).await?
490 {
491 return Ok(witness);
492 }
493
494 let resolution = if let Some(resolution) =
497 self.cache.with_foreign_account_inputs(account_id, |inputs| {
498 resolve_witness_from_inputs(inputs, map_root, map_key)
499 }) {
500 resolution?
501 } else {
502 let account_state_at = self
503 .cache
504 .ref_block()
505 .map(AccountStateAt::Block)
506 .expect("reference block should be set");
507 let inputs = self.fetch_and_cache_foreign_account(account_id, account_state_at).await?;
508 resolve_witness_from_inputs(&inputs, map_root, map_key)?
509 };
510
511 match resolution {
512 WitnessResolution::Witness(witness) => Ok(witness),
513 WitnessResolution::FetchParams(slot_name, known_code) => {
514 self.fetch_and_cache_storage_map_witness(
515 account_id, map_root, slot_name, map_key, known_code,
516 )
517 .await
518 },
519 }
520 }
521
522 async fn get_foreign_account_inputs(
525 &self,
526 foreign_account_id: AccountId,
527 ref_block: BlockNumber,
528 ) -> Result<AccountInputs, DataStoreError> {
529 if let Some(inputs) = self.cache.get_foreign_account_inputs(foreign_account_id) {
531 return Ok(inputs);
532 }
533
534 self.fetch_and_cache_foreign_account(foreign_account_id, AccountStateAt::Block(ref_block))
535 .await
536 }
537
538 fn get_note_script(
541 &self,
542 script_root: NoteScriptRoot,
543 ) -> impl FutureMaybeSend<Result<Option<NoteScript>, DataStoreError>> {
544 let registered_script = self.cache.get_note_script(script_root.into());
545 let store = self.store.clone();
546 let rpc_api = self.rpc_api.clone();
547
548 async move {
549 if let Some(note_script) = registered_script {
551 return Ok(Some(note_script));
552 }
553
554 match store.get_note_script(script_root.into()).await {
556 Ok(note_script) => return Ok(Some(note_script)),
557 Err(StoreError::NoteScriptNotFound(_)) => {},
558 Err(err) => {
559 return Err(DataStoreError::other_with_source(
560 format!("failed to get note script {script_root} from store"),
561 err,
562 ));
563 },
564 }
565
566 let Some(note_script) =
568 rpc_api.get_note_script_by_root(script_root.into()).await.map_err(|err| {
569 DataStoreError::other_with_source("failed to fetch note script via RPC", err)
570 })?
571 else {
572 return Ok(None);
573 };
574
575 if let Err(err) = store.upsert_note_scripts(core::slice::from_ref(¬e_script)).await {
577 tracing::warn!(
578 %err,
579 "Failed to persist fetched note script to store"
580 );
581 }
582
583 Ok(Some(note_script))
584 }
585 }
586}
587
588impl MastForestStore for ClientDataStore {
592 fn get(&self, procedure_hash: &Word) -> Option<LoadedMastForest> {
593 self.cache.mast_store.get(procedure_hash)
594 }
595}
596
597enum WitnessResolution {
603 Witness(StorageMapWitness),
604 FetchParams(StorageSlotName, AccountCode),
607}
608
609fn resolve_witness_from_inputs(
613 inputs: &AccountInputs,
614 map_root: Word,
615 map_key: StorageMapKey,
616) -> Result<WitnessResolution, DataStoreError> {
617 if let Some(partial_map) = inputs.storage().maps().find(|m| m.root() == map_root)
618 && let Ok(witness) = partial_map.open(&map_key)
619 {
620 return Ok(WitnessResolution::Witness(witness));
621 }
622
623 let account_id = inputs.id();
624 let slot_name = inputs
625 .storage()
626 .header()
627 .slots()
628 .find(|slot| slot.slot_type().is_map() && slot.value() == map_root)
629 .map(|slot| slot.name().clone())
630 .ok_or_else(|| {
631 DataStoreError::other(format!(
632 "did not find map slot with root {map_root} for foreign account {account_id}"
633 ))
634 })?;
635
636 Ok(WitnessResolution::FetchParams(slot_name, inputs.code().clone()))
637}
638
639pub(crate) async fn build_partial_mmr_with_paths(
645 store: &alloc::sync::Arc<dyn Store>,
646 peaks: MmrPeaks,
647 authenticated_blocks: &[BlockHeader],
648) -> Result<PartialMmr, DataStoreError> {
649 let mut partial_mmr: PartialMmr = PartialMmr::from_peaks(peaks);
650
651 let block_nums: Vec<BlockNumber> =
652 authenticated_blocks.iter().map(BlockHeader::block_num).collect();
653
654 let authentication_paths =
655 get_authentication_path_for_blocks(store, &block_nums, partial_mmr.forest().num_leaves())
656 .await?;
657
658 for (header, path) in authenticated_blocks.iter().zip(authentication_paths.iter()) {
659 partial_mmr
660 .track(header.block_num().as_usize(), header.commitment(), path)
661 .map_err(|err| DataStoreError::other(format!("error constructing MMR: {err}")))?;
662 }
663
664 Ok(partial_mmr)
665}
666
667async fn get_authentication_path_for_blocks(
673 store: &alloc::sync::Arc<dyn Store>,
674 block_nums: &[BlockNumber],
675 forest: usize,
676) -> Result<Vec<MerklePath>, StoreError> {
677 let mut node_indices = BTreeSet::new();
678
679 for block_num in block_nums {
681 let path_depth = mmr_merkle_path_len(block_num.as_usize(), forest);
682
683 let mut idx = InOrderIndex::from_leaf_pos(block_num.as_usize());
684
685 for _ in 0..path_depth {
686 node_indices.insert(idx.sibling());
687 idx = idx.parent();
688 }
689 }
690
691 let node_indices: Vec<InOrderIndex> = node_indices.into_iter().collect();
693
694 let filter = PartialBlockchainFilter::List(node_indices);
695 let mmr_nodes = store.get_partial_blockchain_nodes(filter).await?;
696
697 let mut authentication_paths = vec![];
699 for block_num in block_nums {
700 let mut merkle_nodes = vec![];
701 let mut idx = InOrderIndex::from_leaf_pos(block_num.as_usize());
702
703 while let Some(node) = mmr_nodes.get(&idx.sibling()) {
704 merkle_nodes.push(*node);
705 idx = idx.parent();
706 }
707 let path = MerklePath::new(merkle_nodes);
708 authentication_paths.push(path);
709 }
710
711 Ok(authentication_paths)
712}
713
714fn mmr_merkle_path_len(leaf_index: usize, forest: usize) -> usize {
718 let before: usize = forest & leaf_index;
719 let after = forest ^ before;
720
721 after.ilog2() as usize
722}