Skip to main content

miden_testing/mock_chain/
chain.rs

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