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