1use alloc::boxed::Box;
2use alloc::collections::{BTreeMap, BTreeSet};
3use alloc::sync::Arc;
4use alloc::vec::Vec;
5use core::sync::atomic::{AtomicUsize, Ordering};
6
7use miden_protocol::Word;
8use miden_protocol::account::{
9 AccountId,
10 AccountUpdateDetails,
11 AccountVaultPatch,
12 StorageMapKey,
13 StorageMapPatchEntries,
14 StorageSlot,
15 StorageSlotContent,
16 StorageSlotName,
17 StorageSlotType,
18};
19use miden_protocol::address::NetworkId;
20use miden_protocol::batch::{ProposedBatch, ProvenBatch};
21use miden_protocol::block::{BlockHeader, BlockNumber, ProvenBlock};
22use miden_protocol::crypto::merkle::MerklePath;
23use miden_protocol::crypto::merkle::mmr::{Forest, Mmr, MmrProof};
24use miden_protocol::crypto::merkle::smt::PartialSmt;
25use miden_protocol::note::{NoteAttachments, NoteHeader, NoteId, NoteScript, NoteTag};
26use miden_protocol::transaction::{OutputNote, ProvenTransaction};
27use miden_testing::{MockChain, MockChainNote};
28use miden_tx::utils::sync::RwLock;
29
30use crate::Client;
31use crate::rpc::domain::account::{
32 AccountDetails,
33 AccountProof,
34 AccountStorageDetails,
35 AccountStorageMapDetails,
36 AccountVaultDetails,
37 GetAccountRequest,
38 StorageMapEntries,
39 StorageMapEntry,
40 StorageMapFetch,
41 VaultFetch,
42};
43use crate::rpc::domain::account_vault::AccountVaultInfo;
44use crate::rpc::domain::note::{CommittedNote, FetchedNote, SyncNotesBlock};
45use crate::rpc::domain::nullifier::NullifierUpdate;
46use crate::rpc::domain::status::NetworkNoteStatusInfo;
47use crate::rpc::domain::storage_map::StorageMapInfo;
48use crate::rpc::domain::sync::{ChainMmrInfo, SyncTarget};
49use crate::rpc::domain::transaction::TransactionRecord;
50use crate::rpc::encryption::{AttestedTransactionEncryptionKey, SealedTransactionInputs};
51use crate::rpc::{AccountStateAt, NodeRpcClient, RpcEndpoint, RpcError, RpcStatusInfo};
52
53pub type MockClient<AUTH> = Client<AUTH>;
54
55#[derive(Clone)]
66pub struct MockRpcApi {
67 account_commitment_updates: Arc<RwLock<BTreeMap<BlockNumber, BTreeMap<AccountId, Word>>>>,
68 pub mock_chain: Arc<RwLock<MockChain>>,
69 historical_chains: Arc<RwLock<BTreeMap<BlockNumber, Arc<MockChain>>>>,
71 oversize_threshold: usize,
72 erased_notes: Arc<RwLock<Vec<NoteHeader>>>,
74 private_note_attachments: Arc<RwLock<BTreeMap<NoteId, NoteAttachments>>>,
78 sync_notes_mmr_path_overrides: Arc<RwLock<BTreeMap<BlockNumber, MerklePath>>>,
80 get_notes_by_id_calls: Arc<AtomicUsize>,
83 next_call_failures: Arc<RwLock<BTreeMap<&'static str, RpcError>>>,
87}
88
89impl Default for MockRpcApi {
90 fn default() -> Self {
91 Self::new(MockChain::new())
92 }
93}
94
95impl MockRpcApi {
96 const PAGINATION_BLOCK_LIMIT: u32 = 5;
98
99 pub fn new(mock_chain: MockChain) -> Self {
101 Self {
102 account_commitment_updates: Arc::new(RwLock::new(build_account_updates(&mock_chain))),
103 mock_chain: Arc::new(RwLock::new(mock_chain)),
104 historical_chains: Arc::new(RwLock::new(BTreeMap::new())),
105 oversize_threshold: 1000,
106 erased_notes: Arc::new(RwLock::new(Vec::new())),
107 private_note_attachments: Arc::new(RwLock::new(BTreeMap::new())),
108 sync_notes_mmr_path_overrides: Arc::new(RwLock::new(BTreeMap::new())),
109 get_notes_by_id_calls: Arc::new(AtomicUsize::new(0)),
110 next_call_failures: Arc::new(RwLock::new(BTreeMap::new())),
111 }
112 }
113
114 pub fn fail_next_call(&self, endpoint: RpcEndpoint, error: RpcError) {
120 self.next_call_failures.write().insert(endpoint.proto_name(), error);
121 }
122
123 fn take_failure(&self, endpoint: RpcEndpoint) -> Option<RpcError> {
125 self.next_call_failures.write().remove(endpoint.proto_name())
126 }
127
128 pub fn register_private_note_attachments(&self, note_id: NoteId, attachments: NoteAttachments) {
131 self.private_note_attachments.write().insert(note_id, attachments);
132 }
133
134 pub fn get_notes_by_id_call_count(&self) -> usize {
136 self.get_notes_by_id_calls.load(Ordering::Relaxed)
137 }
138
139 pub fn set_sync_notes_mmr_path(&self, block_num: BlockNumber, path: MerklePath) {
141 self.sync_notes_mmr_path_overrides.write().insert(block_num, path);
142 }
143
144 #[must_use]
148 pub fn with_oversize_threshold(mut self, threshold: usize) -> Self {
149 self.oversize_threshold = threshold;
150 self
151 }
152
153 pub fn mark_note_as_erased(&self, header: NoteHeader) {
155 self.erased_notes.write().push(header);
156 }
157
158 pub fn get_mmr(&self) -> Mmr {
160 self.mock_chain.read().blockchain().as_mmr().clone()
161 }
162
163 pub fn get_chain_tip_block_num(&self) -> BlockNumber {
165 self.mock_chain.read().latest_block_header().block_num()
166 }
167
168 pub fn prove_block(&self) {
171 let proven_block = {
172 let mut mock_chain = self.mock_chain.write();
173 let historical_block_num = mock_chain.latest_block_header().block_num();
174 let snapshot = Arc::new(mock_chain.clone());
175 let proven_block = mock_chain.prove_next_block().unwrap();
176 self.historical_chains.write().insert(historical_block_num, snapshot);
177 proven_block
178 };
179 let block_num = proven_block.header().block_num();
180 let mut account_commitment_updates = self.account_commitment_updates.write();
181 let updates: BTreeMap<AccountId, Word> = proven_block
182 .body()
183 .updated_accounts()
184 .iter()
185 .map(|update| (update.account_id(), update.final_state_commitment()))
186 .collect();
187
188 if !updates.is_empty() {
189 account_commitment_updates.insert(block_num, updates);
190 }
191 }
192
193 fn get_block_by_num(&self, block_num: BlockNumber) -> BlockHeader {
195 self.mock_chain.read().block_header(block_num.as_usize())
196 }
197
198 fn get_sync_account_vault_request(
202 &self,
203 block_from: BlockNumber,
204 block_to: BlockNumber,
205 account_id: AccountId,
206 ) -> (BlockNumber, BlockNumber, AccountVaultPatch) {
207 let chain_tip = self.get_chain_tip_block_num();
208 let target_block = block_to.min(chain_tip);
209
210 let page_end_block: BlockNumber = (block_from.as_u32() + Self::PAGINATION_BLOCK_LIMIT)
211 .min(target_block.as_u32())
212 .into();
213
214 let mut vault_patch = AccountVaultPatch::default();
216 for block in self.mock_chain.read().proven_blocks() {
217 let block_number = block.header().block_num();
218 if block_number < block_from || block_number > page_end_block {
220 continue;
221 }
222
223 for update in block
224 .body()
225 .updated_accounts()
226 .iter()
227 .filter(|block_acc_update| block_acc_update.account_id() == account_id)
228 {
229 let AccountUpdateDetails::Public(patch) = update.details().clone() else {
230 continue;
231 };
232
233 vault_patch.merge(patch.vault().clone());
234 }
235 }
236
237 (chain_tip, page_end_block, vault_patch)
238 }
239
240 fn get_sync_transactions_request(
242 &self,
243 block_from: BlockNumber,
244 block_to: BlockNumber,
245 account_ids: &[AccountId],
246 ) -> Vec<TransactionRecord> {
247 let mut transactions = Vec::new();
248 for block in self.mock_chain.read().proven_blocks() {
249 let block_number = block.header().block_num();
250 if block_number < block_from || block_number > block_to {
251 continue;
252 }
253
254 for transaction_header in block.body().transactions().as_slice() {
255 if !account_ids.contains(&transaction_header.account_id()) {
256 continue;
257 }
258
259 let erased_output_notes = self.erased_notes.read().clone();
260
261 transactions.push(TransactionRecord {
262 block_num: block_number,
263 transaction_header: transaction_header.clone(),
264 output_notes: vec![],
265 erased_output_notes,
266 consumed_note_refs: vec![],
267 });
268 }
269 }
270
271 transactions
272 }
273
274 fn get_sync_storage_maps_request(
278 &self,
279 block_from: BlockNumber,
280 block_to: BlockNumber,
281 account_id: AccountId,
282 ) -> (BlockNumber, BlockNumber, BTreeMap<StorageSlotName, StorageMapPatchEntries>) {
283 let chain_tip = self.get_chain_tip_block_num();
284 let target_block = block_to.min(chain_tip);
285
286 let page_end_block: BlockNumber = (block_from.as_u32() + Self::PAGINATION_BLOCK_LIMIT)
287 .min(target_block.as_u32())
288 .into();
289
290 let mut map_entries: BTreeMap<StorageSlotName, StorageMapPatchEntries> = BTreeMap::new();
292 for block in self.mock_chain.read().proven_blocks() {
293 let block_number = block.header().block_num();
294 if block_number < block_from || block_number > page_end_block {
296 continue;
297 }
298
299 for update in block
300 .body()
301 .updated_accounts()
302 .iter()
303 .filter(|block_acc_update| block_acc_update.account_id() == account_id)
304 {
305 let AccountUpdateDetails::Public(patch) = update.details().clone() else {
306 continue;
307 };
308
309 for (slot_name, map_patch) in patch.storage().maps() {
310 if let Some(entries) = map_patch.entries() {
311 map_entries
312 .entry(slot_name.clone())
313 .or_default()
314 .as_map_mut()
315 .extend(entries.as_map().clone());
316 }
317 }
318 }
319 }
320
321 (chain_tip, page_end_block, map_entries)
322 }
323
324 pub fn get_available_notes(&self) -> Vec<MockChainNote> {
325 self.mock_chain.read().committed_notes().values().cloned().collect()
326 }
327
328 pub fn get_public_available_notes(&self) -> Vec<MockChainNote> {
329 self.mock_chain
330 .read()
331 .committed_notes()
332 .values()
333 .filter(|n| matches!(n, MockChainNote::Public(_, _)))
334 .cloned()
335 .collect()
336 }
337
338 pub fn get_private_available_notes(&self) -> Vec<MockChainNote> {
339 self.mock_chain
340 .read()
341 .committed_notes()
342 .values()
343 .filter(|n| matches!(n, MockChainNote::Private(_, _, _, _)))
344 .cloned()
345 .collect()
346 }
347
348 pub fn advance_blocks(&self, num_blocks: u32) {
349 let mut mock_chain = self.mock_chain.write();
350 let block_num = mock_chain.latest_block_header().block_num();
351 let snapshot = Arc::new(mock_chain.clone());
352 mock_chain.prove_until_block(block_num + num_blocks).unwrap();
353 self.historical_chains.write().insert(block_num, snapshot);
354 }
355}
356#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
357#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
358impl NodeRpcClient for MockRpcApi {
359 fn has_genesis_commitment(&self) -> Option<Word> {
365 None
366 }
367
368 async fn set_genesis_commitment(&self, _commitment: Word) -> Result<(), RpcError> {
369 Ok(())
371 }
372
373 async fn sync_notes(
376 &self,
377 block_from: BlockNumber,
378 block_to: BlockNumber,
379 note_tags: &BTreeSet<NoteTag>,
380 ) -> Result<Vec<SyncNotesBlock>, RpcError> {
381 let mut blocks_with_notes: BTreeMap<BlockNumber, BTreeMap<NoteId, CommittedNote>> =
382 BTreeMap::new();
383 for note in self.mock_chain.read().committed_notes().values() {
384 let note_block = note.inclusion_proof().location().block_num();
385 if note_tags.contains(¬e.metadata().tag())
386 && note_block >= block_from
387 && note_block <= block_to
388 {
389 let mut committed =
390 CommittedNote::new(note.id(), *note.metadata(), note.inclusion_proof().clone());
391 let attachments = note.attachments();
394 if attachments.iter().all(|attachment| attachment.num_words() == 1) {
395 committed = committed
396 .with_attachments(attachments.clone())
397 .expect("the note's own attachments match its commitment");
398 }
399 blocks_with_notes.entry(note_block).or_default().insert(note.id(), committed);
400 }
401 }
402
403 Ok(blocks_with_notes
404 .into_iter()
405 .map(|(bn, notes)| {
406 let block_header = self.get_block_by_num(bn);
407 let mmr_path =
408 self.sync_notes_mmr_path_overrides.read().get(&bn).cloned().unwrap_or_else(
409 || self.get_mmr().open(bn.as_usize()).unwrap().merkle_path().clone(),
410 );
411 SyncNotesBlock { block_header, mmr_path, notes }
412 })
413 .collect())
414 }
415
416 async fn sync_chain_mmr(
417 &self,
418 current_block_height: BlockNumber,
419 upper_bound: SyncTarget,
420 ) -> Result<ChainMmrInfo, RpcError> {
421 let chain_tip = self.get_chain_tip_block_num();
422 let target_block = match upper_bound {
424 SyncTarget::CommittedChainTip | SyncTarget::ProvenChainTip => chain_tip,
425 };
426
427 let from_forest = if current_block_height == target_block {
428 target_block.as_usize()
429 } else {
430 current_block_height.as_u32() as usize + 1
431 };
432
433 let mmr_delta = self
434 .get_mmr()
435 .get_delta(
436 Forest::new(from_forest).unwrap(),
437 Forest::new(target_block.as_usize()).unwrap(),
438 )
439 .unwrap();
440
441 let block_header = self.get_block_by_num(target_block);
442
443 Ok(ChainMmrInfo {
444 block_from: current_block_height,
445 block_to: target_block,
446 mmr_delta,
447 block_header,
448 })
449 }
450
451 async fn get_block_header_by_number(
454 &self,
455 block_num: Option<BlockNumber>,
456 include_mmr_proof: bool,
457 ) -> Result<(BlockHeader, Option<MmrProof>), RpcError> {
458 let block = if let Some(block_num) = block_num {
459 self.mock_chain.read().block_header(block_num.as_usize())
460 } else {
461 self.mock_chain.read().latest_block_header()
462 };
463
464 let mmr_proof = if include_mmr_proof {
465 Some(self.get_mmr().open(block_num.unwrap().as_usize()).unwrap())
466 } else {
467 None
468 };
469
470 Ok((block, mmr_proof))
471 }
472
473 async fn get_notes_by_id(&self, note_ids: &[NoteId]) -> Result<Vec<FetchedNote>, RpcError> {
475 self.get_notes_by_id_calls.fetch_add(1, Ordering::Relaxed);
476
477 let notes = self.mock_chain.read().committed_notes().clone();
479
480 let hit_notes = note_ids.iter().filter_map(|id| notes.get(id));
481 let mut return_notes = vec![];
482 for note in hit_notes {
483 let fetched_note = match note {
484 MockChainNote::Private(note_id, note_metadata, _, note_inclusion_proof) => {
485 let attachments = self
486 .private_note_attachments
487 .read()
488 .get(note_id)
489 .cloned()
490 .unwrap_or_else(NoteAttachments::empty);
491 FetchedNote::Private(
492 *note_id,
493 *note_metadata,
494 attachments,
495 note_inclusion_proof.clone(),
496 )
497 },
498 MockChainNote::Public(note, note_inclusion_proof) => {
499 FetchedNote::Public(note.clone(), note_inclusion_proof.clone())
500 },
501 };
502 return_notes.push(fetched_note);
503 }
504 Ok(return_notes)
505 }
506
507 async fn get_transaction_encryption_key(
511 &self,
512 ) -> Result<AttestedTransactionEncryptionKey, RpcError> {
513 Err(RpcError::TransactionEncryptionKeyRejected(
514 "the mock RPC client does not serve a transaction encryption key".into(),
515 ))
516 }
517
518 async fn submit_proven_transaction(
521 &self,
522 proven_transaction: ProvenTransaction,
523 _sealed_transaction_inputs: SealedTransactionInputs, ) -> Result<BlockNumber, RpcError> {
526 if let Some(error) = self.take_failure(RpcEndpoint::SubmitProvenTx) {
527 return Err(error);
528 }
529
530 for note in proven_transaction.output_notes().iter() {
534 if let OutputNote::Private(private_note) = note
535 && !private_note.attachments().is_empty()
536 {
537 self.private_note_attachments
538 .write()
539 .insert(private_note.id(), private_note.attachments().clone());
540 }
541 }
542
543 {
544 let mut mock_chain = self.mock_chain.write();
545 mock_chain.add_pending_proven_transaction(proven_transaction.clone());
546 };
547
548 let block_num = self.get_chain_tip_block_num();
549
550 Ok(block_num)
551 }
552
553 async fn submit_proven_batch(
558 &self,
559 proven_batch: ProvenBatch,
560 _proposed_batch: ProposedBatch,
561 _sealed_transaction_inputs: Vec<SealedTransactionInputs>,
562 ) -> Result<BlockNumber, RpcError> {
563 let mut mock_chain = self.mock_chain.write();
564 mock_chain.add_pending_batch(proven_batch);
565 drop(mock_chain);
566
567 let block_num = self.get_chain_tip_block_num();
568
569 Ok(block_num)
570 }
571
572 async fn get_account(
576 &self,
577 account_id: AccountId,
578 request: GetAccountRequest,
579 ) -> Result<(BlockNumber, AccountProof), RpcError> {
580 if let Some(error) = self.take_failure(RpcEndpoint::GetAccount) {
581 return Err(error);
582 }
583 let current_chain = self.mock_chain.read();
584 let current_block_number = current_chain.latest_block_header().block_num();
585 let block_number = match request.at {
586 AccountStateAt::Block(number) => number,
587 AccountStateAt::ChainTip => current_block_number,
588 };
589 let historical_chain = match request.at {
590 AccountStateAt::Block(_) if block_number != current_block_number => Some(
591 self.historical_chains.read().get(&block_number).cloned().ok_or_else(|| {
592 RpcError::InvalidResponse(alloc::format!(
593 "no mock chain snapshot at block {block_number}"
594 ))
595 })?,
596 ),
597 AccountStateAt::ChainTip | AccountStateAt::Block(_) => None,
598 };
599 let mock_chain = historical_chain.as_deref().unwrap_or(&*current_chain);
600
601 let headers = if account_id.is_public() {
602 let account = mock_chain.committed_account(account_id).unwrap();
603
604 let requested_slots: Vec<(StorageSlotName, Vec<StorageMapKey>)> = match &request.storage
608 {
609 StorageMapFetch::Skip => Vec::new(),
610 StorageMapFetch::Slots(reqs) => {
611 reqs.inner().iter().map(|(name, keys)| (name.clone(), keys.clone())).collect()
612 },
613 StorageMapFetch::All => account
614 .storage()
615 .to_header()
616 .slots()
617 .filter(|slot| slot.slot_type() == StorageSlotType::Map)
618 .map(|slot| (slot.name().clone(), Vec::new()))
619 .collect(),
620 };
621
622 let mut map_details = vec![];
623 for (slot_name, requested_keys) in &requested_slots {
624 if let Some(StorageSlotContent::Map(storage_map)) =
625 account.storage().get(slot_name).map(StorageSlot::content)
626 {
627 let entries = if requested_keys.is_empty() {
631 let entries: Vec<StorageMapEntry> = storage_map
632 .entries()
633 .map(|(key, value)| StorageMapEntry { key: *key, value: *value })
634 .collect();
635
636 if entries.len() > self.oversize_threshold {
637 StorageMapEntries::LimitExceeded
638 } else {
639 StorageMapEntries::AllEntries(entries)
640 }
641 } else {
642 let partial_smt = PartialSmt::from_proofs(
643 requested_keys.iter().map(|key| storage_map.open(key).into()),
644 )
645 .expect("proofs from one map share a root");
646
647 StorageMapEntries::PartialMap {
648 map_keys: requested_keys.clone(),
649 partial_smt,
650 }
651 };
652
653 map_details
654 .push(AccountStorageMapDetails { slot_name: slot_name.clone(), entries });
655 } else {
656 panic!("Storage slot {slot_name} is not a map");
657 }
658 }
659
660 let storage_details = AccountStorageDetails {
661 header: account.storage().to_header(),
662 map_details,
663 };
664
665 let include_assets = match request.vault {
668 VaultFetch::Skip => false,
669 VaultFetch::Always => true,
670 VaultFetch::IfChangedFrom(root) => root != account.vault().root(),
671 };
672 let mut assets = vec![];
673 if include_assets {
674 for asset in account.vault().assets() {
675 assets.push(asset);
676 }
677 }
678 let vault_details = AccountVaultDetails {
679 too_many_assets: assets.len() > self.oversize_threshold,
680 assets,
681 };
682
683 Some(AccountDetails {
684 header: account.into(),
685 storage_details,
686 code: account.code().clone(),
687 vault_details,
688 })
689 } else {
690 None
691 };
692
693 let witness = mock_chain.account_tree().open(account_id);
694
695 let proof = AccountProof::new(witness, headers).unwrap();
696
697 Ok((block_number, proof))
698 }
699
700 async fn sync_nullifiers(
703 &self,
704 prefixes: &[u16],
705 block_from: BlockNumber,
706 block_to: BlockNumber,
707 ) -> Result<Vec<NullifierUpdate>, RpcError> {
708 let nullifiers = self
709 .mock_chain
710 .read()
711 .nullifier_tree()
712 .entries()
713 .filter_map(|(nullifier, block_num)| {
714 let within_range = block_num >= block_from && block_num <= block_to;
715
716 if prefixes.contains(&nullifier.prefix()) && within_range {
717 Some(NullifierUpdate { nullifier, block_num })
718 } else {
719 None
720 }
721 })
722 .collect::<Vec<_>>();
723
724 Ok(nullifiers)
725 }
726
727 async fn get_block_by_number(
728 &self,
729 block_num: BlockNumber,
730 _include_proof: bool,
731 ) -> Result<ProvenBlock, RpcError> {
732 let block = self
733 .mock_chain
734 .read()
735 .proven_blocks()
736 .iter()
737 .find(|b| b.header().block_num() == block_num)
738 .unwrap()
739 .clone();
740
741 Ok(block)
742 }
743
744 async fn get_note_script_by_root(&self, root: Word) -> Result<Option<NoteScript>, RpcError> {
745 let script = self
746 .get_available_notes()
747 .iter()
748 .filter_map(|note| note.note())
749 .find(|n| Word::from(n.script().root()) == root)
750 .map(|n| n.script().clone());
751
752 Ok(script)
753 }
754
755 async fn sync_storage_maps(
756 &self,
757 block_from: BlockNumber,
758 block_to: BlockNumber,
759 account_id: AccountId,
760 ) -> Result<StorageMapInfo, RpcError> {
761 let mut map_entries: BTreeMap<StorageSlotName, StorageMapPatchEntries> = BTreeMap::new();
762 let mut current_block_from = block_from;
763 let chain_tip = self.get_chain_tip_block_num();
764 let target_block = block_to.min(chain_tip);
765
766 loop {
767 let (page_chain_tip, page_block_number, page_entries) =
768 self.get_sync_storage_maps_request(current_block_from, block_to, account_id);
769 for (slot_name, entries) in page_entries {
770 map_entries
771 .entry(slot_name)
772 .or_default()
773 .as_map_mut()
774 .extend(entries.into_map());
775 }
776
777 if page_block_number >= target_block {
778 return Ok(StorageMapInfo {
779 chain_tip: page_chain_tip,
780 block_number: page_block_number,
781 map_entries,
782 });
783 }
784
785 current_block_from = (page_block_number.as_u32() + 1).into();
786 }
787 }
788
789 async fn sync_account_vault(
790 &self,
791 block_from: BlockNumber,
792 block_to: BlockNumber,
793 account_id: AccountId,
794 ) -> Result<AccountVaultInfo, RpcError> {
795 let mut vault_patch = AccountVaultPatch::default();
796 let mut current_block_from = block_from;
797 let chain_tip = self.get_chain_tip_block_num();
798 let target_block = block_to.min(chain_tip);
799
800 loop {
801 let (page_chain_tip, page_block_number, page_patch) =
802 self.get_sync_account_vault_request(current_block_from, block_to, account_id);
803 vault_patch.merge(page_patch);
804
805 if page_block_number >= target_block {
806 return Ok(AccountVaultInfo {
807 chain_tip: page_chain_tip,
808 block_number: page_block_number,
809 vault_patch,
810 });
811 }
812
813 current_block_from = (page_block_number.as_u32() + 1).into();
814 }
815 }
816
817 async fn sync_transactions(
818 &self,
819 block_from: BlockNumber,
820 block_to: BlockNumber,
821 account_ids: Vec<AccountId>,
822 ) -> Result<Vec<TransactionRecord>, RpcError> {
823 Ok(self.get_sync_transactions_request(block_from, block_to, &account_ids))
824 }
825
826 async fn get_network_id(&self) -> Result<NetworkId, RpcError> {
827 Ok(NetworkId::Testnet)
828 }
829
830 async fn get_rpc_limits(&self) -> Result<crate::rpc::RpcLimits, RpcError> {
831 Ok(crate::rpc::RpcLimits::default())
832 }
833
834 fn has_rpc_limits(&self) -> Option<crate::rpc::RpcLimits> {
835 None
836 }
837
838 async fn set_rpc_limits(&self, _limits: crate::rpc::RpcLimits) {
839 }
841
842 async fn get_status_unversioned(&self) -> Result<RpcStatusInfo, RpcError> {
843 Ok(RpcStatusInfo {
844 version: env!("CARGO_PKG_VERSION").into(),
845 genesis_commitment: None,
846 chain_tip: 0,
847 block_producer: None,
848 })
849 }
850
851 async fn get_network_note_status(
852 &self,
853 _note_id: NoteId,
854 ) -> Result<NetworkNoteStatusInfo, RpcError> {
855 todo!("We need to check if we want to implement this for the mockchain");
856 }
857}
858
859impl From<MockChain> for MockRpcApi {
863 fn from(mock_chain: MockChain) -> Self {
864 MockRpcApi::new(mock_chain)
865 }
866}
867
868fn build_account_updates(
872 mock_chain: &MockChain,
873) -> BTreeMap<BlockNumber, BTreeMap<AccountId, Word>> {
874 let mut account_commitment_updates = BTreeMap::new();
875 for block in mock_chain.proven_blocks() {
876 let block_num = block.header().block_num();
877 let mut updates = BTreeMap::new();
878
879 for update in block.body().updated_accounts() {
880 updates.insert(update.account_id(), update.final_state_commitment());
881 }
882
883 if updates.is_empty() {
884 continue;
885 }
886
887 account_commitment_updates.insert(block_num, updates);
888 }
889 account_commitment_updates
890}