miden_testing/mock_chain/
chain.rs

1use alloc::boxed::Box;
2use alloc::collections::{BTreeMap, BTreeSet};
3use alloc::string::ToString;
4use alloc::vec::Vec;
5
6use anyhow::Context;
7use miden_block_prover::{LocalBlockProver, ProvenBlockError};
8use miden_lib::note::{create_p2id_note, create_p2ide_note};
9use miden_objects::account::delta::AccountUpdateDetails;
10use miden_objects::account::{Account, AccountId, AuthSecretKey, StorageSlot};
11use miden_objects::asset::Asset;
12use miden_objects::batch::{ProposedBatch, ProvenBatch};
13use miden_objects::block::{
14    AccountTree,
15    AccountWitness,
16    BlockHeader,
17    BlockInputs,
18    BlockNumber,
19    Blockchain,
20    NullifierTree,
21    NullifierWitness,
22    ProposedBlock,
23    ProvenBlock,
24};
25use miden_objects::crypto::merkle::SmtProof;
26use miden_objects::note::{Note, NoteHeader, NoteId, NoteInclusionProof, NoteType, Nullifier};
27use miden_objects::transaction::{
28    AccountInputs,
29    ExecutedTransaction,
30    InputNote,
31    InputNotes,
32    OrderedTransactionHeaders,
33    OutputNote,
34    PartialBlockchain,
35    ProvenTransaction,
36    TransactionHeader,
37    TransactionInputs,
38};
39use miden_objects::{MAX_BATCHES_PER_BLOCK, MAX_OUTPUT_NOTES_PER_BATCH, NoteError};
40use miden_processor::crypto::RpoRandomCoin;
41use miden_processor::{DeserializationError, Word};
42use miden_tx::auth::BasicAuthenticator;
43use miden_tx::utils::{ByteReader, Deserializable, Serializable};
44use rand::SeedableRng;
45use rand_chacha::ChaCha20Rng;
46use winterfell::ByteWriter;
47
48use super::note::MockChainNote;
49use crate::{MockChainBuilder, ProvenTransactionExt, TransactionContextBuilder};
50
51// MOCK CHAIN
52// ================================================================================================
53
54/// The [`MockChain`] simulates a simplified blockchain environment for testing purposes.
55/// It allows creating and managing accounts, minting assets, executing transactions, and applying
56/// state updates.
57///
58/// This struct is designed to mock transaction workflows, asset transfers, and
59/// note creation in a test setting. Once entities are set up, [`TransactionContextBuilder`] objects
60/// can be obtained in order to execute transactions accordingly.
61///
62/// On a high-level, there are two ways to interact with the mock chain:
63/// - Generating transactions yourself and adding them to the mock chain "mempool" using
64///   [`MockChain::add_pending_executed_transaction`] or
65///   [`MockChain::add_pending_proven_transaction`]. Once some transactions have been added, they
66///   can be proven into a block using [`MockChain::prove_next_block`], which commits them to the
67///   chain state.
68/// - Using any of the other pending APIs to _magically_ add new notes, accounts or nullifiers in
69///   the next block. For example, [`MockChain::add_pending_p2id_note`] will create a new P2ID note
70///   in the next proven block, without actually containing a transaction that creates that note.
71///
72/// Both approaches can be mixed in the same block, within limits. In particular, avoid modification
73/// of the _same_ entities using both regular transactions and the magic pending APIs.
74///
75/// The mock chain uses the batch and block provers underneath to process pending transactions, so
76/// the generated blocks are realistic and indistinguishable from a real node. The only caveat is
77/// that no real ZK proofs are generated or validated as part of transaction, batch or block
78/// building. If realistic data is important for your use case, avoid using any pending APIs except
79/// for [`MockChain::add_pending_executed_transaction`] and
80/// [`MockChain::add_pending_proven_transaction`].
81///
82/// # Examples
83///
84/// ## Create mock objects and build a transaction context
85/// ```
86/// # use anyhow::Result;
87/// # use miden_objects::{Felt, asset::{Asset, FungibleAsset}, note::NoteType};
88/// # use miden_testing::{Auth, MockChain, TransactionContextBuilder};
89///
90/// # fn main() -> Result<()> {
91/// let mut builder = MockChain::builder();
92///
93/// let faucet = builder.create_new_faucet(Auth::BasicAuth, "USDT", 100_000)?;
94/// let asset = Asset::from(FungibleAsset::new(faucet.id(), 10)?);
95///
96/// let sender = builder.create_new_wallet(Auth::BasicAuth)?;
97/// let target = builder.create_new_wallet(Auth::BasicAuth)?;
98///
99/// let note = builder.add_p2id_note(faucet.id(), target.id(), &[asset], NoteType::Public)?;
100///
101/// let mock_chain = builder.build()?;
102///
103/// // The target account is a new account so we move it into the build_tx_context, since the
104/// // chain's committed accounts do not yet contain it.
105/// let tx_context = mock_chain.build_tx_context(target, &[note.id()], &[])?.build()?;
106/// let executed_transaction = tx_context.execute_blocking()?;
107/// # Ok(())
108/// # }
109/// ```
110///
111/// ## Executing a simple transaction
112/// ```
113/// # use anyhow::Result;
114/// # use miden_objects::{
115/// #    asset::{Asset, FungibleAsset},
116/// #    note::NoteType,
117/// # };
118/// # use miden_testing::{Auth, MockChain};
119///
120/// # fn main() -> Result<()> {
121/// let mut builder = MockChain::builder();
122///
123/// // Add a recipient wallet.
124/// let receiver = builder.add_existing_wallet(Auth::BasicAuth)?;
125///
126/// // Add a wallet with assets.
127/// let sender = builder.add_existing_wallet(Auth::IncrNonce)?;
128/// let fungible_asset = FungibleAsset::mock(10).unwrap_fungible();
129///
130/// // Add a pending P2ID note to the chain.
131/// let note = builder.add_p2id_note(
132///     sender.id(),
133///     receiver.id(),
134///     &[Asset::Fungible(fungible_asset)],
135///     NoteType::Public,
136/// )?;
137///
138/// let mut mock_chain = builder.build()?;
139///
140/// let transaction = mock_chain
141///     .build_tx_context(receiver.id(), &[note.id()], &[])?
142///     .build()?
143///     .execute_blocking()?;
144///
145/// // Add the transaction to the mock chain's "mempool" of pending transactions.
146/// mock_chain.add_pending_executed_transaction(&transaction);
147///
148/// // Prove the next block to include the transaction in the chain state.
149/// mock_chain.prove_next_block()?;
150///
151/// assert_eq!(
152///     mock_chain
153///         .committed_account(receiver.id())?
154///         .vault()
155///         .get_balance(fungible_asset.faucet_id())?,
156///     fungible_asset.amount()
157/// );
158/// # Ok(())
159/// # }
160/// ```
161#[derive(Debug, Clone)]
162pub struct MockChain {
163    /// An append-only structure used to represent the history of blocks produced for this chain.
164    chain: Blockchain,
165
166    /// History of produced blocks.
167    blocks: Vec<ProvenBlock>,
168
169    /// Tree containing all nullifiers.
170    nullifier_tree: NullifierTree,
171
172    /// Tree containing the state commitments of all accounts.
173    account_tree: AccountTree,
174
175    /// Note batches created in transactions in the block.
176    ///
177    /// These will become available once the block is proven.
178    pending_output_notes: Vec<OutputNote>,
179
180    /// Transactions that have been submitted to the chain but have not yet been included in a
181    /// block.
182    pending_transactions: Vec<ProvenTransaction>,
183
184    /// NoteID |-> MockChainNote mapping to simplify note retrieval.
185    committed_notes: BTreeMap<NoteId, MockChainNote>,
186
187    /// AccountId |-> Account mapping to simplify transaction creation. Latest known account
188    /// state is maintained for each account here.
189    ///
190    /// The map always holds the most recent *public* state known for every account. For private
191    /// accounts, however, transactions do not emit the post-transaction state, so their entries
192    /// remain at the last observed state.
193    committed_accounts: BTreeMap<AccountId, Account>,
194
195    /// AccountId |-> AccountCredentials mapping to store the seed and authenticator for accounts
196    /// to simplify transaction creation.
197    account_credentials: BTreeMap<AccountId, AccountCredentials>,
198
199    // The RNG used to generate note serial numbers, account seeds or cryptographic keys.
200    rng: ChaCha20Rng,
201}
202
203impl MockChain {
204    // CONSTANTS
205    // ----------------------------------------------------------------------------------------
206
207    /// The timestamp of the genesis block of the chain. Chosen as an easily readable number.
208    pub const TIMESTAMP_START_SECS: u32 = 1700000000;
209
210    /// The number of seconds by which a block's timestamp increases over the previous block's
211    /// timestamp, unless overwritten when calling [`Self::prove_next_block_at`].
212    pub const TIMESTAMP_STEP_SECS: u32 = 10;
213
214    // CONSTRUCTORS
215    // ----------------------------------------------------------------------------------------
216
217    /// Creates a new `MockChain` with an empty genesis block.
218    pub fn new() -> Self {
219        Self::builder().build().expect("empty chain should be valid")
220    }
221
222    /// Returns a new, empty [`MockChainBuilder`].
223    pub fn builder() -> MockChainBuilder {
224        MockChainBuilder::new()
225    }
226
227    /// Creates a new `MockChain` with the provided genesis block and account tree.
228    pub(super) fn from_genesis_block(
229        genesis_block: ProvenBlock,
230        account_tree: AccountTree,
231        account_credentials: BTreeMap<AccountId, AccountCredentials>,
232    ) -> anyhow::Result<Self> {
233        let mut chain = MockChain {
234            chain: Blockchain::default(),
235            blocks: vec![],
236            nullifier_tree: NullifierTree::default(),
237            account_tree,
238            pending_output_notes: Vec::new(),
239            pending_transactions: Vec::new(),
240            committed_notes: BTreeMap::new(),
241            committed_accounts: BTreeMap::new(),
242            account_credentials,
243            // Initialize RNG with default seed.
244            rng: ChaCha20Rng::from_seed(Default::default()),
245        };
246
247        // We do not have to apply the tree changes, because the account tree is already initialized
248        // and the nullifier tree is empty at genesis.
249        chain
250            .apply_block(genesis_block)
251            .context("failed to build account from builder")?;
252
253        debug_assert_eq!(chain.blocks.len(), 1);
254        debug_assert_eq!(chain.committed_accounts.len(), chain.account_tree.num_accounts());
255
256        Ok(chain)
257    }
258
259    // PUBLIC ACCESSORS
260    // ----------------------------------------------------------------------------------------
261
262    /// Returns a reference to the current [`Blockchain`].
263    pub fn blockchain(&self) -> &Blockchain {
264        &self.chain
265    }
266
267    /// Returns a [`PartialBlockchain`] instantiated from the current [`Blockchain`] and with
268    /// authentication paths for all all blocks in the chain.
269    pub fn latest_partial_blockchain(&self) -> PartialBlockchain {
270        // We have to exclude the latest block because we need to fetch the state of the chain at
271        // that latest block, which does not include itself.
272        let block_headers =
273            self.blocks.iter().map(|b| b.header()).take(self.blocks.len() - 1).cloned();
274
275        PartialBlockchain::from_blockchain(&self.chain, block_headers)
276            .expect("blockchain should be valid by construction")
277    }
278
279    /// Creates a new [`PartialBlockchain`] with all reference blocks in the given iterator except
280    /// for the latest block header in the chain and returns that latest block header.
281    ///
282    /// The intended use for the latest block header is to become the reference block of a new
283    /// transaction batch or block.
284    pub fn latest_selective_partial_blockchain(
285        &self,
286        reference_blocks: impl IntoIterator<Item = BlockNumber>,
287    ) -> anyhow::Result<(BlockHeader, PartialBlockchain)> {
288        let latest_block_header = self.latest_block_header();
289
290        self.selective_partial_blockchain(latest_block_header.block_num(), reference_blocks)
291    }
292
293    /// Creates a new [`PartialBlockchain`] with all reference blocks in the given iterator except
294    /// for the reference block header in the chain and returns that reference block header.
295    ///
296    /// The intended use for the reference block header is to become the reference block of a new
297    /// transaction batch or block.
298    pub fn selective_partial_blockchain(
299        &self,
300        reference_block: BlockNumber,
301        reference_blocks: impl IntoIterator<Item = BlockNumber>,
302    ) -> anyhow::Result<(BlockHeader, PartialBlockchain)> {
303        let reference_block_header = self.block_header(reference_block.as_usize());
304        // Deduplicate block numbers so each header will be included just once. This is required so
305        // PartialBlockchain::from_blockchain does not panic.
306        let reference_blocks: BTreeSet<_> = reference_blocks.into_iter().collect();
307
308        // Include all block headers except the reference block itself.
309        let mut block_headers = Vec::new();
310
311        for block_ref_num in &reference_blocks {
312            let block_index = block_ref_num.as_usize();
313            let block = self
314                .blocks
315                .get(block_index)
316                .ok_or_else(|| anyhow::anyhow!("block {} not found in chain", block_ref_num))?;
317            let block_header = block.header().clone();
318            // Exclude the reference block header.
319            if block_header.commitment() != reference_block_header.commitment() {
320                block_headers.push(block_header);
321            }
322        }
323
324        let partial_blockchain =
325            PartialBlockchain::from_blockchain_at(&self.chain, reference_block, block_headers)?;
326
327        Ok((reference_block_header, partial_blockchain))
328    }
329
330    /// Returns a map of [`AccountWitness`]es for the requested account IDs from the current
331    /// [`AccountTree`] in the chain.
332    pub fn account_witnesses(
333        &self,
334        account_ids: impl IntoIterator<Item = AccountId>,
335    ) -> BTreeMap<AccountId, AccountWitness> {
336        let mut account_witnesses = BTreeMap::new();
337
338        for account_id in account_ids {
339            let witness = self.account_tree.open(account_id);
340            account_witnesses.insert(account_id, witness);
341        }
342
343        account_witnesses
344    }
345
346    /// Returns a map of [`NullifierWitness`]es for the requested nullifiers from the current
347    /// [`NullifierTree`] in the chain.
348    pub fn nullifier_witnesses(
349        &self,
350        nullifiers: impl IntoIterator<Item = Nullifier>,
351    ) -> BTreeMap<Nullifier, NullifierWitness> {
352        let mut nullifier_proofs = BTreeMap::new();
353
354        for nullifier in nullifiers {
355            let witness = self.nullifier_tree.open(&nullifier);
356            nullifier_proofs.insert(nullifier, witness);
357        }
358
359        nullifier_proofs
360    }
361
362    /// Returns all note inclusion proofs for the requested note IDs, **if they are available for
363    /// consumption**. Therefore, not all of the requested notes will be guaranteed to have an entry
364    /// in the returned map.
365    pub fn unauthenticated_note_proofs(
366        &self,
367        notes: impl IntoIterator<Item = NoteId>,
368    ) -> BTreeMap<NoteId, NoteInclusionProof> {
369        let mut proofs = BTreeMap::default();
370        for note in notes {
371            if let Some(input_note) = self.committed_notes.get(&note) {
372                proofs.insert(note, input_note.inclusion_proof().clone());
373            }
374        }
375
376        proofs
377    }
378
379    /// Returns the genesis [`BlockHeader`] of the chain.
380    pub fn genesis_block_header(&self) -> BlockHeader {
381        self.block_header(BlockNumber::GENESIS.as_usize())
382    }
383
384    /// Returns the latest [`BlockHeader`] in the chain.
385    pub fn latest_block_header(&self) -> BlockHeader {
386        let chain_tip =
387            self.chain.chain_tip().expect("chain should contain at least the genesis block");
388        self.blocks[chain_tip.as_usize()].header().clone()
389    }
390
391    /// Returns the [`BlockHeader`] with the specified `block_number`.
392    ///
393    /// # Panics
394    ///
395    /// - If the block number does not exist in the chain.
396    pub fn block_header(&self, block_number: usize) -> BlockHeader {
397        self.blocks[block_number].header().clone()
398    }
399
400    /// Returns a reference to slice of all created proven blocks.
401    pub fn proven_blocks(&self) -> &[ProvenBlock] {
402        &self.blocks
403    }
404
405    /// Returns the [`AccountId`] of the faucet whose assets are accepted for fee payments in the
406    /// transaction kernel, or in other words, the native asset of the blockchain.
407    ///
408    /// This value is taken from the genesis block because it is assumed not to change throughout
409    /// the chain's lifecycle.
410    pub fn native_asset_id(&self) -> AccountId {
411        self.genesis_block_header().fee_parameters().native_asset_id()
412    }
413
414    /// Returns a reference to the nullifier tree.
415    pub fn nullifier_tree(&self) -> &NullifierTree {
416        &self.nullifier_tree
417    }
418
419    /// Returns the map of note IDs to committed notes.
420    ///
421    /// These notes are committed for authenticated consumption.
422    pub fn committed_notes(&self) -> &BTreeMap<NoteId, MockChainNote> {
423        &self.committed_notes
424    }
425
426    /// Returns an [`InputNote`] for the given note ID. If the note does not exist or is not
427    /// public, `None` is returned.
428    pub fn get_public_note(&self, note_id: &NoteId) -> Option<InputNote> {
429        let note = self.committed_notes.get(note_id)?;
430        note.clone().try_into().ok()
431    }
432
433    /// Returns a reference to the account identified by the given account ID.
434    ///
435    /// The account is retrieved with the latest state known to the [`MockChain`].
436    pub fn committed_account(&self, account_id: AccountId) -> anyhow::Result<&Account> {
437        self.committed_accounts
438            .get(&account_id)
439            .with_context(|| format!("account {account_id} not found in committed accounts"))
440    }
441
442    /// Returns a reference to the [`AccountTree`] of the chain.
443    pub fn account_tree(&self) -> &AccountTree {
444        &self.account_tree
445    }
446
447    // BATCH APIS
448    // ----------------------------------------------------------------------------------------
449
450    /// Proposes a new transaction batch from the provided transactions and returns it.
451    ///
452    /// This method does not modify the chain state.
453    pub fn propose_transaction_batch<I>(
454        &self,
455        txs: impl IntoIterator<Item = ProvenTransaction, IntoIter = I>,
456    ) -> anyhow::Result<ProposedBatch>
457    where
458        I: Iterator<Item = ProvenTransaction> + Clone,
459    {
460        let transactions: Vec<_> = txs.into_iter().map(alloc::sync::Arc::new).collect();
461
462        let (batch_reference_block, partial_blockchain, unauthenticated_note_proofs) = self
463            .get_batch_inputs(
464                transactions.iter().map(|tx| tx.ref_block_num()),
465                transactions
466                    .iter()
467                    .flat_map(|tx| tx.unauthenticated_notes().map(NoteHeader::id)),
468            )?;
469
470        Ok(ProposedBatch::new(
471            transactions,
472            batch_reference_block,
473            partial_blockchain,
474            unauthenticated_note_proofs,
475        )?)
476    }
477
478    /// Mock-proves a proposed transaction batch from the provided [`ProposedBatch`] and returns it.
479    ///
480    /// This method does not modify the chain state.
481    pub fn prove_transaction_batch(
482        &self,
483        proposed_batch: ProposedBatch,
484    ) -> anyhow::Result<ProvenBatch> {
485        let (
486            transactions,
487            block_header,
488            _partial_blockchain,
489            _unauthenticated_note_proofs,
490            id,
491            account_updates,
492            input_notes,
493            output_notes,
494            batch_expiration_block_num,
495        ) = proposed_batch.into_parts();
496
497        // SAFETY: This satisfies the requirements of the ordered tx headers.
498        let tx_headers = OrderedTransactionHeaders::new_unchecked(
499            transactions
500                .iter()
501                .map(AsRef::as_ref)
502                .map(TransactionHeader::from)
503                .collect::<Vec<_>>(),
504        );
505
506        Ok(ProvenBatch::new(
507            id,
508            block_header.commitment(),
509            block_header.block_num(),
510            account_updates,
511            input_notes,
512            output_notes,
513            batch_expiration_block_num,
514            tx_headers,
515        )?)
516    }
517
518    // BLOCK APIS
519    // ----------------------------------------------------------------------------------------
520
521    /// Proposes a new block from the provided batches with the given timestamp and returns it.
522    ///
523    /// This method does not modify the chain state.
524    pub fn propose_block_at<I>(
525        &self,
526        batches: impl IntoIterator<Item = ProvenBatch, IntoIter = I>,
527        timestamp: u32,
528    ) -> anyhow::Result<ProposedBlock>
529    where
530        I: Iterator<Item = ProvenBatch> + Clone,
531    {
532        let batches: Vec<_> = batches.into_iter().collect();
533
534        let block_inputs = self
535            .get_block_inputs(batches.iter())
536            .context("could not retrieve block inputs")?;
537
538        let proposed_block = ProposedBlock::new_at(block_inputs, batches, timestamp)
539            .context("failed to create proposed block")?;
540
541        Ok(proposed_block)
542    }
543
544    /// Proposes a new block from the provided batches and returns it.
545    ///
546    /// This method does not modify the chain state.
547    pub fn propose_block<I>(
548        &self,
549        batches: impl IntoIterator<Item = ProvenBatch, IntoIter = I>,
550    ) -> anyhow::Result<ProposedBlock>
551    where
552        I: Iterator<Item = ProvenBatch> + Clone,
553    {
554        // We can't access system time because we are in a no-std environment, so we use the
555        // minimally correct next timestamp.
556        let timestamp = self.latest_block_header().timestamp() + 1;
557
558        self.propose_block_at(batches, timestamp)
559    }
560
561    /// Mock-proves a proposed block into a proven block and returns it.
562    ///
563    /// This method does not modify the chain state.
564    pub fn prove_block(
565        &self,
566        proposed_block: ProposedBlock,
567    ) -> Result<ProvenBlock, ProvenBlockError> {
568        LocalBlockProver::new(0).prove_without_batch_verification(proposed_block)
569    }
570
571    // TRANSACTION APIS
572    // ----------------------------------------------------------------------------------------
573
574    /// Initializes a [`TransactionContextBuilder`] for executing against a specific block number.
575    ///
576    /// Depending on the provided `input`, the builder is initialized differently:
577    /// - [`TxContextInput::AccountId`]: Initialize the builder with [`TransactionInputs`] fetched
578    ///   from the chain for the public account identified by the ID.
579    /// - [`TxContextInput::Account`]: Initialize the builder with [`TransactionInputs`] where the
580    ///   account is passed as-is to the inputs.
581    /// - [`TxContextInput::ExecutedTransaction`]: Initialize the builder with [`TransactionInputs`]
582    ///   where the account passed to the inputs is the final account of the executed transaction.
583    ///   This is the initial account of the transaction with the account delta applied.
584    ///
585    /// In all cases, if the chain contains a seed or authenticator for the account, they are added
586    /// to the builder.
587    ///
588    /// [`TxContextInput::Account`] and [`TxContextInput::ExecutedTransaction`] can be used to build
589    /// a chain of transactions against the same account that build on top of each other. For
590    /// example, transaction A modifies an account from state 0 to 1, and transaction B modifies
591    /// it from state 1 to 2.
592    pub fn build_tx_context_at(
593        &self,
594        reference_block: impl Into<BlockNumber>,
595        input: impl Into<TxContextInput>,
596        note_ids: &[NoteId],
597        unauthenticated_notes: &[Note],
598    ) -> anyhow::Result<TransactionContextBuilder> {
599        let input = input.into();
600        let reference_block = reference_block.into();
601
602        let credentials = self.account_credentials.get(&input.id());
603        let authenticator =
604            credentials.and_then(|credentials| credentials.authenticator().cloned());
605        let seed = credentials.and_then(|credentials| credentials.seed());
606
607        anyhow::ensure!(
608            reference_block.as_usize() < self.blocks.len(),
609            "reference block {reference_block} is out of range (latest {})",
610            self.latest_block_header().block_num()
611        );
612
613        let account = match input {
614            TxContextInput::AccountId(account_id) => {
615                if account_id.is_private() {
616                    return Err(anyhow::anyhow!(
617                        "transaction contexts for private accounts should be created with TxContextInput::Account"
618                    ));
619                }
620
621                self.committed_accounts
622                    .get(&account_id)
623                    .with_context(|| {
624                        format!("account {account_id} not found in committed accounts")
625                    })?
626                    .clone()
627            },
628            TxContextInput::Account(account) => account,
629            TxContextInput::ExecutedTransaction(executed_transaction) => {
630                let mut initial_account = executed_transaction.initial_account().clone();
631                initial_account
632                    .apply_delta(executed_transaction.account_delta())
633                    .context("could not apply delta from previous transaction")?;
634
635                initial_account
636            },
637        };
638
639        let tx_inputs = self
640            .get_transaction_inputs_at(
641                reference_block,
642                account.clone(),
643                seed,
644                note_ids,
645                unauthenticated_notes,
646            )
647            .context("failed to gather transaction inputs")?;
648
649        let tx_context_builder = TransactionContextBuilder::new(account)
650            .authenticator(authenticator)
651            .account_seed(seed)
652            .tx_inputs(tx_inputs);
653
654        Ok(tx_context_builder)
655    }
656
657    /// Initializes a [`TransactionContextBuilder`] for executing against the last block header.
658    ///
659    /// This is a wrapper around [`Self::build_tx_context_at`] which uses the latest block as the
660    /// reference block. See that function's docs for details.
661    pub fn build_tx_context(
662        &self,
663        input: impl Into<TxContextInput>,
664        note_ids: &[NoteId],
665        unauthenticated_notes: &[Note],
666    ) -> anyhow::Result<TransactionContextBuilder> {
667        let reference_block = self.latest_block_header().block_num();
668        self.build_tx_context_at(reference_block, input, note_ids, unauthenticated_notes)
669    }
670
671    // INPUTS APIS
672    // ----------------------------------------------------------------------------------------
673
674    /// Returns a valid [`TransactionInputs`] for the specified entities, executing against a
675    /// specific block number.
676    pub fn get_transaction_inputs_at(
677        &self,
678        reference_block: BlockNumber,
679        account: Account,
680        account_seed: Option<Word>,
681        notes: &[NoteId],
682        unauthenticated_notes: &[Note],
683    ) -> anyhow::Result<TransactionInputs> {
684        let ref_block = self.block_header(reference_block.as_usize());
685
686        let mut input_notes = vec![];
687        let mut block_headers_map: BTreeMap<BlockNumber, BlockHeader> = BTreeMap::new();
688        for note in notes {
689            let input_note: InputNote = self
690                .committed_notes
691                .get(note)
692                .with_context(|| format!("note with id {note} not found"))?
693                .clone()
694                .try_into()
695                .with_context(|| {
696                    format!("failed to convert mock chain note with id {note} into input note")
697                })?;
698
699            let note_block_num = input_note
700                .location()
701                .with_context(|| format!("note location not available: {note}"))?
702                .block_num();
703
704            if note_block_num > ref_block.block_num() {
705                anyhow::bail!(
706                    "note with ID {note} was created in block {note_block_num} which is larger than the reference block number {}",
707                    ref_block.block_num()
708                )
709            }
710
711            if note_block_num != ref_block.block_num() {
712                let block_header = self
713                    .blocks
714                    .get(note_block_num.as_usize())
715                    .with_context(|| format!("block {note_block_num} not found in chain"))?
716                    .header()
717                    .clone();
718                block_headers_map.insert(note_block_num, block_header);
719            }
720
721            input_notes.push(input_note);
722        }
723
724        for note in unauthenticated_notes {
725            input_notes.push(InputNote::Unauthenticated { note: note.clone() })
726        }
727
728        let block_headers = block_headers_map.values();
729        let (_, partial_blockchain) = self.selective_partial_blockchain(
730            reference_block,
731            block_headers.map(BlockHeader::block_num),
732        )?;
733
734        let input_notes = InputNotes::new(input_notes)?;
735
736        Ok(TransactionInputs::new(
737            account,
738            account_seed,
739            ref_block.clone(),
740            partial_blockchain,
741            input_notes,
742        )?)
743    }
744
745    /// Returns a valid [`TransactionInputs`] for the specified entities.
746    pub fn get_transaction_inputs(
747        &self,
748        account: Account,
749        account_seed: Option<Word>,
750        notes: &[NoteId],
751        unauthenticated_notes: &[Note],
752    ) -> anyhow::Result<TransactionInputs> {
753        let latest_block_num = self.latest_block_header().block_num();
754        self.get_transaction_inputs_at(
755            latest_block_num,
756            account,
757            account_seed,
758            notes,
759            unauthenticated_notes,
760        )
761    }
762
763    /// Returns inputs for a transaction batch for all the reference blocks of the provided
764    /// transactions.
765    pub fn get_batch_inputs(
766        &self,
767        tx_reference_blocks: impl IntoIterator<Item = BlockNumber>,
768        unauthenticated_notes: impl Iterator<Item = NoteId>,
769    ) -> anyhow::Result<(BlockHeader, PartialBlockchain, BTreeMap<NoteId, NoteInclusionProof>)>
770    {
771        // Fetch note proofs for notes that exist in the chain.
772        let unauthenticated_note_proofs = self.unauthenticated_note_proofs(unauthenticated_notes);
773
774        // We also need to fetch block inclusion proofs for any of the blocks that contain
775        // unauthenticated notes for which we want to prove inclusion.
776        let required_blocks = tx_reference_blocks.into_iter().chain(
777            unauthenticated_note_proofs
778                .values()
779                .map(|note_proof| note_proof.location().block_num()),
780        );
781
782        let (batch_reference_block, partial_block_chain) =
783            self.latest_selective_partial_blockchain(required_blocks)?;
784
785        Ok((batch_reference_block, partial_block_chain, unauthenticated_note_proofs))
786    }
787
788    /// Gets foreign account inputs to execute FPI transactions.
789    pub fn get_foreign_account_inputs(
790        &self,
791        account_id: AccountId,
792    ) -> anyhow::Result<AccountInputs> {
793        let account = self.committed_account(account_id)?;
794
795        let account_witness = self.account_tree().open(account_id);
796        assert_eq!(account_witness.state_commitment(), account.commitment());
797
798        let mut storage_map_proofs = vec![];
799        for slot in account.storage().slots() {
800            // if there are storage maps, we populate the merkle store and advice map
801            if let StorageSlot::Map(map) = slot {
802                let proofs: Vec<SmtProof> = map.entries().map(|(key, _)| map.open(key)).collect();
803                storage_map_proofs.extend(proofs);
804            }
805        }
806
807        Ok(AccountInputs::new(account.into(), account_witness))
808    }
809
810    /// Gets the inputs for a block for the provided batches.
811    pub fn get_block_inputs<'batch, I>(
812        &self,
813        batch_iter: impl IntoIterator<Item = &'batch ProvenBatch, IntoIter = I>,
814    ) -> anyhow::Result<BlockInputs>
815    where
816        I: Iterator<Item = &'batch ProvenBatch> + Clone,
817    {
818        let batch_iterator = batch_iter.into_iter();
819
820        let unauthenticated_note_proofs =
821            self.unauthenticated_note_proofs(batch_iterator.clone().flat_map(|batch| {
822                batch.input_notes().iter().filter_map(|note| note.header().map(NoteHeader::id))
823            }));
824
825        let (block_reference_block, partial_blockchain) = self
826            .latest_selective_partial_blockchain(
827                batch_iterator.clone().map(ProvenBatch::reference_block_num).chain(
828                    unauthenticated_note_proofs.values().map(|proof| proof.location().block_num()),
829                ),
830            )?;
831
832        let account_witnesses =
833            self.account_witnesses(batch_iterator.clone().flat_map(ProvenBatch::updated_accounts));
834
835        let nullifier_proofs =
836            self.nullifier_witnesses(batch_iterator.flat_map(ProvenBatch::created_nullifiers));
837
838        Ok(BlockInputs::new(
839            block_reference_block,
840            partial_blockchain,
841            account_witnesses,
842            nullifier_proofs,
843            unauthenticated_note_proofs,
844        ))
845    }
846
847    // PUBLIC MUTATORS
848    // ----------------------------------------------------------------------------------------
849
850    /// Creates the next block in the mock chain.
851    ///
852    /// This will make all the objects currently pending available for use.
853    pub fn prove_next_block(&mut self) -> anyhow::Result<ProvenBlock> {
854        self.prove_block_inner(None)
855    }
856
857    /// Proves the next block in the mock chain at the given timestamp.
858    pub fn prove_next_block_at(&mut self, timestamp: u32) -> anyhow::Result<ProvenBlock> {
859        self.prove_block_inner(Some(timestamp))
860    }
861
862    /// Proves new blocks until the block with the given target block number has been created.
863    ///
864    /// For example, if the latest block is `5` and this function is called with `10`, then blocks
865    /// `6..=10` will be created and block 10 will be returned.
866    ///
867    /// # Panics
868    ///
869    /// Panics if:
870    /// - the given block number is smaller or equal to the number of the latest block in the chain.
871    pub fn prove_until_block(
872        &mut self,
873        target_block_num: impl Into<BlockNumber>,
874    ) -> anyhow::Result<ProvenBlock> {
875        let target_block_num = target_block_num.into();
876        let latest_block_num = self.latest_block_header().block_num();
877        assert!(
878            target_block_num > latest_block_num,
879            "target block number must be greater than the number of the latest block in the chain"
880        );
881
882        let mut last_block = None;
883        for _ in latest_block_num.as_usize()..target_block_num.as_usize() {
884            last_block = Some(self.prove_next_block()?);
885        }
886
887        Ok(last_block.expect("at least one block should have been created"))
888    }
889
890    /// Sets the seed for the internal RNG.
891    pub fn set_rng_seed(&mut self, seed: [u8; 32]) {
892        self.rng = ChaCha20Rng::from_seed(seed);
893    }
894
895    // PUBLIC MUTATORS (PENDING APIS)
896    // ----------------------------------------------------------------------------------------
897
898    /// Adds the given [`ExecutedTransaction`] to the list of pending transactions.
899    ///
900    /// A block has to be created to apply the transaction effects to the chain state, e.g. using
901    /// [`MockChain::prove_next_block`].
902    ///
903    /// Returns the resulting state of the executing account after executing the transaction.
904    pub fn add_pending_executed_transaction(
905        &mut self,
906        transaction: &ExecutedTransaction,
907    ) -> anyhow::Result<Account> {
908        let mut account = transaction.initial_account().clone();
909        account.apply_delta(transaction.account_delta())?;
910
911        // This essentially transforms an executed tx into a proven tx with a dummy proof.
912        let proven_tx = ProvenTransaction::from_executed_transaction_mocked(transaction.clone());
913
914        self.pending_transactions.push(proven_tx);
915
916        Ok(account)
917    }
918
919    /// Adds the given [`ProvenTransaction`] to the list of pending transactions.
920    ///
921    /// A block has to be created to apply the transaction effects to the chain state, e.g. using
922    /// [`MockChain::prove_next_block`].
923    pub fn add_pending_proven_transaction(&mut self, transaction: ProvenTransaction) {
924        self.pending_transactions.push(transaction);
925    }
926
927    /// Adds the given [`OutputNote`] to the list of pending notes.
928    ///
929    /// A block has to be created to add the note to that block and make it available in the chain
930    /// state, e.g. using [`MockChain::prove_next_block`].
931    pub fn add_pending_note(&mut self, note: OutputNote) {
932        self.pending_output_notes.push(note);
933    }
934
935    /// Adds a plain P2ID [`OutputNote`] to the list of pending notes.
936    ///
937    /// The note is immediately spendable by `target_account_id` and carries no
938    /// additional reclaim or timelock conditions.
939    pub fn add_pending_p2id_note(
940        &mut self,
941        sender_account_id: AccountId,
942        target_account_id: AccountId,
943        asset: &[Asset],
944        note_type: NoteType,
945    ) -> Result<Note, NoteError> {
946        let mut rng = RpoRandomCoin::new(Word::empty());
947
948        let note = create_p2id_note(
949            sender_account_id,
950            target_account_id,
951            asset.to_vec(),
952            note_type,
953            Default::default(),
954            &mut rng,
955        )?;
956
957        self.add_pending_note(OutputNote::Full(note.clone()));
958        Ok(note)
959    }
960
961    /// Adds a P2IDE [`OutputNote`] (pay‑to‑ID‑extended) to the list of pending notes.
962    ///
963    /// A P2IDE note can include an optional `timelock_height` and/or an optional
964    /// `reclaim_height` after which the `sender_account_id` may reclaim the
965    /// funds.
966    pub fn add_pending_p2ide_note(
967        &mut self,
968        sender_account_id: AccountId,
969        target_account_id: AccountId,
970        asset: &[Asset],
971        note_type: NoteType,
972        reclaim_height: Option<BlockNumber>,
973        timelock_height: Option<BlockNumber>,
974    ) -> Result<Note, NoteError> {
975        let mut rng = RpoRandomCoin::new(Word::empty());
976
977        let note = create_p2ide_note(
978            sender_account_id,
979            target_account_id,
980            asset.to_vec(),
981            reclaim_height,
982            timelock_height,
983            note_type,
984            Default::default(),
985            &mut rng,
986        )?;
987
988        self.add_pending_note(OutputNote::Full(note.clone()));
989        Ok(note)
990    }
991
992    // PRIVATE HELPERS
993    // ----------------------------------------------------------------------------------------
994
995    /// Applies the given block to the chain state, which means:
996    ///
997    /// - Insert account and nullifiers into the respective trees.
998    /// - Updated accounts from the block are updated in the committed accounts.
999    /// - Created notes are inserted into the committed notes.
1000    /// - Consumed notes are removed from the committed notes.
1001    /// - The block is appended to the [`BlockChain`] and the list of proven blocks.
1002    fn apply_block(&mut self, proven_block: ProvenBlock) -> anyhow::Result<()> {
1003        for account_update in proven_block.updated_accounts() {
1004            self.account_tree
1005                .insert(account_update.account_id(), account_update.final_state_commitment())
1006                .context("failed to insert account update into account tree")?;
1007        }
1008
1009        for nullifier in proven_block.created_nullifiers() {
1010            self.nullifier_tree
1011                .mark_spent(*nullifier, proven_block.header().block_num())
1012                .context("failed to mark block nullifier as spent")?;
1013
1014            // TODO: Remove from self.committed_notes. This is not critical to have for now. It is
1015            // not straightforward, because committed_notes are indexed by note IDs rather than
1016            // nullifiers, so we'll have to create a second index to do this.
1017        }
1018
1019        for account_update in proven_block.updated_accounts() {
1020            match account_update.details() {
1021                AccountUpdateDetails::New(account) => {
1022                    self.committed_accounts.insert(account.id(), account.clone());
1023                },
1024                AccountUpdateDetails::Delta(account_delta) => {
1025                    let committed_account =
1026                        self.committed_accounts.get_mut(&account_update.account_id()).ok_or_else(
1027                            || anyhow::anyhow!("account delta in block for non-existent account"),
1028                        )?;
1029                    committed_account
1030                        .apply_delta(account_delta)
1031                        .context("failed to apply account delta")?;
1032                },
1033                // No state to keep for private accounts other than the commitment on the account
1034                // tree
1035                AccountUpdateDetails::Private => {},
1036            }
1037        }
1038
1039        let notes_tree = proven_block.build_output_note_tree();
1040        for (block_note_index, created_note) in proven_block.output_notes() {
1041            let note_path = notes_tree.open(block_note_index);
1042            let note_inclusion_proof = NoteInclusionProof::new(
1043                proven_block.header().block_num(),
1044                block_note_index.leaf_index_value(),
1045                note_path,
1046            )
1047            .context("failed to construct note inclusion proof")?;
1048
1049            if let OutputNote::Full(note) = created_note {
1050                self.committed_notes
1051                    .insert(note.id(), MockChainNote::Public(note.clone(), note_inclusion_proof));
1052            } else {
1053                self.committed_notes.insert(
1054                    created_note.id(),
1055                    MockChainNote::Private(
1056                        created_note.id(),
1057                        *created_note.metadata(),
1058                        note_inclusion_proof,
1059                    ),
1060                );
1061            }
1062        }
1063
1064        debug_assert_eq!(
1065            self.chain.commitment(),
1066            proven_block.header().chain_commitment(),
1067            "current mock chain commitment and new block's chain commitment should match"
1068        );
1069        debug_assert_eq!(
1070            BlockNumber::from(self.chain.as_mmr().forest().num_leaves() as u32),
1071            proven_block.header().block_num(),
1072            "current mock chain length and new block's number should match"
1073        );
1074
1075        self.chain.push(proven_block.header().commitment());
1076        self.blocks.push(proven_block);
1077
1078        Ok(())
1079    }
1080
1081    fn pending_transactions_to_batches(&mut self) -> anyhow::Result<Vec<ProvenBatch>> {
1082        // Batches must contain at least one transaction, so if there are no pending transactions,
1083        // return early.
1084        if self.pending_transactions.is_empty() {
1085            return Ok(vec![]);
1086        }
1087
1088        let pending_transactions = core::mem::take(&mut self.pending_transactions);
1089
1090        // TODO: Distribute the transactions into multiple batches if the transactions would not fit
1091        // into a single batch (according to max input notes, max output notes and max accounts).
1092        let proposed_batch = self.propose_transaction_batch(pending_transactions)?;
1093        let proven_batch = self.prove_transaction_batch(proposed_batch)?;
1094
1095        Ok(vec![proven_batch])
1096    }
1097
1098    fn apply_pending_notes_to_block(
1099        &mut self,
1100        proven_block: &mut ProvenBlock,
1101    ) -> anyhow::Result<()> {
1102        // Add pending output notes to block.
1103        let output_notes_block: BTreeSet<NoteId> =
1104            proven_block.output_notes().map(|(_, output_note)| output_note.id()).collect();
1105
1106        // We could distribute notes over multiple batches (if space is available), but most likely
1107        // one is sufficient.
1108        if self.pending_output_notes.len() > MAX_OUTPUT_NOTES_PER_BATCH {
1109            return Err(anyhow::anyhow!(
1110                "too many pending output notes: {}, max allowed: {MAX_OUTPUT_NOTES_PER_BATCH}",
1111                self.pending_output_notes.len(),
1112            ));
1113        }
1114
1115        let mut pending_note_batch = Vec::with_capacity(self.pending_output_notes.len());
1116        let pending_output_notes = core::mem::take(&mut self.pending_output_notes);
1117        for (note_idx, output_note) in pending_output_notes.into_iter().enumerate() {
1118            if output_notes_block.contains(&output_note.id()) {
1119                return Err(anyhow::anyhow!(
1120                    "output note {} is already created in block through transactions",
1121                    output_note.id()
1122                ));
1123            }
1124
1125            pending_note_batch.push((note_idx, output_note));
1126        }
1127
1128        if (proven_block.output_note_batches().len() + 1) > MAX_BATCHES_PER_BLOCK {
1129            return Err(anyhow::anyhow!(
1130                "too many batches in block: cannot add more pending notes".to_string(),
1131            ));
1132        }
1133
1134        proven_block.output_note_batches_mut().push(pending_note_batch);
1135
1136        let updated_block_note_tree = proven_block.build_output_note_tree().root();
1137
1138        // Update note tree root in the block header.
1139        let block_header = proven_block.header();
1140        let updated_header = BlockHeader::new(
1141            block_header.version(),
1142            block_header.prev_block_commitment(),
1143            block_header.block_num(),
1144            block_header.chain_commitment(),
1145            block_header.account_root(),
1146            block_header.nullifier_root(),
1147            updated_block_note_tree,
1148            block_header.tx_commitment(),
1149            block_header.tx_kernel_commitment(),
1150            block_header.proof_commitment(),
1151            block_header.fee_parameters().clone(),
1152            block_header.timestamp(),
1153        );
1154        proven_block.set_block_header(updated_header);
1155
1156        Ok(())
1157    }
1158
1159    /// Creates a new block in the mock chain.
1160    ///
1161    /// This will make all the objects currently pending available for use.
1162    ///
1163    /// If a `timestamp` is provided, it will be set on the block.
1164    ///
1165    /// Block building is divided into a few steps:
1166    ///
1167    /// 1. Build batches from pending transactions and a block from those batches. This results in a
1168    ///    block.
1169    /// 2. Take the pending notes and insert them directly into the proven block. This means we have
1170    ///    to update the header of the block with the updated block note tree root.
1171    /// 3. Finally, the block contains both the updates from the regular transactions/batches as
1172    ///    well as the pending notes. Now insert all the accounts, nullifier and notes into the
1173    ///    chain state.
1174    fn prove_block_inner(&mut self, timestamp: Option<u32>) -> anyhow::Result<ProvenBlock> {
1175        // Create batches from pending transactions.
1176        // ----------------------------------------------------------------------------------------
1177
1178        let batches = self.pending_transactions_to_batches()?;
1179
1180        // Create block.
1181        // ----------------------------------------------------------------------------------------
1182
1183        let block_timestamp =
1184            timestamp.unwrap_or(self.latest_block_header().timestamp() + Self::TIMESTAMP_STEP_SECS);
1185
1186        let proposed_block = self
1187            .propose_block_at(batches, block_timestamp)
1188            .context("failed to create proposed block")?;
1189        let mut proven_block = self.prove_block(proposed_block).context("failed to prove block")?;
1190
1191        if !self.pending_output_notes.is_empty() {
1192            self.apply_pending_notes_to_block(&mut proven_block)?;
1193        }
1194
1195        self.apply_block(proven_block.clone()).context("failed to apply block")?;
1196
1197        Ok(proven_block)
1198    }
1199}
1200
1201impl Default for MockChain {
1202    fn default() -> Self {
1203        MockChain::new()
1204    }
1205}
1206
1207// SERIALIZATION
1208// ================================================================================================
1209
1210impl Serializable for MockChain {
1211    fn write_into<W: ByteWriter>(&self, target: &mut W) {
1212        self.chain.write_into(target);
1213        self.blocks.write_into(target);
1214        self.nullifier_tree.write_into(target);
1215        self.account_tree.write_into(target);
1216        self.pending_output_notes.write_into(target);
1217        self.pending_transactions.write_into(target);
1218        self.committed_accounts.write_into(target);
1219        self.committed_notes.write_into(target);
1220        self.account_credentials.write_into(target);
1221    }
1222}
1223
1224impl Deserializable for MockChain {
1225    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
1226        let chain = Blockchain::read_from(source)?;
1227        let blocks = Vec::<ProvenBlock>::read_from(source)?;
1228        let nullifier_tree = NullifierTree::read_from(source)?;
1229        let account_tree = AccountTree::read_from(source)?;
1230        let pending_output_notes = Vec::<OutputNote>::read_from(source)?;
1231        let pending_transactions = Vec::<ProvenTransaction>::read_from(source)?;
1232        let committed_accounts = BTreeMap::<AccountId, Account>::read_from(source)?;
1233        let committed_notes = BTreeMap::<NoteId, MockChainNote>::read_from(source)?;
1234        let account_credentials = BTreeMap::<AccountId, AccountCredentials>::read_from(source)?;
1235
1236        Ok(Self {
1237            chain,
1238            blocks,
1239            nullifier_tree,
1240            account_tree,
1241            pending_output_notes,
1242            pending_transactions,
1243            committed_notes,
1244            committed_accounts,
1245            account_credentials,
1246            rng: ChaCha20Rng::from_os_rng(),
1247        })
1248    }
1249}
1250
1251// ACCOUNT STATE
1252// ================================================================================================
1253
1254/// Helper type for increased readability at call-sites. Indicates whether to build a new (nonce =
1255/// ZERO) or existing account (nonce = ONE).
1256pub enum AccountState {
1257    New,
1258    Exists,
1259}
1260
1261// ACCOUNT CREDENTIALS
1262// ================================================================================================
1263
1264/// A wrapper around the seed and authenticator of an account.
1265#[derive(Debug, Clone)]
1266pub(super) struct AccountCredentials {
1267    seed: Option<Word>,
1268    authenticator: Option<BasicAuthenticator<ChaCha20Rng>>,
1269}
1270
1271impl AccountCredentials {
1272    pub fn new(seed: Option<Word>, authenticator: Option<BasicAuthenticator<ChaCha20Rng>>) -> Self {
1273        Self { seed, authenticator }
1274    }
1275
1276    pub fn authenticator(&self) -> Option<&BasicAuthenticator<ChaCha20Rng>> {
1277        self.authenticator.as_ref()
1278    }
1279
1280    pub fn seed(&self) -> Option<Word> {
1281        self.seed
1282    }
1283}
1284
1285impl PartialEq for AccountCredentials {
1286    fn eq(&self, other: &Self) -> bool {
1287        let authenticator_eq = match (&self.authenticator, &other.authenticator) {
1288            (Some(a), Some(b)) => {
1289                a.keys().keys().zip(b.keys().keys()).all(|(a_key, b_key)| a_key == b_key)
1290            },
1291            (None, None) => true,
1292            _ => false,
1293        };
1294        authenticator_eq && self.seed == other.seed
1295    }
1296}
1297
1298// SERIALIZATION
1299// ================================================================================================
1300
1301impl Serializable for AccountCredentials {
1302    fn write_into<W: ByteWriter>(&self, target: &mut W) {
1303        self.seed.write_into(target);
1304        self.authenticator
1305            .as_ref()
1306            .map(|auth| auth.keys().iter().collect::<Vec<_>>())
1307            .write_into(target);
1308    }
1309}
1310
1311impl Deserializable for AccountCredentials {
1312    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
1313        let seed = Option::<Word>::read_from(source)?;
1314        let authenticator = Option::<Vec<(Word, AuthSecretKey)>>::read_from(source)?;
1315
1316        let authenticator = authenticator
1317            .map(|keys| BasicAuthenticator::new_with_rng(&keys, ChaCha20Rng::from_os_rng()));
1318
1319        Ok(Self { seed, authenticator })
1320    }
1321}
1322
1323// TX CONTEXT INPUT
1324// ================================================================================================
1325
1326/// Helper type to abstract over the inputs to [`MockChain::build_tx_context`]. See that method's
1327/// docs for details.
1328#[derive(Debug, Clone)]
1329pub enum TxContextInput {
1330    AccountId(AccountId),
1331    Account(Account),
1332    ExecutedTransaction(Box<ExecutedTransaction>),
1333}
1334
1335impl TxContextInput {
1336    /// Returns the account ID that this input references.
1337    fn id(&self) -> AccountId {
1338        match self {
1339            TxContextInput::AccountId(account_id) => *account_id,
1340            TxContextInput::Account(account) => account.id(),
1341            TxContextInput::ExecutedTransaction(executed_transaction) => {
1342                executed_transaction.account_id()
1343            },
1344        }
1345    }
1346}
1347
1348impl From<AccountId> for TxContextInput {
1349    fn from(account: AccountId) -> Self {
1350        Self::AccountId(account)
1351    }
1352}
1353
1354impl From<Account> for TxContextInput {
1355    fn from(account: Account) -> Self {
1356        Self::Account(account)
1357    }
1358}
1359
1360impl From<ExecutedTransaction> for TxContextInput {
1361    fn from(tx: ExecutedTransaction) -> Self {
1362        Self::ExecutedTransaction(Box::new(tx))
1363    }
1364}
1365
1366// TESTS
1367// ================================================================================================
1368
1369#[cfg(test)]
1370mod tests {
1371    use miden_lib::account::wallets::BasicWallet;
1372    use miden_objects::account::{AccountBuilder, AccountStorageMode};
1373    use miden_objects::asset::FungibleAsset;
1374    use miden_objects::testing::account_id::{
1375        ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
1376        ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
1377        ACCOUNT_ID_SENDER,
1378    };
1379
1380    use super::*;
1381    use crate::Auth;
1382
1383    #[test]
1384    fn prove_until_block() -> anyhow::Result<()> {
1385        let mut chain = MockChain::new();
1386        let block = chain.prove_until_block(5)?;
1387        assert_eq!(block.header().block_num(), 5u32.into());
1388        assert_eq!(chain.proven_blocks().len(), 6);
1389
1390        Ok(())
1391    }
1392
1393    #[test]
1394    fn private_account_state_update() -> anyhow::Result<()> {
1395        let faucet_id = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into()?;
1396        let account_builder = AccountBuilder::new([4; 32])
1397            .storage_mode(AccountStorageMode::Private)
1398            .with_component(BasicWallet);
1399
1400        let mut builder = MockChain::builder();
1401        let account = builder.add_account_from_builder(
1402            Auth::BasicAuth,
1403            account_builder,
1404            AccountState::New,
1405        )?;
1406
1407        let account_id = account.id();
1408        assert_eq!(account.nonce().as_int(), 0);
1409
1410        let note_1 = builder.add_p2id_note(
1411            ACCOUNT_ID_SENDER.try_into().unwrap(),
1412            account.id(),
1413            &[Asset::Fungible(FungibleAsset::new(faucet_id, 1000u64).unwrap())],
1414            NoteType::Private,
1415        )?;
1416
1417        let mut mock_chain = builder.build()?;
1418        mock_chain.prove_next_block()?;
1419
1420        let tx = mock_chain
1421            .build_tx_context(TxContextInput::Account(account), &[], &[note_1])?
1422            .build()?
1423            .execute_blocking()?;
1424
1425        mock_chain.add_pending_executed_transaction(&tx)?;
1426        mock_chain.prove_next_block()?;
1427
1428        assert!(tx.final_account().nonce().as_int() > 0);
1429        assert_eq!(
1430            tx.final_account().commitment(),
1431            mock_chain.account_tree.open(account_id).state_commitment()
1432        );
1433
1434        Ok(())
1435    }
1436
1437    #[test]
1438    fn mock_chain_serialization() {
1439        let mut builder = MockChain::builder();
1440
1441        let mut notes = vec![];
1442        for i in 0..10 {
1443            let account = builder
1444                .add_account_from_builder(
1445                    Auth::BasicAuth,
1446                    AccountBuilder::new([i; 32]).with_component(BasicWallet),
1447                    AccountState::New,
1448                )
1449                .unwrap();
1450            let note = builder
1451                .add_p2id_note(
1452                    ACCOUNT_ID_SENDER.try_into().unwrap(),
1453                    account.id(),
1454                    &[Asset::Fungible(
1455                        FungibleAsset::new(
1456                            ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET.try_into().unwrap(),
1457                            1000u64,
1458                        )
1459                        .unwrap(),
1460                    )],
1461                    NoteType::Private,
1462                )
1463                .unwrap();
1464            notes.push((account, note));
1465        }
1466
1467        let mut chain = builder.build().unwrap();
1468        for (account, note) in notes {
1469            let tx = chain
1470                .build_tx_context(TxContextInput::Account(account), &[], &[note])
1471                .unwrap()
1472                .build()
1473                .unwrap()
1474                .execute_blocking()
1475                .unwrap();
1476            chain.add_pending_executed_transaction(&tx).unwrap();
1477            chain.prove_next_block().unwrap();
1478        }
1479
1480        let bytes = chain.to_bytes();
1481
1482        let deserialized = MockChain::read_from_bytes(&bytes).unwrap();
1483
1484        assert_eq!(chain.chain.as_mmr().peaks(), deserialized.chain.as_mmr().peaks());
1485        assert_eq!(chain.blocks, deserialized.blocks);
1486        assert_eq!(chain.nullifier_tree, deserialized.nullifier_tree);
1487        assert_eq!(chain.account_tree, deserialized.account_tree);
1488        assert_eq!(chain.pending_output_notes, deserialized.pending_output_notes);
1489        assert_eq!(chain.pending_transactions, deserialized.pending_transactions);
1490        assert_eq!(chain.committed_accounts, deserialized.committed_accounts);
1491        assert_eq!(chain.committed_notes, deserialized.committed_notes);
1492        assert_eq!(chain.account_credentials, deserialized.account_credentials);
1493    }
1494}