1use alloc::boxed::Box;
2use alloc::collections::{BTreeMap, BTreeSet};
3use alloc::string::String;
4use alloc::sync::Arc;
5use alloc::vec::Vec;
6use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
7
8use miden_protocol::Word;
9use miden_protocol::account::{
10 AccountId,
11 AccountUpdateDetails,
12 AccountVaultPatch,
13 StorageMapKey,
14 StorageMapPatchEntries,
15 StorageSlot,
16 StorageSlotContent,
17 StorageSlotName,
18 StorageSlotType,
19};
20use miden_protocol::address::NetworkId;
21use miden_protocol::batch::{ProposedBatch, ProvenBatch};
22use miden_protocol::block::{BlockHeader, BlockNumber, SignedBlock};
23use miden_protocol::crypto::merkle::MerklePath;
24use miden_protocol::crypto::merkle::mmr::{Forest, Mmr, MmrProof};
25use miden_protocol::crypto::merkle::smt::PartialSmt;
26use miden_protocol::note::{NoteAttachments, NoteHeader, NoteId, NoteScript, NoteTag};
27use miden_protocol::protocol_config::ProtocolConfig;
28use miden_protocol::transaction::{ExecutedTransaction, OutputNote, ProvenTransaction};
29use miden_protocol::vm::ExecutionProof;
30use miden_testing::{MockChain, MockChainNote};
31use miden_tx::utils::sync::RwLock;
32
33use crate::Client;
34use crate::rpc::domain::account::{
35 AccountDetails,
36 AccountProof,
37 AccountStorageDetails,
38 AccountStorageMapDetails,
39 AccountVaultDetails,
40 GetAccountRequest,
41 StorageMapEntries,
42 StorageMapEntry,
43 StorageMapFetch,
44 VaultFetch,
45};
46use crate::rpc::domain::account_vault::AccountVaultInfo;
47use crate::rpc::domain::note::{CommittedNote, FetchedNote, SyncNotesBlock};
48use crate::rpc::domain::nullifier::NullifierUpdate;
49use crate::rpc::domain::status::NetworkNoteStatusInfo;
50use crate::rpc::domain::storage_map::StorageMapInfo;
51use crate::rpc::domain::sync::{ChainMmrInfo, SyncTarget};
52use crate::rpc::domain::transaction::TransactionRecord;
53use crate::rpc::encryption::{AttestedTransactionEncryptionKey, SealedTransactionInputs};
54use crate::rpc::{AccountStateAt, NodeRpcClient, RpcEndpoint, RpcError, RpcStatusInfo};
55
56pub type MockClient<AUTH> = Client<AUTH>;
57
58#[derive(Clone, Copy)]
59struct BlockHeaderRequest {
60 block_num: Option<BlockNumber>,
61 include_mmr_proof: bool,
62}
63
64#[derive(Clone)]
75pub struct MockRpcApi {
76 account_commitment_updates: Arc<RwLock<BTreeMap<BlockNumber, BTreeMap<AccountId, Word>>>>,
77 pub mock_chain: Arc<RwLock<MockChain>>,
78 historical_chains: Arc<RwLock<BTreeMap<BlockNumber, Arc<MockChain>>>>,
80 oversize_threshold: usize,
81 erased_notes: Arc<RwLock<Vec<NoteHeader>>>,
83 private_note_attachments: Arc<RwLock<BTreeMap<NoteId, NoteAttachments>>>,
87 sync_notes_mmr_path_overrides: Arc<RwLock<BTreeMap<BlockNumber, MerklePath>>>,
89 get_notes_by_id_calls: Arc<AtomicUsize>,
92 block_header_requests: Arc<RwLock<Vec<BlockHeaderRequest>>>,
94 get_account_calls: Arc<AtomicUsize>,
97 next_call_failures: Arc<RwLock<BTreeMap<&'static str, RpcError>>>,
101 registered_accounts: Arc<RwLock<BTreeMap<AccountId, String>>>,
103 allowlist_enforced: Arc<AtomicBool>,
107 is_account_allowed_calls: Arc<AtomicUsize>,
110 submitted_batch_sealed_inputs: Arc<RwLock<Vec<Vec<SealedTransactionInputs>>>>,
114}
115
116impl Default for MockRpcApi {
117 fn default() -> Self {
118 Self::new(MockChain::new())
119 }
120}
121
122impl MockRpcApi {
123 const PAGINATION_BLOCK_LIMIT: u32 = 5;
125
126 pub fn new(mock_chain: MockChain) -> Self {
128 Self {
129 account_commitment_updates: Arc::new(RwLock::new(build_account_updates(&mock_chain))),
130 mock_chain: Arc::new(RwLock::new(mock_chain)),
131 historical_chains: Arc::new(RwLock::new(BTreeMap::new())),
132 oversize_threshold: 1000,
133 erased_notes: Arc::new(RwLock::new(Vec::new())),
134 private_note_attachments: Arc::new(RwLock::new(BTreeMap::new())),
135 sync_notes_mmr_path_overrides: Arc::new(RwLock::new(BTreeMap::new())),
136 get_notes_by_id_calls: Arc::new(AtomicUsize::new(0)),
137 block_header_requests: Arc::new(RwLock::new(Vec::new())),
138 get_account_calls: Arc::new(AtomicUsize::new(0)),
139 next_call_failures: Arc::new(RwLock::new(BTreeMap::new())),
140 registered_accounts: Arc::new(RwLock::new(BTreeMap::new())),
141 allowlist_enforced: Arc::new(AtomicBool::new(false)),
142 is_account_allowed_calls: Arc::new(AtomicUsize::new(0)),
143 submitted_batch_sealed_inputs: Arc::new(RwLock::new(Vec::new())),
144 }
145 }
146
147 pub fn enforce_account_allowlist(&self) {
150 self.allowlist_enforced.store(true, Ordering::SeqCst);
151 }
152
153 pub fn first_account_id(&self) -> AccountId {
158 self.mock_chain
159 .read()
160 .proven_blocks()
161 .iter()
162 .flat_map(|block| block.body().updated_accounts())
163 .next()
164 .expect("the mock chain must have at least one account update")
165 .account_id()
166 }
167
168 pub fn submitted_batch_sealed_inputs(&self) -> Vec<Vec<SealedTransactionInputs>> {
172 self.submitted_batch_sealed_inputs.read().clone()
173 }
174
175 pub fn fail_next_call(&self, endpoint: RpcEndpoint, error: RpcError) {
181 self.next_call_failures.write().insert(endpoint.proto_name(), error);
182 }
183
184 fn take_failure(&self, endpoint: RpcEndpoint) -> Option<RpcError> {
186 self.next_call_failures.write().remove(endpoint.proto_name())
187 }
188
189 pub fn registered_invitation_code(&self, account_id: AccountId) -> Option<String> {
192 self.registered_accounts.read().get(&account_id).cloned()
193 }
194
195 pub fn register_private_note_attachments(&self, note_id: NoteId, attachments: NoteAttachments) {
198 self.private_note_attachments.write().insert(note_id, attachments);
199 }
200
201 pub fn get_notes_by_id_call_count(&self) -> usize {
203 self.get_notes_by_id_calls.load(Ordering::Relaxed)
204 }
205
206 pub fn block_header_requests(&self, block_num: BlockNumber) -> Vec<bool> {
208 self.block_header_requests
209 .read()
210 .iter()
211 .filter_map(|request| {
212 (request.block_num == Some(block_num)).then_some(request.include_mmr_proof)
213 })
214 .collect()
215 }
216
217 pub fn get_account_call_count(&self) -> usize {
219 self.get_account_calls.load(Ordering::Relaxed)
220 }
221
222 pub fn is_account_allowed_call_count(&self) -> usize {
224 self.is_account_allowed_calls.load(Ordering::Relaxed)
225 }
226
227 pub fn set_sync_notes_mmr_path(&self, block_num: BlockNumber, path: MerklePath) {
229 self.sync_notes_mmr_path_overrides.write().insert(block_num, path);
230 }
231
232 #[must_use]
236 pub fn with_oversize_threshold(mut self, threshold: usize) -> Self {
237 self.oversize_threshold = threshold;
238 self
239 }
240
241 pub fn mark_note_as_erased(&self, header: NoteHeader) {
243 self.erased_notes.write().push(header);
244 }
245
246 pub fn get_mmr(&self) -> Mmr {
248 self.mock_chain.read().blockchain().as_mmr().clone()
249 }
250
251 pub fn protocol_config(&self) -> ProtocolConfig {
253 self.mock_chain.read().protocol_config().clone()
254 }
255
256 pub fn get_chain_tip_block_num(&self) -> BlockNumber {
258 self.mock_chain.read().latest_block_header().block_num()
259 }
260
261 pub fn add_pending_executed_transaction(&self, executed_transaction: &ExecutedTransaction) {
266 self.mock_chain
267 .write()
268 .add_pending_executed_transaction(executed_transaction)
269 .expect("mock chain should accept the executed transaction");
270 }
271
272 pub fn prove_block(&self) {
275 let proven_block = {
276 let mut mock_chain = self.mock_chain.write();
277 let historical_block_num = mock_chain.latest_block_header().block_num();
278 let snapshot = Arc::new(mock_chain.clone());
279 let proven_block = mock_chain.prove_next_block().unwrap();
280 self.historical_chains.write().insert(historical_block_num, snapshot);
281 proven_block
282 };
283 let block_num = proven_block.header().block_num();
284 let mut account_commitment_updates = self.account_commitment_updates.write();
285 let updates: BTreeMap<AccountId, Word> = proven_block
286 .body()
287 .updated_accounts()
288 .iter()
289 .map(|update| (update.account_id(), update.final_state_commitment()))
290 .collect();
291
292 if !updates.is_empty() {
293 account_commitment_updates.insert(block_num, updates);
294 }
295 }
296
297 pub fn prune_account_state_at(&self, block_num: BlockNumber) {
301 self.historical_chains.write().remove(&block_num);
302 }
303
304 fn get_block_by_num(&self, block_num: BlockNumber) -> BlockHeader {
306 self.mock_chain.read().block_header(block_num.as_usize())
307 }
308
309 fn get_sync_account_vault_request(
312 &self,
313 block_from: BlockNumber,
314 block_to: BlockNumber,
315 account_id: AccountId,
316 ) -> (BlockNumber, BlockNumber, AccountVaultPatch) {
317 let chain_tip = self.get_chain_tip_block_num();
318 let target_block = block_to.min(chain_tip);
319
320 let page_end_block: BlockNumber = (block_from.as_u32() + Self::PAGINATION_BLOCK_LIMIT)
321 .min(target_block.as_u32())
322 .into();
323
324 let mut vault_patch = AccountVaultPatch::default();
326 for block in self.mock_chain.read().proven_blocks() {
327 let block_number = block.header().block_num();
328 if block_number < block_from || block_number > page_end_block {
330 continue;
331 }
332
333 for update in block
334 .body()
335 .updated_accounts()
336 .iter()
337 .filter(|block_acc_update| block_acc_update.account_id() == account_id)
338 {
339 let AccountUpdateDetails::Public(patch) = update.details().clone() else {
340 continue;
341 };
342
343 vault_patch.merge(patch.vault().clone());
344 }
345 }
346
347 (chain_tip, page_end_block, vault_patch)
348 }
349
350 fn get_sync_transactions_request(
352 &self,
353 block_from: BlockNumber,
354 block_to: BlockNumber,
355 account_ids: &[AccountId],
356 ) -> Vec<TransactionRecord> {
357 let mut transactions = Vec::new();
358 for block in self.mock_chain.read().proven_blocks() {
359 let block_number = block.header().block_num();
360 if block_number < block_from || block_number > block_to {
361 continue;
362 }
363
364 for transaction_header in block.body().transactions().as_slice() {
365 if !account_ids.contains(&transaction_header.account_id()) {
366 continue;
367 }
368
369 let erased_output_notes = self.erased_notes.read().clone();
370
371 transactions.push(TransactionRecord {
372 block_num: block_number,
373 transaction_header: transaction_header.clone(),
374 output_notes: vec![],
375 erased_output_notes,
376 consumed_note_refs: vec![],
377 });
378 }
379 }
380
381 transactions
382 }
383
384 fn get_sync_storage_maps_request(
388 &self,
389 block_from: BlockNumber,
390 block_to: BlockNumber,
391 account_id: AccountId,
392 ) -> (BlockNumber, BlockNumber, BTreeMap<StorageSlotName, StorageMapPatchEntries>) {
393 let chain_tip = self.get_chain_tip_block_num();
394 let target_block = block_to.min(chain_tip);
395
396 let page_end_block: BlockNumber = (block_from.as_u32() + Self::PAGINATION_BLOCK_LIMIT)
397 .min(target_block.as_u32())
398 .into();
399
400 let mut map_entries: BTreeMap<StorageSlotName, StorageMapPatchEntries> = BTreeMap::new();
402 for block in self.mock_chain.read().proven_blocks() {
403 let block_number = block.header().block_num();
404 if block_number < block_from || block_number > page_end_block {
406 continue;
407 }
408
409 for update in block
410 .body()
411 .updated_accounts()
412 .iter()
413 .filter(|block_acc_update| block_acc_update.account_id() == account_id)
414 {
415 let AccountUpdateDetails::Public(patch) = update.details().clone() else {
416 continue;
417 };
418
419 for (slot_name, map_patch) in patch.storage().maps() {
420 if let Some(entries) = map_patch.entries() {
421 map_entries
422 .entry(slot_name.clone())
423 .or_default()
424 .as_map_mut()
425 .extend(entries.as_map().clone());
426 }
427 }
428 }
429 }
430
431 (chain_tip, page_end_block, map_entries)
432 }
433
434 pub fn get_available_notes(&self) -> Vec<MockChainNote> {
435 self.mock_chain.read().committed_notes().values().cloned().collect()
436 }
437
438 pub fn get_public_available_notes(&self) -> Vec<MockChainNote> {
439 self.mock_chain
440 .read()
441 .committed_notes()
442 .values()
443 .filter(|n| matches!(n, MockChainNote::Public(_, _)))
444 .cloned()
445 .collect()
446 }
447
448 pub fn advance_blocks(&self, num_blocks: u32) {
449 let mut mock_chain = self.mock_chain.write();
450 let block_num = mock_chain.latest_block_header().block_num();
451 let snapshot = Arc::new(mock_chain.clone());
452 mock_chain.prove_until_block(block_num + num_blocks).unwrap();
453 self.historical_chains.write().insert(block_num, snapshot);
454 }
455}
456#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
457#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
458impl NodeRpcClient for MockRpcApi {
459 fn has_genesis_commitment(&self) -> Option<Word> {
465 None
466 }
467
468 async fn set_genesis_commitment(&self, _commitment: Word) -> Result<(), RpcError> {
469 Ok(())
471 }
472
473 async fn sync_notes(
476 &self,
477 block_from: BlockNumber,
478 block_to: BlockNumber,
479 note_tags: &BTreeSet<NoteTag>,
480 ) -> Result<Vec<SyncNotesBlock>, RpcError> {
481 let mut blocks_with_notes: BTreeMap<BlockNumber, BTreeMap<NoteId, CommittedNote>> =
482 BTreeMap::new();
483 for note in self.mock_chain.read().committed_notes().values() {
484 let note_block = note.inclusion_proof().location().block_num();
485 if note_tags.contains(¬e.metadata().tag())
486 && note_block >= block_from
487 && note_block <= block_to
488 {
489 let mut committed =
490 CommittedNote::new(note.id(), *note.metadata(), note.inclusion_proof().clone());
491 let attachments = note.attachments();
494 if attachments.iter().all(|attachment| attachment.num_words() == 1) {
495 committed = committed
496 .with_attachments(attachments.clone())
497 .expect("the note's own attachments match its commitment");
498 }
499 blocks_with_notes.entry(note_block).or_default().insert(note.id(), committed);
500 }
501 }
502
503 Ok(blocks_with_notes
504 .into_iter()
505 .map(|(bn, notes)| {
506 let block_header = self.get_block_by_num(bn);
507 let mmr_path =
508 self.sync_notes_mmr_path_overrides.read().get(&bn).cloned().unwrap_or_else(
509 || self.get_mmr().open(bn.as_usize()).unwrap().merkle_path().clone(),
510 );
511 SyncNotesBlock { block_header, mmr_path, notes }
512 })
513 .collect())
514 }
515
516 async fn sync_chain_mmr(
517 &self,
518 current_block_height: BlockNumber,
519 upper_bound: SyncTarget,
520 ) -> Result<ChainMmrInfo, RpcError> {
521 let chain_tip = self.get_chain_tip_block_num();
522 let target_block = match upper_bound {
524 SyncTarget::CommittedChainTip | SyncTarget::ProvenChainTip => chain_tip,
525 };
526
527 let from_forest = if current_block_height == target_block {
528 target_block.as_usize()
529 } else {
530 current_block_height.as_u32() as usize + 1
531 };
532
533 let mmr_delta = self
534 .get_mmr()
535 .get_delta(
536 Forest::new(from_forest).unwrap(),
537 Forest::new(target_block.as_usize()).unwrap(),
538 )
539 .unwrap();
540
541 let block_header = self.get_block_by_num(target_block);
542 let block_signatures = self
543 .mock_chain
544 .read()
545 .proven_blocks()
546 .iter()
547 .find(|block| block.header().block_num() == target_block)
548 .expect("the mock chain contains the target block")
549 .signatures()
550 .clone();
551
552 let protocol_config = if current_block_height == BlockNumber::GENESIS {
555 Some(self.protocol_config())
556 } else if current_block_height == target_block {
557 None
558 } else {
559 let commitment_at_start =
560 self.get_block_by_num(current_block_height).protocol_config_commitment();
561 (commitment_at_start != block_header.protocol_config_commitment())
562 .then(|| self.protocol_config())
563 };
564
565 Ok(ChainMmrInfo {
566 block_from: current_block_height,
567 block_to: target_block,
568 mmr_delta,
569 block_header,
570 protocol_config,
571 block_signatures,
572 })
573 }
574
575 async fn get_block_header_by_number(
578 &self,
579 block_num: Option<BlockNumber>,
580 include_mmr_proof: bool,
581 ) -> Result<(BlockHeader, Option<MmrProof>), RpcError> {
582 self.block_header_requests
583 .write()
584 .push(BlockHeaderRequest { block_num, include_mmr_proof });
585
586 let block = if let Some(block_num) = block_num {
587 self.mock_chain.read().block_header(block_num.as_usize())
588 } else {
589 self.mock_chain.read().latest_block_header()
590 };
591
592 let mmr_proof = if include_mmr_proof {
593 Some(self.get_mmr().open(block_num.unwrap().as_usize()).unwrap())
594 } else {
595 None
596 };
597
598 Ok((block, mmr_proof))
599 }
600
601 async fn get_notes_by_id(&self, note_ids: &[NoteId]) -> Result<Vec<FetchedNote>, RpcError> {
603 self.get_notes_by_id_calls.fetch_add(1, Ordering::Relaxed);
604
605 let notes = self.mock_chain.read().committed_notes().clone();
606
607 let hit_notes = note_ids.iter().filter_map(|id| notes.get(id));
608 let mut return_notes = vec![];
609 for note in hit_notes {
610 let fetched_note = match note {
611 MockChainNote::Private(note_id, note_metadata, _, note_inclusion_proof) => {
612 let attachments = self
613 .private_note_attachments
614 .read()
615 .get(note_id)
616 .cloned()
617 .unwrap_or_else(NoteAttachments::empty);
618 FetchedNote::Private(
619 *note_id,
620 *note_metadata,
621 attachments,
622 note_inclusion_proof.clone(),
623 )
624 },
625 MockChainNote::Public(note, note_inclusion_proof) => {
626 FetchedNote::Public(note.clone(), note_inclusion_proof.clone())
627 },
628 };
629 return_notes.push(fetched_note);
630 }
631 Ok(return_notes)
632 }
633
634 async fn get_transaction_encryption_key(
638 &self,
639 ) -> Result<AttestedTransactionEncryptionKey, RpcError> {
640 Err(RpcError::TransactionEncryptionKeyRejected(
641 "the mock RPC client does not serve a transaction encryption key".into(),
642 ))
643 }
644
645 async fn submit_proven_transaction(
648 &self,
649 proven_transaction: &ProvenTransaction,
650 _sealed_transaction_inputs: SealedTransactionInputs, ) -> Result<BlockNumber, RpcError> {
653 if let Some(error) = self.take_failure(RpcEndpoint::SubmitProvenTx) {
654 return Err(error);
655 }
656
657 for note in proven_transaction.output_notes().iter() {
661 if let OutputNote::Private(private_note) = note
662 && !private_note.attachments().is_empty()
663 {
664 self.private_note_attachments
665 .write()
666 .insert(private_note.id(), private_note.attachments().clone());
667 }
668 }
669
670 {
671 let mut mock_chain = self.mock_chain.write();
672 mock_chain.add_pending_proven_transaction(proven_transaction.clone());
673 };
674
675 let block_num = self.get_chain_tip_block_num();
676
677 Ok(block_num)
678 }
679
680 async fn submit_proven_batch(
685 &self,
686 proven_batch: &ProvenBatch,
687 _proposed_batch: &ProposedBatch,
688 sealed_transaction_inputs: Vec<SealedTransactionInputs>,
689 ) -> Result<BlockNumber, RpcError> {
690 self.submitted_batch_sealed_inputs.write().push(sealed_transaction_inputs);
693
694 if let Some(error) = self.take_failure(RpcEndpoint::SubmitProvenBatch) {
695 return Err(error);
696 }
697
698 let mut mock_chain = self.mock_chain.write();
699 mock_chain.add_pending_batch(proven_batch.clone());
700 drop(mock_chain);
701
702 let block_num = self.get_chain_tip_block_num();
703
704 Ok(block_num)
705 }
706
707 async fn get_account(
711 &self,
712 account_id: AccountId,
713 request: GetAccountRequest,
714 ) -> Result<(BlockNumber, AccountProof), RpcError> {
715 self.get_account_calls.fetch_add(1, Ordering::Relaxed);
716
717 if let Some(error) = self.take_failure(RpcEndpoint::GetAccount) {
718 return Err(error);
719 }
720
721 let current_chain = self.mock_chain.read();
722 let current_block_number = current_chain.latest_block_header().block_num();
723 let block_number = match request.at {
724 AccountStateAt::Block(number) => number,
725 AccountStateAt::ChainTip => current_block_number,
726 };
727 let historical_chain = match request.at {
728 AccountStateAt::Block(_) if block_number != current_block_number => Some(
729 self.historical_chains.read().get(&block_number).cloned().ok_or_else(|| {
730 RpcError::InvalidResponse(alloc::format!(
731 "no mock chain snapshot at block {block_number}"
732 ))
733 })?,
734 ),
735 AccountStateAt::ChainTip | AccountStateAt::Block(_) => None,
736 };
737 let mock_chain = historical_chain.as_deref().unwrap_or(&*current_chain);
738
739 let headers = if account_id.is_public() {
740 let account = mock_chain.committed_account(account_id).unwrap();
741
742 let requested_slots: Vec<(StorageSlotName, Vec<StorageMapKey>)> = match &request.storage
746 {
747 StorageMapFetch::Skip => Vec::new(),
748 StorageMapFetch::Slots(reqs) => {
749 reqs.inner().iter().map(|(name, keys)| (name.clone(), keys.clone())).collect()
750 },
751 StorageMapFetch::All => account
752 .storage()
753 .to_header()
754 .slots()
755 .filter(|slot| slot.slot_type() == StorageSlotType::Map)
756 .map(|slot| (slot.name().clone(), Vec::new()))
757 .collect(),
758 };
759
760 let mut map_details = vec![];
761 for (slot_name, requested_keys) in &requested_slots {
762 if let Some(StorageSlotContent::Map(storage_map)) =
763 account.storage().get(slot_name).map(StorageSlot::content)
764 {
765 let entries = if requested_keys.is_empty() {
769 let entries: Vec<StorageMapEntry> = storage_map
770 .entries()
771 .map(|(key, value)| StorageMapEntry { key: *key, value: *value })
772 .collect();
773
774 if entries.len() > self.oversize_threshold {
775 StorageMapEntries::LimitExceeded
776 } else {
777 StorageMapEntries::AllEntries(entries)
778 }
779 } else {
780 let partial_smt = PartialSmt::from_proofs(
781 requested_keys.iter().map(|key| storage_map.open(key).into()),
782 )
783 .expect("proofs from one map share a root");
784
785 StorageMapEntries::PartialMap {
786 map_keys: requested_keys.clone(),
787 partial_smt,
788 }
789 };
790
791 map_details
792 .push(AccountStorageMapDetails { slot_name: slot_name.clone(), entries });
793 } else {
794 panic!("Storage slot {slot_name} is not a map");
795 }
796 }
797
798 let storage_details = AccountStorageDetails {
799 header: account.storage().to_header(),
800 map_details,
801 };
802
803 let include_assets = match request.vault {
806 VaultFetch::Skip => false,
807 VaultFetch::Always => true,
808 VaultFetch::IfChangedFrom(root) => root != account.vault().root(),
809 };
810 let mut assets = vec![];
811 if include_assets {
812 for asset in account.vault().assets() {
813 assets.push(asset);
814 }
815 }
816 let vault_details = AccountVaultDetails {
817 too_many_assets: assets.len() > self.oversize_threshold,
818 assets,
819 };
820
821 Some(AccountDetails {
822 header: account.into(),
823 storage_details,
824 code: account.code().clone(),
825 vault_details,
826 })
827 } else {
828 None
829 };
830
831 let witness = mock_chain.account_tree().open(account_id);
832
833 let proof = AccountProof::new(witness, headers).unwrap();
834
835 Ok((block_number, proof))
836 }
837
838 async fn register_account(
839 &self,
840 invitation_code: &str,
841 account_id: AccountId,
842 ) -> Result<(), RpcError> {
843 if let Some(error) = self.take_failure(RpcEndpoint::RegisterAccount) {
844 return Err(error);
845 }
846
847 self.registered_accounts
850 .write()
851 .insert(account_id, String::from(invitation_code));
852
853 Ok(())
854 }
855
856 async fn is_account_allowed(&self, account_id: AccountId) -> Result<bool, RpcError> {
857 self.is_account_allowed_calls.fetch_add(1, Ordering::Relaxed);
858
859 if let Some(error) = self.take_failure(RpcEndpoint::IsAccountAllowed) {
860 return Err(error);
861 }
862
863 if !self.allowlist_enforced.load(Ordering::SeqCst) {
866 return Ok(true);
867 }
868
869 Ok(self.registered_accounts.read().contains_key(&account_id))
870 }
871
872 async fn sync_nullifiers(
875 &self,
876 prefixes: &[u16],
877 block_from: BlockNumber,
878 block_to: BlockNumber,
879 ) -> Result<Vec<NullifierUpdate>, RpcError> {
880 let nullifiers = self
881 .mock_chain
882 .read()
883 .nullifier_tree()
884 .entries()
885 .filter_map(|(nullifier, block_num)| {
886 let within_range = block_num >= block_from && block_num <= block_to;
887
888 if prefixes.contains(&nullifier.prefix()) && within_range {
889 Some(NullifierUpdate { nullifier, block_num })
890 } else {
891 None
892 }
893 })
894 .collect::<Vec<_>>();
895
896 Ok(nullifiers)
897 }
898
899 async fn get_block_by_number(
900 &self,
901 block_num: BlockNumber,
902 include_proof: bool,
903 ) -> Result<(SignedBlock, Option<ExecutionProof>), RpcError> {
904 let block = self
905 .mock_chain
906 .read()
907 .proven_blocks()
908 .iter()
909 .find(|b| b.header().block_num() == block_num)
910 .unwrap()
911 .clone();
912 let (header, body, signatures, proof) = block.into_parts();
913
914 Ok((
915 SignedBlock::new_unchecked(header, body, signatures),
916 include_proof.then_some(proof),
917 ))
918 }
919
920 async fn get_note_script_by_root(&self, root: Word) -> Result<Option<NoteScript>, RpcError> {
921 let script = self
922 .get_available_notes()
923 .iter()
924 .filter_map(|note| note.note())
925 .find(|n| Word::from(n.script().root()) == root)
926 .map(|n| n.script().clone());
927
928 Ok(script)
929 }
930
931 async fn sync_storage_maps(
932 &self,
933 block_from: BlockNumber,
934 block_to: BlockNumber,
935 account_id: AccountId,
936 ) -> Result<StorageMapInfo, RpcError> {
937 let mut map_entries: BTreeMap<StorageSlotName, StorageMapPatchEntries> = BTreeMap::new();
938 let mut current_block_from = block_from;
939 let chain_tip = self.get_chain_tip_block_num();
940 let target_block = block_to.min(chain_tip);
941
942 loop {
943 let (page_chain_tip, page_block_number, page_entries) =
944 self.get_sync_storage_maps_request(current_block_from, block_to, account_id);
945 for (slot_name, entries) in page_entries {
946 map_entries
947 .entry(slot_name)
948 .or_default()
949 .as_map_mut()
950 .extend(entries.into_map());
951 }
952
953 if page_block_number >= target_block {
954 return Ok(StorageMapInfo {
955 chain_tip: page_chain_tip,
956 block_number: page_block_number,
957 map_entries,
958 });
959 }
960
961 current_block_from = (page_block_number.as_u32() + 1).into();
962 }
963 }
964
965 async fn sync_account_vault(
966 &self,
967 block_from: BlockNumber,
968 block_to: BlockNumber,
969 account_id: AccountId,
970 ) -> Result<AccountVaultInfo, RpcError> {
971 let mut vault_patch = AccountVaultPatch::default();
972 let mut current_block_from = block_from;
973 let chain_tip = self.get_chain_tip_block_num();
974 let target_block = block_to.min(chain_tip);
975
976 loop {
977 let (page_chain_tip, page_block_number, page_patch) =
978 self.get_sync_account_vault_request(current_block_from, block_to, account_id);
979 vault_patch.merge(page_patch);
980
981 if page_block_number >= target_block {
982 return Ok(AccountVaultInfo {
983 chain_tip: page_chain_tip,
984 block_number: page_block_number,
985 vault_patch,
986 });
987 }
988
989 current_block_from = (page_block_number.as_u32() + 1).into();
990 }
991 }
992
993 async fn sync_transactions(
994 &self,
995 block_from: BlockNumber,
996 block_to: BlockNumber,
997 account_ids: Vec<AccountId>,
998 ) -> Result<Vec<TransactionRecord>, RpcError> {
999 Ok(self.get_sync_transactions_request(block_from, block_to, &account_ids))
1000 }
1001
1002 async fn get_network_id(&self) -> Result<NetworkId, RpcError> {
1003 Ok(NetworkId::Testnet)
1004 }
1005
1006 async fn get_rpc_limits(&self) -> Result<crate::rpc::RpcLimits, RpcError> {
1007 Ok(crate::rpc::RpcLimits::default())
1008 }
1009
1010 fn has_rpc_limits(&self) -> Option<crate::rpc::RpcLimits> {
1011 None
1012 }
1013
1014 async fn set_rpc_limits(&self, _limits: crate::rpc::RpcLimits) {
1015 }
1017
1018 async fn get_status_unversioned(&self) -> Result<RpcStatusInfo, RpcError> {
1019 Ok(RpcStatusInfo {
1020 version: env!("CARGO_PKG_VERSION").into(),
1021 genesis_commitment: None,
1022 chain_tip: 0,
1023 block_producer: None,
1024 })
1025 }
1026
1027 async fn get_network_note_status(
1028 &self,
1029 _note_id: NoteId,
1030 ) -> Result<NetworkNoteStatusInfo, RpcError> {
1031 todo!("We need to check if we want to implement this for the mockchain");
1032 }
1033}
1034
1035impl From<MockChain> for MockRpcApi {
1039 fn from(mock_chain: MockChain) -> Self {
1040 MockRpcApi::new(mock_chain)
1041 }
1042}
1043
1044fn build_account_updates(
1048 mock_chain: &MockChain,
1049) -> BTreeMap<BlockNumber, BTreeMap<AccountId, Word>> {
1050 let mut account_commitment_updates = BTreeMap::new();
1051 for block in mock_chain.proven_blocks() {
1052 let block_num = block.header().block_num();
1053 let mut updates = BTreeMap::new();
1054
1055 for update in block.body().updated_accounts() {
1056 updates.insert(update.account_id(), update.final_state_commitment());
1057 }
1058
1059 if updates.is_empty() {
1060 continue;
1061 }
1062
1063 account_commitment_updates.insert(block_num, updates);
1064 }
1065 account_commitment_updates
1066}