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