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::Word;
8use miden_protocol::account::auth::{AuthSecretKey, PublicKey};
9use miden_protocol::account::{Account, AccountId, AccountUpdateDetails, PartialAccount};
10use miden_protocol::batch::{ProposedBatch, ProvenBatch};
11use miden_protocol::block::account_tree::{AccountTree, AccountWitness};
12use miden_protocol::block::nullifier_tree::{NullifierTree, NullifierWitness};
13use miden_protocol::block::{
14 BlockAccountUpdate,
15 BlockBody,
16 BlockHeader,
17 BlockInputs,
18 BlockNumber,
19 BlockSignatures,
20 Blockchain,
21 OutputNoteBatch,
22 ProposedBlock,
23 ProvenBlock,
24 ValidatorConfig,
25};
26use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SigningKey;
27use miden_protocol::note::{Note, NoteHeader, NoteId, NoteInclusionProof, Nullifier};
28use miden_protocol::protocol_config::ProtocolConfig;
29use miden_protocol::transaction::{
30 ExecutedTransaction,
31 InputNote,
32 InputNotes,
33 OrderedTransactionHeaders,
34 OutputNote,
35 PartialBlockchain,
36 ProvenTransaction,
37 TransactionInputs,
38};
39use miden_protocol::vm::ExecutionProof;
40use miden_tx::LocalTransactionProver;
41use miden_tx::auth::BasicAuthenticator;
42use miden_tx::utils::serde::{ByteReader, ByteWriter, Deserializable, Serializable};
43use miden_tx_batch::LocalBatchProver;
44
45use super::note::MockChainNote;
46use crate::{MockChainBuilder, MockTransactionBuilder};
47
48#[derive(Debug, Clone)]
184pub struct MockChain {
185 chain: Blockchain,
187
188 blocks: Vec<ProvenBlock>,
190
191 nullifier_tree: NullifierTree,
193
194 account_tree: AccountTree,
196
197 pending_transactions: Vec<ProvenTransaction>,
200
201 pending_batches: Vec<ProvenBatch>,
203
204 committed_notes: BTreeMap<NoteId, MockChainNote>,
206
207 committed_accounts: BTreeMap<AccountId, Account>,
212
213 account_authenticators: BTreeMap<AccountId, AccountAuthenticator>,
216
217 validator_secret_keys: Vec<SigningKey>,
219
220 protocol_config: ProtocolConfig,
222}
223
224impl MockChain {
225 pub const TIMESTAMP_START_SECS: u32 = 1700000000;
230
231 pub const TIMESTAMP_STEP_SECS: u32 = 10;
234
235 pub fn new() -> Self {
240 Self::builder().build().expect("empty chain should be valid")
241 }
242
243 pub fn builder() -> MockChainBuilder {
245 MockChainBuilder::new()
246 }
247
248 pub(super) fn from_genesis_block(
250 genesis_block: ProvenBlock,
251 account_tree: AccountTree,
252 account_authenticators: BTreeMap<AccountId, AccountAuthenticator>,
253 secret_keys: Vec<SigningKey>,
254 protocol_config: ProtocolConfig,
255 genesis_notes: Vec<Note>,
256 ) -> anyhow::Result<Self> {
257 let mut chain = MockChain {
258 chain: Blockchain::default(),
259 blocks: vec![],
260 nullifier_tree: NullifierTree::default(),
261 account_tree,
262 pending_transactions: Vec::new(),
263 pending_batches: Vec::new(),
264 committed_notes: BTreeMap::new(),
265 committed_accounts: BTreeMap::new(),
266 account_authenticators,
267 validator_secret_keys: secret_keys,
268 protocol_config,
269 };
270
271 chain
274 .apply_block(genesis_block)
275 .context("failed to build account from builder")?;
276
277 for note in genesis_notes {
281 if let Some(MockChainNote::Private(_, _, _, inclusion_proof)) =
282 chain.committed_notes.get(¬e.id())
283 {
284 chain.committed_notes.insert(
285 note.id(),
286 MockChainNote::Public(note.clone(), inclusion_proof.clone()),
287 );
288 }
289 }
290
291 debug_assert_eq!(chain.blocks.len(), 1);
292
293 Ok(chain)
294 }
295
296 pub fn blockchain(&self) -> &Blockchain {
301 &self.chain
302 }
303
304 pub fn latest_partial_blockchain(&self) -> PartialBlockchain {
307 let block_headers =
310 self.blocks.iter().map(|b| b.header()).take(self.blocks.len() - 1).cloned();
311
312 PartialBlockchain::from_blockchain(&self.chain, block_headers)
313 .expect("blockchain should be valid by construction")
314 }
315
316 pub fn latest_selective_partial_blockchain(
322 &self,
323 reference_blocks: impl IntoIterator<Item = BlockNumber>,
324 ) -> anyhow::Result<(BlockHeader, PartialBlockchain)> {
325 let latest_block_header = self.latest_block_header();
326
327 self.selective_partial_blockchain(latest_block_header.block_num(), reference_blocks)
328 }
329
330 pub fn selective_partial_blockchain(
336 &self,
337 reference_block: BlockNumber,
338 reference_blocks: impl IntoIterator<Item = BlockNumber>,
339 ) -> anyhow::Result<(BlockHeader, PartialBlockchain)> {
340 let reference_block_header = self.block_header(reference_block.as_usize());
341 let reference_blocks: BTreeSet<_> = reference_blocks.into_iter().collect();
344
345 let mut block_headers = Vec::new();
347
348 for block_ref_num in &reference_blocks {
349 let block_index = block_ref_num.as_usize();
350 let block = self
351 .blocks
352 .get(block_index)
353 .ok_or_else(|| anyhow::anyhow!("block {} not found in chain", block_ref_num))?;
354 let block_header = block.header().clone();
355 if block_header.commitment() != reference_block_header.commitment() {
357 block_headers.push(block_header);
358 }
359 }
360
361 let partial_blockchain =
362 PartialBlockchain::from_blockchain_at(&self.chain, reference_block, block_headers)?;
363
364 Ok((reference_block_header, partial_blockchain))
365 }
366
367 pub fn account_witnesses(
370 &self,
371 account_ids: impl IntoIterator<Item = AccountId>,
372 ) -> BTreeMap<AccountId, AccountWitness> {
373 let mut account_witnesses = BTreeMap::new();
374
375 for account_id in account_ids {
376 let witness = self.account_tree.open(account_id);
377 account_witnesses.insert(account_id, witness);
378 }
379
380 account_witnesses
381 }
382
383 pub fn nullifier_witnesses(
386 &self,
387 nullifiers: impl IntoIterator<Item = Nullifier>,
388 ) -> BTreeMap<Nullifier, NullifierWitness> {
389 let mut nullifier_proofs = BTreeMap::new();
390
391 for nullifier in nullifiers {
392 let witness = self.nullifier_tree.open(&nullifier);
393 nullifier_proofs.insert(nullifier, witness);
394 }
395
396 nullifier_proofs
397 }
398
399 pub fn unauthenticated_note_proofs(
403 &self,
404 notes: impl IntoIterator<Item = NoteId>,
405 ) -> BTreeMap<NoteId, NoteInclusionProof> {
406 let mut proofs = BTreeMap::default();
407 for note in notes {
408 if let Some(input_note) = self.committed_notes.get(¬e) {
409 proofs.insert(note, input_note.inclusion_proof().clone());
410 }
411 }
412
413 proofs
414 }
415
416 pub fn genesis_block_header(&self) -> BlockHeader {
418 self.block_header(BlockNumber::GENESIS.as_usize())
419 }
420
421 pub fn latest_block_header(&self) -> BlockHeader {
423 let chain_tip =
424 self.chain.chain_tip().expect("chain should contain at least the genesis block");
425 self.blocks[chain_tip.as_usize()].header().clone()
426 }
427
428 pub fn validator_config(&self) -> ValidatorConfig {
430 ValidatorConfig::from_signers(&self.validator_secret_keys)
431 }
432
433 fn sign_block(&self, commitment: Word) -> BlockSignatures {
436 let signatures = self
437 .validator_config()
438 .keys()
439 .iter()
440 .map(|key| {
441 let signer = self
442 .validator_secret_keys
443 .iter()
444 .find(|sk| &sk.public_key() == key)
445 .expect("a signer should exist for every validator key");
446 signer.sign(commitment)
447 })
448 .collect();
449 BlockSignatures::new(signatures).expect("signature count same as validator key count")
450 }
451
452 pub fn latest_block(&self) -> ProvenBlock {
454 let chain_tip =
455 self.chain.chain_tip().expect("chain should contain at least the genesis block");
456 self.blocks[chain_tip.as_usize()].clone()
457 }
458
459 pub fn block_header(&self, block_number: usize) -> BlockHeader {
465 self.blocks[block_number].header().clone()
466 }
467
468 pub fn proven_blocks(&self) -> &[ProvenBlock] {
470 &self.blocks
471 }
472
473 pub fn protocol_config(&self) -> &ProtocolConfig {
475 &self.protocol_config
476 }
477
478 pub fn fee_faucet_id(&self) -> AccountId {
481 self.protocol_config.fee_asset_id().faucet_id()
482 }
483
484 pub fn nullifier_tree(&self) -> &NullifierTree {
486 &self.nullifier_tree
487 }
488
489 pub fn committed_notes(&self) -> &BTreeMap<NoteId, MockChainNote> {
493 &self.committed_notes
494 }
495
496 pub fn is_note_committed(&self, note_id: &NoteId) -> bool {
498 self.committed_notes.contains_key(note_id)
499 }
500
501 pub fn is_note_consumed(&self, nullifier: &Nullifier) -> bool {
503 self.nullifier_tree.get_block_num(nullifier).is_some()
504 }
505
506 pub fn is_note_unspent(&self, nullifier: &Nullifier) -> bool {
511 !self.is_note_consumed(nullifier)
512 }
513
514 pub fn get_public_note(&self, note_id: &NoteId) -> Option<InputNote> {
517 let note = self.committed_notes.get(note_id)?;
518 note.clone().try_into().ok()
519 }
520
521 pub fn committed_account(&self, account_id: AccountId) -> anyhow::Result<&Account> {
526 self.committed_accounts
527 .get(&account_id)
528 .with_context(|| format!("account {account_id} not found in committed accounts"))
529 }
530
531 pub fn account_tree(&self) -> &AccountTree {
533 &self.account_tree
534 }
535
536 pub fn propose_transaction_batch<I>(
543 &self,
544 txs: impl IntoIterator<Item = ProvenTransaction, IntoIter = I>,
545 ) -> anyhow::Result<ProposedBatch>
546 where
547 I: Iterator<Item = ProvenTransaction> + Clone,
548 {
549 let transactions: Vec<_> = txs.into_iter().map(alloc::sync::Arc::new).collect();
550
551 let (batch_reference_block, partial_blockchain, unauthenticated_note_proofs) = self
552 .get_batch_inputs(
553 transactions.iter().map(|tx| tx.ref_block_num()),
554 transactions
555 .iter()
556 .flat_map(|tx| tx.unauthenticated_notes().map(NoteHeader::id)),
557 )?;
558
559 Ok(ProposedBatch::new_unverified(
560 transactions,
561 batch_reference_block,
562 partial_blockchain,
563 unauthenticated_note_proofs,
564 )?)
565 }
566
567 pub fn prove_transaction_batch(
571 &self,
572 proposed_batch: ProposedBatch,
573 ) -> anyhow::Result<ProvenBatch> {
574 let batch_prover = LocalBatchProver::default();
575 Ok(batch_prover.prove_dummy(proposed_batch)?)
576 }
577
578 pub fn propose_block_at<I>(
585 &self,
586 batches: impl IntoIterator<Item = ProvenBatch, IntoIter = I>,
587 timestamp: u32,
588 ) -> anyhow::Result<ProposedBlock>
589 where
590 I: Iterator<Item = ProvenBatch> + Clone,
591 {
592 let batches: Vec<_> = batches.into_iter().collect();
593
594 let block_inputs = self
595 .get_block_inputs(batches.iter())
596 .context("could not retrieve block inputs")?;
597
598 let proposed_block = ProposedBlock::new_at(block_inputs, batches, timestamp)
599 .context("failed to create proposed block")?;
600
601 Ok(proposed_block)
602 }
603
604 pub fn propose_block<I>(
608 &self,
609 batches: impl IntoIterator<Item = ProvenBatch, IntoIter = I>,
610 ) -> anyhow::Result<ProposedBlock>
611 where
612 I: Iterator<Item = ProvenBatch> + Clone,
613 {
614 let timestamp = self.latest_block_header().timestamp() + 1;
617
618 self.propose_block_at(batches, timestamp)
619 }
620
621 pub fn build_transaction(
642 &self,
643 input: impl Into<MockTransactionInput>,
644 ) -> MockTransactionBuilder<'_> {
645 MockTransactionBuilder::new(self, input)
646 }
647
648 pub(crate) fn resolve_tx_account(
653 &self,
654 input: MockTransactionInput,
655 ) -> anyhow::Result<Account> {
656 match input {
657 MockTransactionInput::AccountId(account_id) => {
658 anyhow::ensure!(
659 !account_id.is_private(),
660 "mock transactions for private accounts should be created with MockTransactionInput::Account"
661 );
662
663 self.committed_account(account_id).cloned()
664 },
665 MockTransactionInput::Account(account) => Ok(account),
666 }
667 }
668
669 pub(crate) fn account_authenticator(
671 &self,
672 account_id: AccountId,
673 ) -> Option<BasicAuthenticator> {
674 self.account_authenticators
675 .get(&account_id)
676 .and_then(|authenticator| authenticator.authenticator().cloned())
677 }
678
679 pub fn get_transaction_inputs_at(
690 &self,
691 reference_block: BlockNumber,
692 account: impl Into<PartialAccount>,
693 notes: &[NoteId],
694 unauthenticated_notes: &[Note],
695 required_blocks: impl IntoIterator<Item = BlockNumber>,
696 ) -> anyhow::Result<TransactionInputs> {
697 let ref_block = self.block_header(reference_block.as_usize());
698
699 let mut input_notes = vec![];
700 let mut block_headers_map: BTreeMap<BlockNumber, BlockHeader> = BTreeMap::new();
701
702 for block_num in required_blocks {
703 if block_num < ref_block.block_num() {
704 let block_header = self
705 .blocks
706 .get(block_num.as_usize())
707 .with_context(|| format!("block {block_num} not found in chain"))?
708 .header()
709 .clone();
710 block_headers_map.insert(block_num, block_header);
711 }
712 }
713
714 for note in notes {
715 let input_note: InputNote = self
716 .committed_notes
717 .get(note)
718 .with_context(|| format!("note with id {note} not found"))?
719 .clone()
720 .try_into()
721 .with_context(|| {
722 format!("failed to convert mock chain note with id {note} into input note")
723 })?;
724
725 let note_block_num = input_note
726 .location()
727 .with_context(|| format!("note location not available: {note}"))?
728 .block_num();
729
730 if note_block_num > ref_block.block_num() {
731 anyhow::bail!(
732 "note with ID {note} was created in block {note_block_num} which is larger than the reference block number {}",
733 ref_block.block_num()
734 )
735 }
736
737 if note_block_num != ref_block.block_num() {
738 let block_header = self
739 .blocks
740 .get(note_block_num.as_usize())
741 .with_context(|| format!("block {note_block_num} not found in chain"))?
742 .header()
743 .clone();
744 block_headers_map.insert(note_block_num, block_header);
745 }
746
747 input_notes.push(input_note);
748 }
749
750 for note in unauthenticated_notes {
751 input_notes.push(InputNote::Unauthenticated { note: note.clone() })
752 }
753
754 let block_headers = block_headers_map.values();
755 let (_, partial_blockchain) = self.selective_partial_blockchain(
756 reference_block,
757 block_headers.map(BlockHeader::block_num),
758 )?;
759
760 let input_notes = InputNotes::new(input_notes)?;
761
762 Ok(TransactionInputs::new(
763 account.into(),
764 ref_block.clone(),
765 self.protocol_config.clone(),
766 partial_blockchain,
767 input_notes,
768 )?)
769 }
770
771 pub fn get_transaction_inputs(
773 &self,
774 account: impl Into<PartialAccount>,
775 notes: &[NoteId],
776 unauthenticated_notes: &[Note],
777 ) -> anyhow::Result<TransactionInputs> {
778 let latest_block_num = self.latest_block_header().block_num();
779 self.get_transaction_inputs_at(latest_block_num, account, notes, unauthenticated_notes, [])
780 }
781
782 pub fn get_batch_inputs(
785 &self,
786 tx_reference_blocks: impl IntoIterator<Item = BlockNumber>,
787 unauthenticated_notes: impl Iterator<Item = NoteId>,
788 ) -> anyhow::Result<(BlockHeader, PartialBlockchain, BTreeMap<NoteId, NoteInclusionProof>)>
789 {
790 let unauthenticated_note_proofs = self.unauthenticated_note_proofs(unauthenticated_notes);
792
793 let required_blocks = tx_reference_blocks.into_iter().chain(
796 unauthenticated_note_proofs
797 .values()
798 .map(|note_proof| note_proof.location().block_num()),
799 );
800
801 let (batch_reference_block, partial_block_chain) =
802 self.latest_selective_partial_blockchain(required_blocks)?;
803
804 Ok((batch_reference_block, partial_block_chain, unauthenticated_note_proofs))
805 }
806
807 pub fn get_foreign_account_inputs(
812 &self,
813 input: impl Into<MockTransactionInput>,
814 ) -> anyhow::Result<(Account, AccountWitness)> {
815 let account = self.resolve_tx_account(input.into())?;
816 let account_id = account.id();
817
818 let account_witness = self.account_tree().open(account_id);
819 anyhow::ensure!(
820 account_witness.state_commitment() == account.to_commitment(),
821 "account {account_id} does not match its witness"
822 );
823
824 Ok((account, account_witness))
825 }
826
827 pub fn get_block_inputs<'batch, I>(
829 &self,
830 batch_iter: impl IntoIterator<Item = &'batch ProvenBatch, IntoIter = I>,
831 ) -> anyhow::Result<BlockInputs>
832 where
833 I: Iterator<Item = &'batch ProvenBatch> + Clone,
834 {
835 let batch_iterator = batch_iter.into_iter();
836
837 let unauthenticated_note_proofs =
838 self.unauthenticated_note_proofs(batch_iterator.clone().flat_map(|batch| {
839 batch.input_notes().iter().filter_map(|note| note.header().map(NoteHeader::id))
840 }));
841
842 let (block_reference_block, partial_blockchain) = self
843 .latest_selective_partial_blockchain(
844 batch_iterator.clone().map(ProvenBatch::reference_block_num).chain(
845 unauthenticated_note_proofs.values().map(|proof| proof.location().block_num()),
846 ),
847 )?;
848
849 let account_witnesses =
850 self.account_witnesses(batch_iterator.clone().flat_map(ProvenBatch::updated_accounts));
851
852 let nullifier_proofs =
853 self.nullifier_witnesses(batch_iterator.flat_map(ProvenBatch::created_nullifiers));
854
855 Ok(BlockInputs::new(
856 block_reference_block,
857 partial_blockchain,
858 account_witnesses,
859 nullifier_proofs,
860 unauthenticated_note_proofs,
861 ))
862 }
863
864 pub fn prove_next_block(&mut self) -> anyhow::Result<ProvenBlock> {
871 self.prove_and_apply_block(None, None)
872 }
873
874 pub fn prove_next_block_with_validator_config_rotation(
883 &mut self,
884 new_secret_keys: Vec<SigningKey>,
885 ) -> anyhow::Result<ProvenBlock> {
886 let next_config = ValidatorConfig::from_signers(&new_secret_keys);
887 let block = self.prove_and_apply_block(None, Some(next_config))?;
888 self.validator_secret_keys = new_secret_keys;
889 Ok(block)
890 }
891
892 pub fn prove_next_block_at(&mut self, timestamp: u32) -> anyhow::Result<ProvenBlock> {
896 self.prove_and_apply_block(Some(timestamp), None)
897 }
898
899 pub fn prove_until_block(
909 &mut self,
910 target_block_num: impl Into<BlockNumber>,
911 ) -> anyhow::Result<ProvenBlock> {
912 let target_block_num = target_block_num.into();
913 let latest_block_num = self.latest_block_header().block_num();
914 assert!(
915 target_block_num > latest_block_num,
916 "target block number must be greater than the number of the latest block in the chain"
917 );
918
919 let mut last_block = None;
920 for _ in latest_block_num.as_usize()..target_block_num.as_usize() {
921 last_block = Some(self.prove_next_block()?);
922 }
923
924 Ok(last_block.expect("at least one block should have been created"))
925 }
926
927 pub fn add_pending_executed_transaction(
935 &mut self,
936 transaction: &ExecutedTransaction,
937 ) -> anyhow::Result<()> {
938 let proven_tx = LocalTransactionProver::default()
940 .prove_dummy(transaction.clone())
941 .context("failed to dummy-prove executed transaction into proven transaction")?;
942
943 self.pending_transactions.push(proven_tx);
944
945 Ok(())
946 }
947
948 pub fn add_pending_proven_transaction(&mut self, transaction: ProvenTransaction) {
953 self.pending_transactions.push(transaction);
954 }
955
956 pub fn add_pending_batch(&mut self, batch: ProvenBatch) {
961 self.pending_batches.push(batch);
962 }
963
964 fn apply_block(&mut self, proven_block: ProvenBlock) -> anyhow::Result<()> {
975 if proven_block.header().block_num() != BlockNumber::GENESIS {
978 let parent = self.latest_block_header();
979 proven_block
980 .validate(Some(&parent))
981 .context("block failed validation against its parent")?;
982 }
983
984 for account_update in proven_block.body().updated_accounts() {
985 self.account_tree
986 .insert(account_update.account_id(), account_update.final_state_commitment())
987 .context("failed to insert account update into account tree")?;
988 }
989
990 for nullifier in proven_block.body().created_nullifiers() {
991 self.nullifier_tree
992 .mark_spent(*nullifier, proven_block.header().block_num())
993 .context("failed to mark block nullifier as spent")?;
994
995 }
999
1000 for account_update in proven_block.body().updated_accounts() {
1001 match account_update.details() {
1002 AccountUpdateDetails::Public(account_patch) => {
1003 match self.committed_accounts.get_mut(&account_update.account_id()) {
1006 Some(committed_account) => committed_account
1007 .apply_patch(account_patch)
1008 .context("failed to apply account patch")?,
1009 None => {
1010 let account = account_patch
1011 .try_to_new_account()
1012 .context("failed to convert creation patch into account")?;
1013 self.committed_accounts.insert(account.id(), account);
1014 },
1015 }
1016 },
1017 AccountUpdateDetails::Private => {},
1020 }
1021 }
1022
1023 let notes_tree = proven_block.body().compute_block_note_tree();
1024 for (block_note_index, created_note) in proven_block.body().output_notes() {
1025 let note_path = notes_tree.open(block_note_index);
1026 let note_inclusion_proof = NoteInclusionProof::new(
1027 proven_block.header().block_num(),
1028 block_note_index.leaf_index_value(),
1029 note_path,
1030 )
1031 .context("failed to create inclusion proof for output note")?;
1032
1033 match created_note {
1034 OutputNote::Public(public_note) => {
1035 self.committed_notes.insert(
1036 public_note.id(),
1037 MockChainNote::Public(public_note.as_note().clone(), note_inclusion_proof),
1038 );
1039 },
1040 OutputNote::Private(private_note) => {
1041 self.committed_notes.insert(
1042 private_note.id(),
1043 MockChainNote::Private(
1044 private_note.id(),
1045 *private_note.metadata(),
1046 private_note.attachments().clone(),
1047 note_inclusion_proof,
1048 ),
1049 );
1050 },
1051 }
1052 }
1053
1054 debug_assert_eq!(
1055 self.chain.commitment(),
1056 proven_block.header().chain_commitment(),
1057 "current mock chain commitment and new block's chain commitment should match"
1058 );
1059 debug_assert_eq!(
1060 BlockNumber::from(self.chain.as_mmr().forest().num_leaves() as u32),
1061 proven_block.header().block_num(),
1062 "current mock chain length and new block's number should match"
1063 );
1064
1065 self.chain.push(proven_block.header().commitment());
1066 self.blocks.push(proven_block);
1067
1068 Ok(())
1069 }
1070
1071 fn pending_transactions_to_batches(&mut self) -> anyhow::Result<Vec<ProvenBatch>> {
1072 if self.pending_transactions.is_empty() {
1075 return Ok(vec![]);
1076 }
1077
1078 let pending_transactions = core::mem::take(&mut self.pending_transactions);
1079
1080 let proposed_batch = self.propose_transaction_batch(pending_transactions)?;
1083 let proven_batch = self.prove_transaction_batch(proposed_batch)?;
1084
1085 Ok(vec![proven_batch])
1086 }
1087
1088 fn prove_and_apply_block(
1098 &mut self,
1099 timestamp: Option<u32>,
1100 next_validator_config: Option<ValidatorConfig>,
1101 ) -> anyhow::Result<ProvenBlock> {
1102 let mut batches = self.pending_transactions_to_batches()?;
1106 batches.extend(core::mem::take(&mut self.pending_batches));
1107
1108 let block_timestamp =
1112 timestamp.unwrap_or(self.latest_block_header().timestamp() + Self::TIMESTAMP_STEP_SECS);
1113
1114 let mut proposed_block = self
1115 .propose_block_at(batches.clone(), block_timestamp)
1116 .context("failed to create proposed block")?;
1117
1118 if let Some(next_validator_config) = next_validator_config {
1120 proposed_block = proposed_block.with_next_validator_config(next_validator_config);
1121 }
1122
1123 let proven_block = self.prove_block(proposed_block.clone())?;
1124
1125 self.apply_block(proven_block.clone()).context("failed to apply block")?;
1129
1130 Ok(proven_block)
1131 }
1132
1133 pub fn prove_block(&self, proposed_block: ProposedBlock) -> anyhow::Result<ProvenBlock> {
1135 let (header, body) = proposed_block.into_header_and_body()?;
1136 let block_proof = LocalBlockProver::default().prove_dummy();
1137 let signatures = self.sign_block(header.commitment());
1138 Ok(ProvenBlock::new_unchecked(header, body, signatures, block_proof))
1139 }
1140}
1141
1142impl Default for MockChain {
1143 fn default() -> Self {
1144 MockChain::new()
1145 }
1146}
1147
1148fn read_blocks_with_unchecked_genesis<R: ByteReader>(
1152 source: &mut R,
1153) -> Result<Vec<ProvenBlock>, DeserializationError> {
1154 let block_count = source.read_usize()?;
1155 if block_count == 0 {
1156 return Ok(Vec::new());
1157 }
1158
1159 let mut blocks = vec![read_genesis_block_unchecked(source)?];
1160 let remaining_blocks = source
1161 .read_many_iter(block_count - 1)?
1162 .collect::<Result<Vec<ProvenBlock>, _>>()?;
1163 blocks.extend(remaining_blocks);
1164
1165 Ok(blocks)
1166}
1167
1168fn read_genesis_block_unchecked<R: ByteReader>(
1169 source: &mut R,
1170) -> Result<ProvenBlock, DeserializationError> {
1171 let header = BlockHeader::read_from(source)?;
1172 if header.block_num() != BlockNumber::GENESIS {
1173 return Err(DeserializationError::InvalidValue(format!(
1174 "first mock chain block must be genesis, got block {}",
1175 header.block_num()
1176 )));
1177 }
1178
1179 let body = BlockBody::new_unchecked(
1180 Vec::<BlockAccountUpdate>::read_from(source)?,
1181 Vec::<OutputNoteBatch>::read_from(source)?,
1182 Vec::<Nullifier>::read_from(source)?,
1183 OrderedTransactionHeaders::read_from(source)?,
1184 );
1185 let signatures = BlockSignatures::read_from(source)?;
1186 let proof = ExecutionProof::read_from(source)?;
1187
1188 Ok(ProvenBlock::new_unchecked(header, body, signatures, proof))
1189}
1190
1191impl Serializable for MockChain {
1192 fn write_into<W: ByteWriter>(&self, target: &mut W) {
1193 self.chain.write_into(target);
1194 self.blocks.write_into(target);
1195 self.nullifier_tree.write_into(target);
1196 self.account_tree.write_into(target);
1197 self.pending_transactions.write_into(target);
1198 self.committed_accounts.write_into(target);
1199 self.committed_notes.write_into(target);
1200 self.account_authenticators.write_into(target);
1201 self.validator_secret_keys.write_into(target);
1202 self.protocol_config.write_into(target);
1203 }
1204}
1205
1206impl Deserializable for MockChain {
1207 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
1208 let chain = Blockchain::read_from(source)?;
1209 let blocks = read_blocks_with_unchecked_genesis(source)?;
1210 let nullifier_tree = NullifierTree::read_from(source)?;
1211 let account_tree = AccountTree::read_from(source)?;
1212 let pending_transactions = Vec::<ProvenTransaction>::read_from(source)?;
1213 let committed_accounts = BTreeMap::<AccountId, Account>::read_from(source)?;
1214 let committed_notes = BTreeMap::<NoteId, MockChainNote>::read_from(source)?;
1215 let account_authenticators =
1216 BTreeMap::<AccountId, AccountAuthenticator>::read_from(source)?;
1217 let secret_keys = Vec::<SigningKey>::read_from(source)?;
1218 let protocol_config = ProtocolConfig::read_from(source)?;
1219
1220 Ok(Self {
1221 chain,
1222 blocks,
1223 nullifier_tree,
1224 account_tree,
1225 pending_transactions,
1226 pending_batches: Vec::new(),
1227 committed_notes,
1228 committed_accounts,
1229 account_authenticators,
1230 validator_secret_keys: secret_keys,
1231 protocol_config,
1232 })
1233 }
1234}
1235
1236pub enum AccountState {
1242 New,
1243 Exists,
1244}
1245
1246#[derive(Debug, Clone)]
1251pub(super) struct AccountAuthenticator {
1252 authenticator: Option<BasicAuthenticator>,
1253}
1254
1255impl AccountAuthenticator {
1256 pub fn new(authenticator: Option<BasicAuthenticator>) -> Self {
1257 Self { authenticator }
1258 }
1259
1260 pub fn authenticator(&self) -> Option<&BasicAuthenticator> {
1261 self.authenticator.as_ref()
1262 }
1263}
1264
1265impl PartialEq for AccountAuthenticator {
1266 fn eq(&self, other: &Self) -> bool {
1267 match (&self.authenticator, &other.authenticator) {
1268 (Some(a), Some(b)) => {
1269 a.keys().keys().zip(b.keys().keys()).all(|(a_key, b_key)| a_key == b_key)
1270 },
1271 (None, None) => true,
1272 _ => false,
1273 }
1274 }
1275}
1276
1277impl Serializable for AccountAuthenticator {
1281 fn write_into<W: ByteWriter>(&self, target: &mut W) {
1282 self.authenticator
1283 .as_ref()
1284 .map(|auth| {
1285 auth.keys()
1286 .values()
1287 .map(|(secret_key, public_key)| (secret_key, public_key.as_ref().clone()))
1288 .collect::<Vec<_>>()
1289 })
1290 .write_into(target);
1291 }
1292}
1293
1294impl Deserializable for AccountAuthenticator {
1295 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
1296 let authenticator = Option::<Vec<(AuthSecretKey, PublicKey)>>::read_from(source)?;
1297
1298 let authenticator = authenticator.map(|keys| BasicAuthenticator::from_key_pairs(&keys));
1299
1300 Ok(Self { authenticator })
1301 }
1302}
1303
1304#[allow(clippy::large_enum_variant)]
1314#[derive(Debug, Clone)]
1315pub enum MockTransactionInput {
1316 AccountId(AccountId),
1317 Account(Account),
1318}
1319
1320impl MockTransactionInput {
1321 pub(crate) fn id(&self) -> AccountId {
1323 match self {
1324 MockTransactionInput::AccountId(account_id) => *account_id,
1325 MockTransactionInput::Account(account) => account.id(),
1326 }
1327 }
1328}
1329
1330impl From<AccountId> for MockTransactionInput {
1331 fn from(account: AccountId) -> Self {
1332 Self::AccountId(account)
1333 }
1334}
1335
1336impl From<Account> for MockTransactionInput {
1337 fn from(account: Account) -> Self {
1338 Self::Account(account)
1339 }
1340}
1341
1342#[cfg(test)]
1346mod tests {
1347 use miden_protocol::account::auth::AuthScheme;
1348 use miden_protocol::account::{AccountBuilder, AccountType};
1349 use miden_protocol::asset::{Asset, FungibleAsset};
1350 use miden_protocol::errors::ValidatorConfigError;
1351 use miden_protocol::note::NoteType;
1352 use miden_protocol::testing::account_id::{
1353 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
1354 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
1355 ACCOUNT_ID_SENDER,
1356 };
1357 use miden_protocol::testing::random_secret_key::random_secret_key;
1358 use miden_standards::account::wallets::BasicWallet;
1359 use miden_tx::utils::serde::SliceReader;
1360
1361 use super::*;
1362 use crate::Auth;
1363
1364 #[test]
1365 fn prove_until_block() -> anyhow::Result<()> {
1366 let mut chain = MockChain::new();
1367 let block = chain.prove_until_block(5)?;
1368 assert_eq!(block.header().block_num(), 5u32.into());
1369 assert_eq!(chain.proven_blocks().len(), 6);
1370
1371 Ok(())
1372 }
1373
1374 #[test]
1375 fn genesis_private_account_is_not_retained() -> anyhow::Result<()> {
1376 let account_builder = AccountBuilder::new([5; 32])
1377 .account_type(AccountType::Private)
1378 .with_component(BasicWallet);
1379 let mut builder = MockChain::builder();
1380 let account = builder.add_account_from_builder(
1381 Auth::BasicAuth { auth_scheme: AuthScheme::EcdsaK256Keccak },
1382 account_builder,
1383 AccountState::Exists,
1384 )?;
1385
1386 let chain = builder.build()?;
1387 let update = &chain.blocks[0].body().updated_accounts()[0];
1388
1389 assert!(update.details().is_private());
1390 assert!(chain.committed_account(account.id()).is_err());
1391
1392 Ok(())
1393 }
1394
1395 #[test]
1396 fn unchecked_genesis_deserialization_rejects_non_genesis_block() -> anyhow::Result<()> {
1397 let mut chain = MockChain::new();
1398 let block = chain.prove_next_block()?;
1399 let bytes = vec![block].to_bytes();
1400
1401 let error = read_blocks_with_unchecked_genesis(&mut SliceReader::new(&bytes)).unwrap_err();
1402
1403 assert!(matches!(
1404 error,
1405 DeserializationError::InvalidValue(message)
1406 if message == "first mock chain block must be genesis, got block 1"
1407 ));
1408
1409 Ok(())
1410 }
1411
1412 #[rstest::rstest]
1413 #[case::default(None)]
1414 #[case::supplied(Some(1))]
1415 fn validator_config_rotation_across_blocks(
1416 #[case] key_count: Option<usize>,
1417 ) -> anyhow::Result<()> {
1418 let mut builder = MockChain::builder();
1419 if let Some(key_count) = key_count {
1420 builder = builder
1421 .validator_signing_keys((0..key_count).map(|_| random_secret_key()).collect());
1422 }
1423 let mut chain = builder.build()?;
1424 let original_keys = chain.validator_config();
1425
1426 chain.prove_next_block()?;
1429 chain.prove_next_block()?;
1430 assert_eq!(chain.validator_config(), original_keys);
1431
1432 let new_signers: Vec<SigningKey> = (0..4).map(|_| random_secret_key()).collect();
1434 let new_keys = ValidatorConfig::from_signers(&new_signers);
1435 let rotation_block = chain.prove_next_block_with_validator_config_rotation(new_signers)?;
1436
1437 assert_eq!(rotation_block.header().validator_config(), &new_keys);
1440 assert_eq!(chain.validator_config(), new_keys);
1441 rotation_block
1442 .signatures()
1443 .verify_against(rotation_block.header().commitment(), &original_keys)?;
1444
1445 chain.prove_next_block()?;
1448 assert_eq!(chain.validator_config(), new_keys);
1449
1450 Ok(())
1451 }
1452
1453 #[rstest::rstest]
1454 #[case::single(1)]
1455 #[case::multiple(3)]
1456 #[case::maximum(ValidatorConfig::MAX_VALIDATORS)]
1457 fn supplied_validator_signing_keys(#[case] key_count: usize) -> anyhow::Result<()> {
1458 let mut keys: Vec<SigningKey> = (0..key_count).map(|_| random_secret_key()).collect();
1459 keys.sort_by_key(|key| core::cmp::Reverse(key.public_key().to_bytes()));
1461 let expected_config = ValidatorConfig::from_signers(&keys);
1462 let mut chain = MockChain::builder().validator_signing_keys(keys.clone()).build()?;
1463
1464 assert_eq!(chain.validator_config(), expected_config);
1465 assert_eq!(chain.genesis_block_header().validator_config(), &expected_config);
1466 assert_eq!(expected_config.len(), key_count);
1467 assert_eq!(usize::from(expected_config.quorum()), key_count);
1468
1469 let genesis = chain.latest_block();
1470 genesis
1471 .signatures()
1472 .verify_against(genesis.header().commitment(), &expected_config)?;
1473
1474 keys.reverse();
1476 let reordered = MockChain::builder().validator_signing_keys(keys).build()?;
1477 assert_eq!(chain.genesis_block_header(), reordered.genesis_block_header());
1478
1479 for _ in 0..2 {
1480 let block = chain.prove_next_block()?;
1481 assert_eq!(block.header().validator_config(), &expected_config);
1482 block
1483 .signatures()
1484 .verify_against(block.header().commitment(), &expected_config)?;
1485 }
1486
1487 Ok(())
1488 }
1489
1490 #[test]
1491 fn empty_validator_signing_keys_are_rejected() {
1492 let error = MockChain::builder().validator_signing_keys(Vec::new()).build().unwrap_err();
1493 assert!(matches!(
1494 error.downcast_ref::<ValidatorConfigError>(),
1495 Some(ValidatorConfigError::EmptySet)
1496 ));
1497 }
1498
1499 #[test]
1500 fn duplicate_validator_signing_keys_are_rejected() {
1501 let key = random_secret_key();
1502 let error = MockChain::builder()
1503 .validator_signing_keys(vec![key.clone(), key])
1504 .build()
1505 .unwrap_err();
1506 assert!(matches!(
1507 error.downcast_ref::<ValidatorConfigError>(),
1508 Some(ValidatorConfigError::DuplicateKey)
1509 ));
1510 }
1511
1512 #[test]
1513 fn too_many_validator_signing_keys_are_rejected() {
1514 let count = ValidatorConfig::MAX_VALIDATORS + 1;
1515 let keys = (0..count).map(|_| random_secret_key()).collect();
1516 let error = MockChain::builder().validator_signing_keys(keys).build().unwrap_err();
1517 assert!(matches!(
1518 error.downcast_ref::<ValidatorConfigError>(),
1519 Some(ValidatorConfigError::TooManyKeys { count: actual }) if *actual == count
1520 ));
1521 }
1522
1523 #[test]
1524 fn validator_signing_key_count_overflow_is_rejected() {
1525 let keys = vec![random_secret_key(); usize::from(u16::MAX) + 1];
1526 assert!(MockChain::builder().validator_signing_keys(keys).build().is_err());
1527 }
1528
1529 #[test]
1530 fn proposed_block_serialization_round_trip() -> anyhow::Result<()> {
1531 let chain = MockChain::new();
1532 let timestamp = chain.latest_block_header().timestamp() + 1;
1533 let next_keys = ValidatorConfig::from_signers(&[random_secret_key()]);
1534 let proposed = chain
1535 .propose_block_at(Vec::<ProvenBatch>::new(), timestamp)?
1536 .with_next_validator_config(next_keys.clone());
1537
1538 let bytes = proposed.to_bytes();
1539 let deserialized = ProposedBlock::read_from_bytes(&bytes).unwrap();
1540
1541 assert_eq!(deserialized.to_bytes(), bytes);
1544 assert_eq!(deserialized.next_validator_config(), &next_keys);
1545
1546 Ok(())
1547 }
1548
1549 #[tokio::test]
1550 async fn private_account_state_update() -> anyhow::Result<()> {
1551 let faucet_id = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into()?;
1552 let account_builder = AccountBuilder::new([4; 32])
1553 .account_type(AccountType::Private)
1554 .with_component(BasicWallet);
1555
1556 let mut builder = MockChain::builder();
1557 let auth_scheme = AuthScheme::EcdsaK256Keccak;
1558 let account = builder.add_account_from_builder(
1559 Auth::BasicAuth { auth_scheme },
1560 account_builder,
1561 AccountState::New,
1562 )?;
1563
1564 let account_id = account.id();
1565 assert_eq!(account.nonce().as_canonical_u64(), 0);
1566
1567 let note_1 = builder.add_p2id_note(
1568 ACCOUNT_ID_SENDER.try_into().unwrap(),
1569 account.id(),
1570 &[Asset::from(FungibleAsset::new(faucet_id, 1000u64).unwrap())],
1571 NoteType::Private,
1572 )?;
1573
1574 let mut mock_chain = builder.build()?;
1575 mock_chain.prove_next_block()?;
1576
1577 let tx = mock_chain
1578 .build_transaction(account)
1579 .unauthenticated_input_note(note_1)
1580 .build()?
1581 .execute()
1582 .await?;
1583
1584 mock_chain.add_pending_executed_transaction(&tx)?;
1585 mock_chain.prove_next_block()?;
1586
1587 assert!(tx.final_account().nonce().as_canonical_u64() > 0);
1588 assert_eq!(
1589 tx.final_account().to_commitment(),
1590 mock_chain.account_tree.open(account_id).state_commitment()
1591 );
1592
1593 Ok(())
1594 }
1595
1596 #[tokio::test]
1597 async fn mock_chain_serialization() {
1598 let mut builder = MockChain::builder();
1599
1600 let mut notes = vec![];
1601 for i in 0..10 {
1602 let account = builder
1603 .add_account_from_builder(
1604 Auth::BasicAuth {
1605 auth_scheme: AuthScheme::Falcon512Poseidon2,
1606 },
1607 AccountBuilder::new([i; 32]).with_component(BasicWallet),
1608 AccountState::New,
1609 )
1610 .unwrap();
1611 let note = builder
1612 .add_p2id_note(
1613 ACCOUNT_ID_SENDER.try_into().unwrap(),
1614 account.id(),
1615 &[Asset::from(
1616 FungibleAsset::new(
1617 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET.try_into().unwrap(),
1618 1000u64,
1619 )
1620 .unwrap(),
1621 )],
1622 NoteType::Private,
1623 )
1624 .unwrap();
1625 notes.push((account, note));
1626 }
1627
1628 let mut chain = builder.build().unwrap();
1629 for (account, note) in notes {
1630 let tx = chain
1631 .build_transaction(account)
1632 .unauthenticated_input_note(note)
1633 .build()
1634 .unwrap()
1635 .execute()
1636 .await
1637 .unwrap();
1638 chain.add_pending_executed_transaction(&tx).unwrap();
1639 chain.prove_next_block().unwrap();
1640 }
1641
1642 let bytes = chain.to_bytes();
1643
1644 let deserialized = MockChain::read_from_bytes(&bytes).unwrap();
1645
1646 assert_eq!(chain.chain.as_mmr().peaks(), deserialized.chain.as_mmr().peaks());
1647 assert_eq!(chain.blocks, deserialized.blocks);
1648 assert_eq!(chain.nullifier_tree, deserialized.nullifier_tree);
1649 assert_eq!(chain.account_tree, deserialized.account_tree);
1650 assert_eq!(chain.pending_transactions, deserialized.pending_transactions);
1651 assert_eq!(chain.committed_accounts, deserialized.committed_accounts);
1652 assert_eq!(chain.committed_notes, deserialized.committed_notes);
1653 assert_eq!(chain.account_authenticators, deserialized.account_authenticators);
1654 }
1655
1656 #[test]
1657 fn mock_chain_block_signatures() -> anyhow::Result<()> {
1658 let mut builder = MockChain::builder();
1659 builder.add_existing_mock_account(Auth::IncrNonce)?;
1660 let mut chain = builder.build()?;
1661
1662 let genesis_block = chain.latest_block();
1665 let genesis_validator_config = genesis_block.header().validator_config().clone();
1666 assert_eq!(genesis_validator_config.len(), 3);
1667 assert_eq!(genesis_validator_config.quorum(), 3);
1668 genesis_block
1669 .signatures()
1670 .verify_against(genesis_block.header().commitment(), &genesis_validator_config)
1671 .unwrap();
1672
1673 chain.prove_next_block()?;
1675
1676 let next_block = chain.latest_block();
1679 next_block
1680 .signatures()
1681 .verify_against(next_block.header().commitment(), &genesis_validator_config)
1682 .unwrap();
1683
1684 assert_eq!(next_block.header().validator_config(), &genesis_validator_config);
1687
1688 Ok(())
1689 }
1690
1691 #[tokio::test]
1692 async fn add_pending_batch() -> anyhow::Result<()> {
1693 let mut builder = MockChain::builder();
1694 let account = builder.add_existing_mock_account(Auth::IncrNonce)?;
1695 let mut chain = builder.build()?;
1696
1697 let tx = chain.build_transaction(account.id()).build()?.execute().await?;
1699 let proven_tx = LocalTransactionProver::default().prove_dummy(tx)?;
1700 let proposed_batch = chain.propose_transaction_batch(vec![proven_tx])?;
1701 let proven_batch = chain.prove_transaction_batch(proposed_batch)?;
1702
1703 let num_blocks_before = chain.proven_blocks().len();
1705 chain.add_pending_batch(proven_batch);
1706 chain.prove_next_block()?;
1707
1708 assert_eq!(chain.proven_blocks().len(), num_blocks_before + 1);
1709
1710 Ok(())
1711 }
1712}