Skip to main content

miden_client/test_utils/
mock.rs

1use alloc::boxed::Box;
2use alloc::collections::{BTreeMap, BTreeSet};
3use alloc::sync::Arc;
4use alloc::vec::Vec;
5
6use miden_protocol::Word;
7use miden_protocol::account::delta::AccountUpdateDetails;
8use miden_protocol::account::{AccountId, StorageSlot, StorageSlotContent, StorageSlotType};
9use miden_protocol::address::NetworkId;
10use miden_protocol::batch::{ProposedBatch, ProvenBatch};
11use miden_protocol::block::{BlockHeader, BlockNumber, ProvenBlock};
12use miden_protocol::crypto::merkle::mmr::{Forest, Mmr, MmrProof};
13use miden_protocol::note::{NoteAttachments, NoteHeader, NoteId, NoteScript, NoteTag};
14use miden_protocol::transaction::{ProvenTransaction, TransactionInputs};
15use miden_testing::{MockChain, MockChainNote};
16use miden_tx::utils::sync::RwLock;
17
18use crate::Client;
19use crate::rpc::domain::account::{
20    AccountDetails,
21    AccountProof,
22    AccountStorageDetails,
23    AccountStorageMapDetails,
24    AccountVaultDetails,
25    GetAccountRequest,
26    StorageMapEntries,
27    StorageMapEntry,
28    StorageMapFetch,
29};
30use crate::rpc::domain::account_vault::{AccountVaultInfo, AccountVaultUpdate};
31use crate::rpc::domain::note::{CommittedNote, FetchedNote, NoteSyncBlock};
32use crate::rpc::domain::nullifier::NullifierUpdate;
33use crate::rpc::domain::status::NetworkNoteStatusInfo;
34use crate::rpc::domain::storage_map::{StorageMapInfo, StorageMapUpdate};
35use crate::rpc::domain::sync::{ChainMmrInfo, SyncTarget};
36use crate::rpc::domain::transaction::TransactionRecord;
37use crate::rpc::{AccountStateAt, NodeRpcClient, RpcError, RpcStatusInfo};
38
39pub type MockClient<AUTH> = Client<AUTH>;
40
41/// Mock RPC API
42///
43/// This struct implements the RPC API used by the client to communicate with the node. It simulates
44/// most of the functionality of the actual node, with some small differences:
45/// - It uses a [`MockChain`] to simulate the blockchain state.
46/// - Blocks are not automatically created after time passes, but rather new blocks are created when
47///   calling the `prove_block` method.
48/// - Network account and transactions aren't supported in the current version.
49/// - Account update block numbers aren't tracked, so any endpoint that returns when certain account
50///   updates were made will return the chain tip block number instead.
51#[derive(Clone)]
52pub struct MockRpcApi {
53    account_commitment_updates: Arc<RwLock<BTreeMap<BlockNumber, BTreeMap<AccountId, Word>>>>,
54    pub mock_chain: Arc<RwLock<MockChain>>,
55    oversize_threshold: usize,
56    /// Note headers to report as erased in sync transaction responses.
57    erased_notes: Arc<RwLock<Vec<NoteHeader>>>,
58    /// Attachment content for private notes, keyed by note ID. The [`MockChain`] stores private
59    /// notes without their attachment content (only metadata), so tests that need
60    /// `get_notes_by_id` to return private-note attachments register them here.
61    private_note_attachments: Arc<RwLock<BTreeMap<NoteId, NoteAttachments>>>,
62}
63
64impl Default for MockRpcApi {
65    fn default() -> Self {
66        Self::new(MockChain::new())
67    }
68}
69
70impl MockRpcApi {
71    // Constant to use in mocked pagination.
72    const PAGINATION_BLOCK_LIMIT: u32 = 5;
73
74    /// Creates a new [`MockRpcApi`] instance with the state of the provided [`MockChain`].
75    pub fn new(mock_chain: MockChain) -> Self {
76        Self {
77            account_commitment_updates: Arc::new(RwLock::new(build_account_updates(&mock_chain))),
78            mock_chain: Arc::new(RwLock::new(mock_chain)),
79            oversize_threshold: 1000,
80            erased_notes: Arc::new(RwLock::new(Vec::new())),
81            private_note_attachments: Arc::new(RwLock::new(BTreeMap::new())),
82        }
83    }
84
85    /// Registers the attachment content for a private note so that subsequent `get_notes_by_id`
86    /// responses include it, mirroring a node that stores private-note attachments on-chain.
87    pub fn register_private_note_attachments(&self, note_id: NoteId, attachments: NoteAttachments) {
88        self.private_note_attachments.write().insert(note_id, attachments);
89    }
90
91    /// Sets the oversize threshold for `get_account`. Any storage map with more entries than
92    /// this threshold, or a vault with more assets, will have the `too_many_entries` /
93    /// `too_many_assets` flags set in the response.
94    #[must_use]
95    pub fn with_oversize_threshold(mut self, threshold: usize) -> Self {
96        self.oversize_threshold = threshold;
97        self
98    }
99
100    /// Registers a note header to be reported as erased in subsequent sync transaction responses.
101    pub fn mark_note_as_erased(&self, header: NoteHeader) {
102        self.erased_notes.write().push(header);
103    }
104
105    /// Returns the current MMR of the blockchain.
106    pub fn get_mmr(&self) -> Mmr {
107        self.mock_chain.read().blockchain().as_mmr().clone()
108    }
109
110    /// Returns the chain tip block number.
111    pub fn get_chain_tip_block_num(&self) -> BlockNumber {
112        self.mock_chain.read().latest_block_header().block_num()
113    }
114
115    /// Advances the mock chain by proving the next block, committing all pending objects to the
116    /// chain in the process.
117    pub fn prove_block(&self) {
118        let proven_block = self.mock_chain.write().prove_next_block().unwrap();
119        let mut account_commitment_updates = self.account_commitment_updates.write();
120        let block_num = proven_block.header().block_num();
121        let updates: BTreeMap<AccountId, Word> = proven_block
122            .body()
123            .updated_accounts()
124            .iter()
125            .map(|update| (update.account_id(), update.final_state_commitment()))
126            .collect();
127
128        if !updates.is_empty() {
129            account_commitment_updates.insert(block_num, updates);
130        }
131    }
132
133    /// Retrieves a block by its block number.
134    fn get_block_by_num(&self, block_num: BlockNumber) -> BlockHeader {
135        self.mock_chain.read().block_header(block_num.as_usize())
136    }
137
138    /// Retrieves account vault updates in a given block range.
139    /// This method tries to simulate pagination by limiting the number of blocks processed per
140    /// request.
141    fn get_sync_account_vault_request(
142        &self,
143        block_from: BlockNumber,
144        block_to: BlockNumber,
145        account_id: AccountId,
146    ) -> AccountVaultInfo {
147        let chain_tip = self.get_chain_tip_block_num();
148        let target_block = block_to.min(chain_tip);
149
150        let page_end_block: BlockNumber = (block_from.as_u32() + Self::PAGINATION_BLOCK_LIMIT)
151            .min(target_block.as_u32())
152            .into();
153
154        let mut updates = vec![];
155        for block in self.mock_chain.read().proven_blocks() {
156            let block_number = block.header().block_num();
157            // Only include blocks in range [block_from, page_end_block]
158            if block_number < block_from || block_number > page_end_block {
159                continue;
160            }
161
162            for update in block
163                .body()
164                .updated_accounts()
165                .iter()
166                .filter(|block_acc_update| block_acc_update.account_id() == account_id)
167            {
168                let AccountUpdateDetails::Delta(account_delta) = update.details().clone() else {
169                    continue;
170                };
171
172                let vault_delta = account_delta.vault();
173
174                for asset in vault_delta.added_assets() {
175                    let account_vault_update = AccountVaultUpdate {
176                        block_num: block_number,
177                        asset: Some(asset),
178                        vault_key: asset.vault_key(),
179                    };
180                    updates.push(account_vault_update);
181                }
182            }
183        }
184
185        AccountVaultInfo {
186            chain_tip,
187            block_number: page_end_block,
188            updates,
189        }
190    }
191
192    /// Retrieves transactions in a given block range that match the provided account IDs
193    fn get_sync_transactions_request(
194        &self,
195        block_from: BlockNumber,
196        block_to: BlockNumber,
197        account_ids: &[AccountId],
198    ) -> Vec<TransactionRecord> {
199        let mut transactions = Vec::new();
200        for block in self.mock_chain.read().proven_blocks() {
201            let block_number = block.header().block_num();
202            if block_number < block_from || block_number > block_to {
203                continue;
204            }
205
206            for transaction_header in block.body().transactions().as_slice() {
207                if !account_ids.contains(&transaction_header.account_id()) {
208                    continue;
209                }
210
211                let erased_output_notes = self.erased_notes.read().clone();
212
213                transactions.push(TransactionRecord {
214                    block_num: block_number,
215                    transaction_header: transaction_header.clone(),
216                    output_notes: vec![],
217                    erased_output_notes,
218                });
219            }
220        }
221
222        transactions
223    }
224
225    /// Retrieves storage map updates in a given block range.
226    ///
227    /// This method tries to simulate pagination of the real node.
228    fn get_sync_storage_maps_request(
229        &self,
230        block_from: BlockNumber,
231        block_to: BlockNumber,
232        account_id: AccountId,
233    ) -> StorageMapInfo {
234        let chain_tip = self.get_chain_tip_block_num();
235        let target_block = block_to.min(chain_tip);
236
237        let page_end_block: BlockNumber = (block_from.as_u32() + Self::PAGINATION_BLOCK_LIMIT)
238            .min(target_block.as_u32())
239            .into();
240
241        let mut updates = vec![];
242        for block in self.mock_chain.read().proven_blocks() {
243            let block_number = block.header().block_num();
244            // Only include blocks in range [block_from, page_end_block]
245            if block_number < block_from || block_number > page_end_block {
246                continue;
247            }
248
249            for update in block
250                .body()
251                .updated_accounts()
252                .iter()
253                .filter(|block_acc_update| block_acc_update.account_id() == account_id)
254            {
255                let AccountUpdateDetails::Delta(account_delta) = update.details().clone() else {
256                    continue;
257                };
258
259                let storage_delta = account_delta.storage();
260
261                for (slot_name, map_delta) in storage_delta.maps() {
262                    for (key, value) in map_delta.entries() {
263                        let storage_map_info = StorageMapUpdate {
264                            block_num: block_number,
265                            slot_name: slot_name.clone(),
266                            key: *key,
267                            value: *value,
268                        };
269                        updates.push(storage_map_info);
270                    }
271                }
272            }
273        }
274
275        StorageMapInfo {
276            chain_tip,
277            block_number: page_end_block,
278            updates,
279        }
280    }
281
282    pub fn get_available_notes(&self) -> Vec<MockChainNote> {
283        self.mock_chain.read().committed_notes().values().cloned().collect()
284    }
285
286    pub fn get_public_available_notes(&self) -> Vec<MockChainNote> {
287        self.mock_chain
288            .read()
289            .committed_notes()
290            .values()
291            .filter(|n| matches!(n, MockChainNote::Public(_, _)))
292            .cloned()
293            .collect()
294    }
295
296    pub fn get_private_available_notes(&self) -> Vec<MockChainNote> {
297        self.mock_chain
298            .read()
299            .committed_notes()
300            .values()
301            .filter(|n| matches!(n, MockChainNote::Private(_, _, _)))
302            .cloned()
303            .collect()
304    }
305
306    pub fn advance_blocks(&self, num_blocks: u32) {
307        let current_height = self.get_chain_tip_block_num();
308        let mut mock_chain = self.mock_chain.write();
309        mock_chain.prove_until_block(current_height + num_blocks).unwrap();
310    }
311}
312#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
313#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
314impl NodeRpcClient for MockRpcApi {
315    fn has_genesis_commitment(&self) -> Option<Word> {
316        None
317    }
318
319    async fn set_genesis_commitment(&self, _commitment: Word) -> Result<(), RpcError> {
320        // The mock client doesn't use accept headers, so we don't need to do anything here.
321        Ok(())
322    }
323
324    /// Returns note updates in the inclusive block range `[block_from, block_to]`.
325    /// Only notes that match the provided tags will be returned, grouped by block.
326    async fn sync_notes(
327        &self,
328        block_from: BlockNumber,
329        block_to: BlockNumber,
330        note_tags: &BTreeSet<NoteTag>,
331    ) -> Result<Vec<NoteSyncBlock>, RpcError> {
332        let mut blocks_with_notes: BTreeMap<BlockNumber, BTreeMap<NoteId, CommittedNote>> =
333            BTreeMap::new();
334        for note in self.mock_chain.read().committed_notes().values() {
335            let note_block = note.inclusion_proof().location().block_num();
336            if note_tags.contains(&note.metadata().tag())
337                && note_block >= block_from
338                && note_block <= block_to
339            {
340                let committed =
341                    CommittedNote::new(note.id(), *note.metadata(), note.inclusion_proof().clone());
342                blocks_with_notes.entry(note_block).or_default().insert(note.id(), committed);
343            }
344        }
345
346        Ok(blocks_with_notes
347            .into_iter()
348            .map(|(bn, notes)| {
349                let block_header = self.get_block_by_num(bn);
350                let mmr_path = self.get_mmr().open(bn.as_usize()).unwrap().merkle_path().clone();
351                NoteSyncBlock { block_header, mmr_path, notes }
352            })
353            .collect())
354    }
355
356    async fn sync_chain_mmr(
357        &self,
358        current_block_height: BlockNumber,
359        upper_bound: SyncTarget,
360    ) -> Result<ChainMmrInfo, RpcError> {
361        let chain_tip = self.get_chain_tip_block_num();
362        // The mock chain doesn't distinguish committed vs proven tips.
363        let target_block = match upper_bound {
364            SyncTarget::CommittedChainTip | SyncTarget::ProvenChainTip => chain_tip,
365        };
366
367        let from_forest = if current_block_height == target_block {
368            target_block.as_usize()
369        } else {
370            current_block_height.as_u32() as usize + 1
371        };
372
373        let mmr_delta = self
374            .get_mmr()
375            .get_delta(
376                Forest::new(from_forest).unwrap(),
377                Forest::new(target_block.as_usize()).unwrap(),
378            )
379            .unwrap();
380
381        let block_header = self.get_block_by_num(target_block);
382
383        Ok(ChainMmrInfo {
384            block_from: current_block_height,
385            block_to: target_block,
386            mmr_delta,
387            block_header,
388        })
389    }
390
391    /// Retrieves the block header for the specified block number. If the block number is not
392    /// provided, the chain tip block header will be returned.
393    async fn get_block_header_by_number(
394        &self,
395        block_num: Option<BlockNumber>,
396        include_mmr_proof: bool,
397    ) -> Result<(BlockHeader, Option<MmrProof>), RpcError> {
398        let block = if let Some(block_num) = block_num {
399            self.mock_chain.read().block_header(block_num.as_usize())
400        } else {
401            self.mock_chain.read().latest_block_header()
402        };
403
404        let mmr_proof = if include_mmr_proof {
405            Some(self.get_mmr().open(block_num.unwrap().as_usize()).unwrap())
406        } else {
407            None
408        };
409
410        Ok((block, mmr_proof))
411    }
412
413    /// Returns the node's tracked notes that match the provided note IDs.
414    async fn get_notes_by_id(&self, note_ids: &[NoteId]) -> Result<Vec<FetchedNote>, RpcError> {
415        // assume all public notes for now
416        let notes = self.mock_chain.read().committed_notes().clone();
417
418        let hit_notes = note_ids.iter().filter_map(|id| notes.get(id));
419        let mut return_notes = vec![];
420        for note in hit_notes {
421            let fetched_note = match note {
422                MockChainNote::Private(note_id, note_metadata, note_inclusion_proof) => {
423                    let attachments = self
424                        .private_note_attachments
425                        .read()
426                        .get(note_id)
427                        .cloned()
428                        .unwrap_or_else(NoteAttachments::empty);
429                    FetchedNote::Private(
430                        *note_id,
431                        *note_metadata,
432                        attachments,
433                        note_inclusion_proof.clone(),
434                    )
435                },
436                MockChainNote::Public(note, note_inclusion_proof) => {
437                    FetchedNote::Public(note.clone(), note_inclusion_proof.clone())
438                },
439            };
440            return_notes.push(fetched_note);
441        }
442        Ok(return_notes)
443    }
444
445    /// Simulates the submission of a proven transaction to the node. This will create a new block
446    /// just for the new transaction and return the block number of the newly created block.
447    async fn submit_proven_transaction(
448        &self,
449        proven_transaction: ProvenTransaction,
450        _tx_inputs: TransactionInputs, // Unnecessary for testing client itself.
451    ) -> Result<BlockNumber, RpcError> {
452        // TODO: add some basic validations to test error cases
453
454        {
455            let mut mock_chain = self.mock_chain.write();
456            mock_chain.add_pending_proven_transaction(proven_transaction.clone());
457        };
458
459        let block_num = self.get_chain_tip_block_num();
460
461        Ok(block_num)
462    }
463
464    /// Simulates the submission of a proven batch to the node by adding it to the mock chain's
465    /// pending batches. The `proposed_batch` and `transaction_inputs` arguments are accepted to
466    /// match the trait signature but are unused — the mock relies on the `ProvenBatch` alone,
467    /// matching how `submit_proven_transaction` ignores its `transaction_inputs`.
468    async fn submit_proven_batch(
469        &self,
470        proven_batch: ProvenBatch,
471        _proposed_batch: ProposedBatch,
472        _transaction_inputs: Vec<TransactionInputs>,
473    ) -> Result<BlockNumber, RpcError> {
474        let mut mock_chain = self.mock_chain.write();
475        mock_chain.add_pending_batch(proven_batch);
476        drop(mock_chain);
477
478        let block_num = self.get_chain_tip_block_num();
479
480        Ok(block_num)
481    }
482
483    /// Returns the account proof for the specified account. The `known_code` and `vault` fields
484    /// of the request are ignored in the mock implementation: the latest account code and full
485    /// asset list are always returned, and the truncation flags are set when the data exceeds
486    /// `oversize_threshold`.
487    async fn get_account(
488        &self,
489        account_id: AccountId,
490        request: GetAccountRequest,
491    ) -> Result<(BlockNumber, AccountProof), RpcError> {
492        let mock_chain = self.mock_chain.read();
493
494        let block_number = match request.at {
495            AccountStateAt::Block(number) => number,
496            AccountStateAt::ChainTip => mock_chain.latest_block_header().block_num(),
497        };
498
499        let headers = if account_id.is_public() {
500            let account = mock_chain.committed_account(account_id).unwrap();
501
502            // `All` enumerates the account's map slots directly — the mock can introspect the
503            // account, so it simulates the (not-yet-on-the-wire) "all storage maps" request.
504            let requested_slots: Vec<_> = match &request.storage {
505                StorageMapFetch::Skip => Vec::new(),
506                StorageMapFetch::Slots(reqs) => reqs.inner().keys().cloned().collect(),
507                StorageMapFetch::All => account
508                    .storage()
509                    .to_header()
510                    .slots()
511                    .filter(|slot| slot.slot_type() == StorageSlotType::Map)
512                    .map(|slot| slot.name().clone())
513                    .collect(),
514            };
515
516            let mut map_details = vec![];
517            for slot_name in &requested_slots {
518                if let Some(StorageSlotContent::Map(storage_map)) =
519                    account.storage().get(slot_name).map(StorageSlot::content)
520                {
521                    let entries: Vec<StorageMapEntry> = storage_map
522                        .entries()
523                        .map(|(key, value)| StorageMapEntry { key: *key, value: *value })
524                        .collect();
525
526                    // NOTE: The mock returns all entries even when too_many_entries is set.
527                    // In production, the node would return partial data for oversized maps.
528                    let too_many_entries = entries.len() > self.oversize_threshold;
529                    let account_storage_map_detail = AccountStorageMapDetails {
530                        slot_name: slot_name.clone(),
531                        too_many_entries,
532                        entries: StorageMapEntries::AllEntries(entries),
533                    };
534
535                    map_details.push(account_storage_map_detail);
536                } else {
537                    panic!("Storage slot {slot_name} is not a map");
538                }
539            }
540
541            let storage_details = AccountStorageDetails {
542                header: account.storage().to_header(),
543                map_details,
544            };
545
546            let mut assets = vec![];
547            for asset in account.vault().assets() {
548                assets.push(asset);
549            }
550            let vault_details = AccountVaultDetails {
551                too_many_assets: assets.len() > self.oversize_threshold,
552                assets,
553            };
554
555            Some(AccountDetails {
556                header: account.into(),
557                storage_details,
558                code: account.code().clone(),
559                vault_details,
560            })
561        } else {
562            None
563        };
564
565        let witness = mock_chain.account_tree().open(account_id);
566
567        let proof = AccountProof::new(witness, headers).unwrap();
568
569        Ok((block_number, proof))
570    }
571
572    /// Returns the nullifiers created after the specified block number that match the provided
573    /// prefixes.
574    async fn sync_nullifiers(
575        &self,
576        prefixes: &[u16],
577        block_from: BlockNumber,
578        block_to: BlockNumber,
579    ) -> Result<Vec<NullifierUpdate>, RpcError> {
580        let nullifiers = self
581            .mock_chain
582            .read()
583            .nullifier_tree()
584            .entries()
585            .filter_map(|(nullifier, block_num)| {
586                let within_range = block_num >= block_from && block_num <= block_to;
587
588                if prefixes.contains(&nullifier.prefix()) && within_range {
589                    Some(NullifierUpdate { nullifier, block_num })
590                } else {
591                    None
592                }
593            })
594            .collect::<Vec<_>>();
595
596        Ok(nullifiers)
597    }
598
599    async fn get_block_by_number(
600        &self,
601        block_num: BlockNumber,
602        _include_proof: bool,
603    ) -> Result<ProvenBlock, RpcError> {
604        let block = self
605            .mock_chain
606            .read()
607            .proven_blocks()
608            .iter()
609            .find(|b| b.header().block_num() == block_num)
610            .unwrap()
611            .clone();
612
613        Ok(block)
614    }
615
616    async fn get_note_script_by_root(&self, root: Word) -> Result<Option<NoteScript>, RpcError> {
617        let script = self
618            .get_available_notes()
619            .iter()
620            .filter_map(|note| note.note())
621            .find(|n| Word::from(n.script().root()) == root)
622            .map(|n| n.script().clone());
623
624        Ok(script)
625    }
626
627    async fn sync_storage_maps(
628        &self,
629        block_from: BlockNumber,
630        block_to: BlockNumber,
631        account_id: AccountId,
632    ) -> Result<StorageMapInfo, RpcError> {
633        let mut all_updates = Vec::new();
634        let mut current_block_from = block_from;
635        let chain_tip = self.get_chain_tip_block_num();
636        let target_block = block_to.min(chain_tip);
637
638        loop {
639            let response =
640                self.get_sync_storage_maps_request(current_block_from, block_to, account_id);
641            all_updates.extend(response.updates);
642
643            if response.block_number >= target_block {
644                return Ok(StorageMapInfo {
645                    chain_tip: response.chain_tip,
646                    block_number: response.block_number,
647                    updates: all_updates,
648                });
649            }
650
651            current_block_from = (response.block_number.as_u32() + 1).into();
652        }
653    }
654
655    async fn sync_account_vault(
656        &self,
657        block_from: BlockNumber,
658        block_to: BlockNumber,
659        account_id: AccountId,
660    ) -> Result<AccountVaultInfo, RpcError> {
661        let mut all_updates = Vec::new();
662        let mut current_block_from = block_from;
663        let chain_tip = self.get_chain_tip_block_num();
664        let target_block = block_to.min(chain_tip);
665
666        loop {
667            let response =
668                self.get_sync_account_vault_request(current_block_from, block_to, account_id);
669            all_updates.extend(response.updates);
670
671            if response.block_number >= target_block {
672                return Ok(AccountVaultInfo {
673                    chain_tip: response.chain_tip,
674                    block_number: response.block_number,
675                    updates: all_updates,
676                });
677            }
678
679            current_block_from = (response.block_number.as_u32() + 1).into();
680        }
681    }
682
683    async fn sync_transactions(
684        &self,
685        block_from: BlockNumber,
686        block_to: BlockNumber,
687        account_ids: Vec<AccountId>,
688    ) -> Result<Vec<TransactionRecord>, RpcError> {
689        Ok(self.get_sync_transactions_request(block_from, block_to, &account_ids))
690    }
691
692    async fn get_network_id(&self) -> Result<NetworkId, RpcError> {
693        Ok(NetworkId::Testnet)
694    }
695
696    async fn get_rpc_limits(&self) -> Result<crate::rpc::RpcLimits, RpcError> {
697        Ok(crate::rpc::RpcLimits::default())
698    }
699
700    fn has_rpc_limits(&self) -> Option<crate::rpc::RpcLimits> {
701        None
702    }
703
704    async fn set_rpc_limits(&self, _limits: crate::rpc::RpcLimits) {
705        // No-op for mock client
706    }
707
708    async fn get_status_unversioned(&self) -> Result<RpcStatusInfo, RpcError> {
709        Ok(RpcStatusInfo {
710            version: env!("CARGO_PKG_VERSION").into(),
711            genesis_commitment: None,
712            chain_tip: 0,
713            block_producer: None,
714        })
715    }
716
717    async fn get_network_note_status(
718        &self,
719        _note_id: NoteId,
720    ) -> Result<NetworkNoteStatusInfo, RpcError> {
721        todo!("We need to check if we want to implement this for the mockchain");
722    }
723}
724
725// CONVERSIONS
726// ================================================================================================
727
728impl From<MockChain> for MockRpcApi {
729    fn from(mock_chain: MockChain) -> Self {
730        MockRpcApi::new(mock_chain)
731    }
732}
733
734// HELPERS
735// ================================================================================================
736
737fn build_account_updates(
738    mock_chain: &MockChain,
739) -> BTreeMap<BlockNumber, BTreeMap<AccountId, Word>> {
740    let mut account_commitment_updates = BTreeMap::new();
741    for block in mock_chain.proven_blocks() {
742        let block_num = block.header().block_num();
743        let mut updates = BTreeMap::new();
744
745        for update in block.body().updated_accounts() {
746            updates.insert(update.account_id(), update.final_state_commitment());
747        }
748
749        if updates.is_empty() {
750            continue;
751        }
752
753        account_commitment_updates.insert(block_num, updates);
754    }
755    account_commitment_updates
756}