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