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