1use alloc::collections::{BTreeMap, BTreeSet};
2use alloc::vec::Vec;
3
4use anyhow::Context;
5use miden_block_prover::LocalBlockProver;
6use miden_processor::serde::DeserializationError;
7use miden_protocol::account::auth::{AuthSecretKey, PublicKey};
8use miden_protocol::account::{Account, AccountId, AccountUpdateDetails, PartialAccount};
9use miden_protocol::batch::{ProposedBatch, ProvenBatch};
10use miden_protocol::block::account_tree::{AccountTree, AccountWitness};
11use miden_protocol::block::nullifier_tree::{NullifierTree, NullifierWitness};
12use miden_protocol::block::{
13 BlockHeader,
14 BlockInputs,
15 BlockNumber,
16 BlockSignatures,
17 Blockchain,
18 ProposedBlock,
19 ProvenBlock,
20 ValidatorKeys,
21};
22use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SigningKey;
23use miden_protocol::note::{Note, NoteHeader, NoteId, NoteInclusionProof, Nullifier};
24use miden_protocol::transaction::{
25 ExecutedTransaction,
26 InputNote,
27 InputNotes,
28 OutputNote,
29 PartialBlockchain,
30 ProvenTransaction,
31 TransactionInputs,
32};
33use miden_protocol::{MIN_PROOF_SECURITY_LEVEL, Word};
34use miden_tx::LocalTransactionProver;
35use miden_tx::auth::BasicAuthenticator;
36use miden_tx::utils::serde::{ByteReader, ByteWriter, Deserializable, Serializable};
37use miden_tx_batch::LocalBatchProver;
38
39use super::note::MockChainNote;
40use crate::{MockChainBuilder, MockTransactionBuilder};
41
42#[derive(Debug, Clone)]
178pub struct MockChain {
179 chain: Blockchain,
181
182 blocks: Vec<ProvenBlock>,
184
185 nullifier_tree: NullifierTree,
187
188 account_tree: AccountTree,
190
191 pending_transactions: Vec<ProvenTransaction>,
194
195 pending_batches: Vec<ProvenBatch>,
197
198 committed_notes: BTreeMap<NoteId, MockChainNote>,
200
201 committed_accounts: BTreeMap<AccountId, Account>,
208
209 account_authenticators: BTreeMap<AccountId, AccountAuthenticator>,
212
213 validator_secret_keys: Vec<SigningKey>,
215}
216
217impl MockChain {
218 pub const TIMESTAMP_START_SECS: u32 = 1700000000;
223
224 pub const TIMESTAMP_STEP_SECS: u32 = 10;
227
228 pub fn new() -> Self {
233 Self::builder().build().expect("empty chain should be valid")
234 }
235
236 pub fn builder() -> MockChainBuilder {
238 MockChainBuilder::new()
239 }
240
241 pub(super) fn from_genesis_block(
243 genesis_block: ProvenBlock,
244 account_tree: AccountTree,
245 account_authenticators: BTreeMap<AccountId, AccountAuthenticator>,
246 secret_keys: Vec<SigningKey>,
247 genesis_notes: Vec<Note>,
248 ) -> anyhow::Result<Self> {
249 let mut chain = MockChain {
250 chain: Blockchain::default(),
251 blocks: vec![],
252 nullifier_tree: NullifierTree::default(),
253 account_tree,
254 pending_transactions: Vec::new(),
255 pending_batches: Vec::new(),
256 committed_notes: BTreeMap::new(),
257 committed_accounts: BTreeMap::new(),
258 account_authenticators,
259 validator_secret_keys: secret_keys,
260 };
261
262 chain
265 .apply_block(genesis_block)
266 .context("failed to build account from builder")?;
267
268 for note in genesis_notes {
272 if let Some(MockChainNote::Private(_, _, _, inclusion_proof)) =
273 chain.committed_notes.get(¬e.id())
274 {
275 chain.committed_notes.insert(
276 note.id(),
277 MockChainNote::Public(note.clone(), inclusion_proof.clone()),
278 );
279 }
280 }
281
282 debug_assert_eq!(chain.blocks.len(), 1);
283 debug_assert_eq!(chain.committed_accounts.len(), chain.account_tree.num_accounts());
284
285 Ok(chain)
286 }
287
288 pub fn blockchain(&self) -> &Blockchain {
293 &self.chain
294 }
295
296 pub fn latest_partial_blockchain(&self) -> PartialBlockchain {
299 let block_headers =
302 self.blocks.iter().map(|b| b.header()).take(self.blocks.len() - 1).cloned();
303
304 PartialBlockchain::from_blockchain(&self.chain, block_headers)
305 .expect("blockchain should be valid by construction")
306 }
307
308 pub fn latest_selective_partial_blockchain(
314 &self,
315 reference_blocks: impl IntoIterator<Item = BlockNumber>,
316 ) -> anyhow::Result<(BlockHeader, PartialBlockchain)> {
317 let latest_block_header = self.latest_block_header();
318
319 self.selective_partial_blockchain(latest_block_header.block_num(), reference_blocks)
320 }
321
322 pub fn selective_partial_blockchain(
328 &self,
329 reference_block: BlockNumber,
330 reference_blocks: impl IntoIterator<Item = BlockNumber>,
331 ) -> anyhow::Result<(BlockHeader, PartialBlockchain)> {
332 let reference_block_header = self.block_header(reference_block.as_usize());
333 let reference_blocks: BTreeSet<_> = reference_blocks.into_iter().collect();
336
337 let mut block_headers = Vec::new();
339
340 for block_ref_num in &reference_blocks {
341 let block_index = block_ref_num.as_usize();
342 let block = self
343 .blocks
344 .get(block_index)
345 .ok_or_else(|| anyhow::anyhow!("block {} not found in chain", block_ref_num))?;
346 let block_header = block.header().clone();
347 if block_header.commitment() != reference_block_header.commitment() {
349 block_headers.push(block_header);
350 }
351 }
352
353 let partial_blockchain =
354 PartialBlockchain::from_blockchain_at(&self.chain, reference_block, block_headers)?;
355
356 Ok((reference_block_header, partial_blockchain))
357 }
358
359 pub fn account_witnesses(
362 &self,
363 account_ids: impl IntoIterator<Item = AccountId>,
364 ) -> BTreeMap<AccountId, AccountWitness> {
365 let mut account_witnesses = BTreeMap::new();
366
367 for account_id in account_ids {
368 let witness = self.account_tree.open(account_id);
369 account_witnesses.insert(account_id, witness);
370 }
371
372 account_witnesses
373 }
374
375 pub fn nullifier_witnesses(
378 &self,
379 nullifiers: impl IntoIterator<Item = Nullifier>,
380 ) -> BTreeMap<Nullifier, NullifierWitness> {
381 let mut nullifier_proofs = BTreeMap::new();
382
383 for nullifier in nullifiers {
384 let witness = self.nullifier_tree.open(&nullifier);
385 nullifier_proofs.insert(nullifier, witness);
386 }
387
388 nullifier_proofs
389 }
390
391 pub fn unauthenticated_note_proofs(
395 &self,
396 notes: impl IntoIterator<Item = NoteId>,
397 ) -> BTreeMap<NoteId, NoteInclusionProof> {
398 let mut proofs = BTreeMap::default();
399 for note in notes {
400 if let Some(input_note) = self.committed_notes.get(¬e) {
401 proofs.insert(note, input_note.inclusion_proof().clone());
402 }
403 }
404
405 proofs
406 }
407
408 pub fn genesis_block_header(&self) -> BlockHeader {
410 self.block_header(BlockNumber::GENESIS.as_usize())
411 }
412
413 pub fn latest_block_header(&self) -> BlockHeader {
415 let chain_tip =
416 self.chain.chain_tip().expect("chain should contain at least the genesis block");
417 self.blocks[chain_tip.as_usize()].header().clone()
418 }
419
420 pub fn validator_keys(&self) -> ValidatorKeys {
422 ValidatorKeys::new(self.validator_secret_keys.iter().map(|sk| sk.public_key()).collect())
423 .expect("the mock chain holds distinct validator keys")
424 }
425
426 fn sign_block(&self, commitment: Word) -> BlockSignatures {
429 let signatures = self
430 .validator_keys()
431 .as_keys()
432 .iter()
433 .map(|key| {
434 let signer = self
435 .validator_secret_keys
436 .iter()
437 .find(|sk| &sk.public_key() == key)
438 .expect("a signer should exist for every validator key");
439 signer.sign(commitment)
440 })
441 .collect();
442 BlockSignatures::new(signatures).expect("signature count same as validator key count")
443 }
444
445 pub fn latest_block(&self) -> ProvenBlock {
447 let chain_tip =
448 self.chain.chain_tip().expect("chain should contain at least the genesis block");
449 self.blocks[chain_tip.as_usize()].clone()
450 }
451
452 pub fn block_header(&self, block_number: usize) -> BlockHeader {
458 self.blocks[block_number].header().clone()
459 }
460
461 pub fn proven_blocks(&self) -> &[ProvenBlock] {
463 &self.blocks
464 }
465
466 pub fn fee_faucet_id(&self) -> AccountId {
472 self.genesis_block_header().fee_parameters().fee_faucet_id()
473 }
474
475 pub fn nullifier_tree(&self) -> &NullifierTree {
477 &self.nullifier_tree
478 }
479
480 pub fn committed_notes(&self) -> &BTreeMap<NoteId, MockChainNote> {
484 &self.committed_notes
485 }
486
487 pub fn is_note_committed(&self, note_id: &NoteId) -> bool {
489 self.committed_notes.contains_key(note_id)
490 }
491
492 pub fn is_note_consumed(&self, nullifier: &Nullifier) -> bool {
494 self.nullifier_tree.get_block_num(nullifier).is_some()
495 }
496
497 pub fn is_note_unspent(&self, nullifier: &Nullifier) -> bool {
502 !self.is_note_consumed(nullifier)
503 }
504
505 pub fn get_public_note(&self, note_id: &NoteId) -> Option<InputNote> {
508 let note = self.committed_notes.get(note_id)?;
509 note.clone().try_into().ok()
510 }
511
512 pub fn committed_account(&self, account_id: AccountId) -> anyhow::Result<&Account> {
516 self.committed_accounts
517 .get(&account_id)
518 .with_context(|| format!("account {account_id} not found in committed accounts"))
519 }
520
521 pub fn account_tree(&self) -> &AccountTree {
523 &self.account_tree
524 }
525
526 pub fn propose_transaction_batch<I>(
533 &self,
534 txs: impl IntoIterator<Item = ProvenTransaction, IntoIter = I>,
535 ) -> anyhow::Result<ProposedBatch>
536 where
537 I: Iterator<Item = ProvenTransaction> + Clone,
538 {
539 let transactions: Vec<_> = txs.into_iter().map(alloc::sync::Arc::new).collect();
540
541 let (batch_reference_block, partial_blockchain, unauthenticated_note_proofs) = self
542 .get_batch_inputs(
543 transactions.iter().map(|tx| tx.ref_block_num()),
544 transactions
545 .iter()
546 .flat_map(|tx| tx.unauthenticated_notes().map(NoteHeader::id)),
547 )?;
548
549 Ok(ProposedBatch::new_unverified(
550 transactions,
551 batch_reference_block,
552 partial_blockchain,
553 unauthenticated_note_proofs,
554 )?)
555 }
556
557 pub fn prove_transaction_batch(
561 &self,
562 proposed_batch: ProposedBatch,
563 ) -> anyhow::Result<ProvenBatch> {
564 let batch_prover = LocalBatchProver::new();
565 Ok(batch_prover.prove_dummy(proposed_batch)?)
566 }
567
568 pub fn propose_block_at<I>(
575 &self,
576 batches: impl IntoIterator<Item = ProvenBatch, IntoIter = I>,
577 timestamp: u32,
578 ) -> anyhow::Result<ProposedBlock>
579 where
580 I: Iterator<Item = ProvenBatch> + Clone,
581 {
582 let batches: Vec<_> = batches.into_iter().collect();
583
584 let block_inputs = self
585 .get_block_inputs(batches.iter())
586 .context("could not retrieve block inputs")?;
587
588 let proposed_block = ProposedBlock::new_at(block_inputs, batches, timestamp)
589 .context("failed to create proposed block")?;
590
591 Ok(proposed_block)
592 }
593
594 pub fn propose_block<I>(
598 &self,
599 batches: impl IntoIterator<Item = ProvenBatch, IntoIter = I>,
600 ) -> anyhow::Result<ProposedBlock>
601 where
602 I: Iterator<Item = ProvenBatch> + Clone,
603 {
604 let timestamp = self.latest_block_header().timestamp() + 1;
607
608 self.propose_block_at(batches, timestamp)
609 }
610
611 pub fn build_transaction(
632 &self,
633 input: impl Into<MockTransactionInput>,
634 ) -> MockTransactionBuilder<'_> {
635 MockTransactionBuilder::new(self, input)
636 }
637
638 pub(crate) fn resolve_tx_account(
643 &self,
644 input: MockTransactionInput,
645 ) -> anyhow::Result<Account> {
646 match input {
647 MockTransactionInput::AccountId(account_id) => {
648 anyhow::ensure!(
649 !account_id.is_private(),
650 "mock transactions for private accounts should be created with MockTransactionInput::Account"
651 );
652
653 self.committed_account(account_id).cloned()
654 },
655 MockTransactionInput::Account(account) => Ok(account),
656 }
657 }
658
659 pub(crate) fn account_authenticator(
661 &self,
662 account_id: AccountId,
663 ) -> Option<BasicAuthenticator> {
664 self.account_authenticators
665 .get(&account_id)
666 .and_then(|authenticator| authenticator.authenticator().cloned())
667 }
668
669 pub fn get_transaction_inputs_at(
675 &self,
676 reference_block: BlockNumber,
677 account: impl Into<PartialAccount>,
678 notes: &[NoteId],
679 unauthenticated_notes: &[Note],
680 ) -> anyhow::Result<TransactionInputs> {
681 let ref_block = self.block_header(reference_block.as_usize());
682
683 let mut input_notes = vec![];
684 let mut block_headers_map: BTreeMap<BlockNumber, BlockHeader> = BTreeMap::new();
685 for note in notes {
686 let input_note: InputNote = self
687 .committed_notes
688 .get(note)
689 .with_context(|| format!("note with id {note} not found"))?
690 .clone()
691 .try_into()
692 .with_context(|| {
693 format!("failed to convert mock chain note with id {note} into input note")
694 })?;
695
696 let note_block_num = input_note
697 .location()
698 .with_context(|| format!("note location not available: {note}"))?
699 .block_num();
700
701 if note_block_num > ref_block.block_num() {
702 anyhow::bail!(
703 "note with ID {note} was created in block {note_block_num} which is larger than the reference block number {}",
704 ref_block.block_num()
705 )
706 }
707
708 if note_block_num != ref_block.block_num() {
709 let block_header = self
710 .blocks
711 .get(note_block_num.as_usize())
712 .with_context(|| format!("block {note_block_num} not found in chain"))?
713 .header()
714 .clone();
715 block_headers_map.insert(note_block_num, block_header);
716 }
717
718 input_notes.push(input_note);
719 }
720
721 for note in unauthenticated_notes {
722 input_notes.push(InputNote::Unauthenticated { note: note.clone() })
723 }
724
725 let block_headers = block_headers_map.values();
726 let (_, partial_blockchain) = self.selective_partial_blockchain(
727 reference_block,
728 block_headers.map(BlockHeader::block_num),
729 )?;
730
731 let input_notes = InputNotes::new(input_notes)?;
732
733 Ok(TransactionInputs::new(
734 account.into(),
735 ref_block.clone(),
736 partial_blockchain,
737 input_notes,
738 )?)
739 }
740
741 pub fn get_transaction_inputs(
743 &self,
744 account: impl Into<PartialAccount>,
745 notes: &[NoteId],
746 unauthenticated_notes: &[Note],
747 ) -> anyhow::Result<TransactionInputs> {
748 let latest_block_num = self.latest_block_header().block_num();
749 self.get_transaction_inputs_at(latest_block_num, account, notes, unauthenticated_notes)
750 }
751
752 pub fn get_batch_inputs(
755 &self,
756 tx_reference_blocks: impl IntoIterator<Item = BlockNumber>,
757 unauthenticated_notes: impl Iterator<Item = NoteId>,
758 ) -> anyhow::Result<(BlockHeader, PartialBlockchain, BTreeMap<NoteId, NoteInclusionProof>)>
759 {
760 let unauthenticated_note_proofs = self.unauthenticated_note_proofs(unauthenticated_notes);
762
763 let required_blocks = tx_reference_blocks.into_iter().chain(
766 unauthenticated_note_proofs
767 .values()
768 .map(|note_proof| note_proof.location().block_num()),
769 );
770
771 let (batch_reference_block, partial_block_chain) =
772 self.latest_selective_partial_blockchain(required_blocks)?;
773
774 Ok((batch_reference_block, partial_block_chain, unauthenticated_note_proofs))
775 }
776
777 pub fn get_foreign_account_inputs(
781 &self,
782 account_id: AccountId,
783 ) -> anyhow::Result<(Account, AccountWitness)> {
784 let account = self.committed_account(account_id)?.clone();
785
786 let account_witness = self.account_tree().open(account_id);
787 assert_eq!(account_witness.state_commitment(), account.to_commitment());
788
789 Ok((account, account_witness))
790 }
791
792 pub fn get_block_inputs<'batch, I>(
794 &self,
795 batch_iter: impl IntoIterator<Item = &'batch ProvenBatch, IntoIter = I>,
796 ) -> anyhow::Result<BlockInputs>
797 where
798 I: Iterator<Item = &'batch ProvenBatch> + Clone,
799 {
800 let batch_iterator = batch_iter.into_iter();
801
802 let unauthenticated_note_proofs =
803 self.unauthenticated_note_proofs(batch_iterator.clone().flat_map(|batch| {
804 batch.input_notes().iter().filter_map(|note| note.header().map(NoteHeader::id))
805 }));
806
807 let (block_reference_block, partial_blockchain) = self
808 .latest_selective_partial_blockchain(
809 batch_iterator.clone().map(ProvenBatch::reference_block_num).chain(
810 unauthenticated_note_proofs.values().map(|proof| proof.location().block_num()),
811 ),
812 )?;
813
814 let account_witnesses =
815 self.account_witnesses(batch_iterator.clone().flat_map(ProvenBatch::updated_accounts));
816
817 let nullifier_proofs =
818 self.nullifier_witnesses(batch_iterator.flat_map(ProvenBatch::created_nullifiers));
819
820 Ok(BlockInputs::new(
821 block_reference_block,
822 partial_blockchain,
823 account_witnesses,
824 nullifier_proofs,
825 unauthenticated_note_proofs,
826 ))
827 }
828
829 pub fn prove_next_block(&mut self) -> anyhow::Result<ProvenBlock> {
836 self.prove_and_apply_block(None, None)
837 }
838
839 pub fn prove_next_block_with_validator_keys_rotation(
848 &mut self,
849 new_validator_keys: Vec<SigningKey>,
850 ) -> anyhow::Result<ProvenBlock> {
851 let next_keys =
852 ValidatorKeys::new(new_validator_keys.iter().map(|sk| sk.public_key()).collect())
853 .context("invalid rotated validator key set")?;
854 let block = self.prove_and_apply_block(None, Some(next_keys))?;
855 self.validator_secret_keys = new_validator_keys;
856 Ok(block)
857 }
858
859 pub fn prove_next_block_at(&mut self, timestamp: u32) -> anyhow::Result<ProvenBlock> {
863 self.prove_and_apply_block(Some(timestamp), None)
864 }
865
866 pub fn prove_until_block(
876 &mut self,
877 target_block_num: impl Into<BlockNumber>,
878 ) -> anyhow::Result<ProvenBlock> {
879 let target_block_num = target_block_num.into();
880 let latest_block_num = self.latest_block_header().block_num();
881 assert!(
882 target_block_num > latest_block_num,
883 "target block number must be greater than the number of the latest block in the chain"
884 );
885
886 let mut last_block = None;
887 for _ in latest_block_num.as_usize()..target_block_num.as_usize() {
888 last_block = Some(self.prove_next_block()?);
889 }
890
891 Ok(last_block.expect("at least one block should have been created"))
892 }
893
894 pub fn add_pending_executed_transaction(
902 &mut self,
903 transaction: &ExecutedTransaction,
904 ) -> anyhow::Result<()> {
905 let proven_tx = LocalTransactionProver::default()
907 .prove_dummy(transaction.clone())
908 .context("failed to dummy-prove executed transaction into proven transaction")?;
909
910 self.pending_transactions.push(proven_tx);
911
912 Ok(())
913 }
914
915 pub fn add_pending_proven_transaction(&mut self, transaction: ProvenTransaction) {
920 self.pending_transactions.push(transaction);
921 }
922
923 pub fn add_pending_batch(&mut self, batch: ProvenBatch) {
928 self.pending_batches.push(batch);
929 }
930
931 fn apply_block(&mut self, proven_block: ProvenBlock) -> anyhow::Result<()> {
942 if proven_block.header().block_num() != BlockNumber::GENESIS {
945 let parent = self.latest_block_header();
946 proven_block
947 .validate(Some(&parent))
948 .context("block failed validation against its parent")?;
949 }
950
951 for account_update in proven_block.body().updated_accounts() {
952 self.account_tree
953 .insert(account_update.account_id(), account_update.final_state_commitment())
954 .context("failed to insert account update into account tree")?;
955 }
956
957 for nullifier in proven_block.body().created_nullifiers() {
958 self.nullifier_tree
959 .mark_spent(*nullifier, proven_block.header().block_num())
960 .context("failed to mark block nullifier as spent")?;
961
962 }
966
967 for account_update in proven_block.body().updated_accounts() {
968 match account_update.details() {
969 AccountUpdateDetails::Public(account_patch) => {
970 if account_patch.is_full_state() {
971 let account = Account::try_from(account_patch)
972 .context("failed to convert full state patch into full account")?;
973 self.committed_accounts.insert(account.id(), account.clone());
974 } else {
975 let committed_account = self
976 .committed_accounts
977 .get_mut(&account_update.account_id())
978 .ok_or_else(|| {
979 anyhow::anyhow!("account patch in block for non-existent account")
980 })?;
981 committed_account
982 .apply_patch(account_patch)
983 .context("failed to apply account patch")?;
984 }
985 },
986 AccountUpdateDetails::Private => {},
989 }
990 }
991
992 let notes_tree = proven_block.body().compute_block_note_tree();
993 for (block_note_index, created_note) in proven_block.body().output_notes() {
994 let note_path = notes_tree.open(block_note_index);
995 let note_inclusion_proof = NoteInclusionProof::new(
996 proven_block.header().block_num(),
997 block_note_index.leaf_index_value(),
998 note_path,
999 )
1000 .context("failed to create inclusion proof for output note")?;
1001
1002 match created_note {
1003 OutputNote::Public(public_note) => {
1004 self.committed_notes.insert(
1005 public_note.id(),
1006 MockChainNote::Public(public_note.as_note().clone(), note_inclusion_proof),
1007 );
1008 },
1009 OutputNote::Private(private_note) => {
1010 self.committed_notes.insert(
1011 private_note.id(),
1012 MockChainNote::Private(
1013 private_note.id(),
1014 *private_note.metadata(),
1015 private_note.attachments().clone(),
1016 note_inclusion_proof,
1017 ),
1018 );
1019 },
1020 }
1021 }
1022
1023 debug_assert_eq!(
1024 self.chain.commitment(),
1025 proven_block.header().chain_commitment(),
1026 "current mock chain commitment and new block's chain commitment should match"
1027 );
1028 debug_assert_eq!(
1029 BlockNumber::from(self.chain.as_mmr().forest().num_leaves() as u32),
1030 proven_block.header().block_num(),
1031 "current mock chain length and new block's number should match"
1032 );
1033
1034 self.chain.push(proven_block.header().commitment());
1035 self.blocks.push(proven_block);
1036
1037 Ok(())
1038 }
1039
1040 fn pending_transactions_to_batches(&mut self) -> anyhow::Result<Vec<ProvenBatch>> {
1041 if self.pending_transactions.is_empty() {
1044 return Ok(vec![]);
1045 }
1046
1047 let pending_transactions = core::mem::take(&mut self.pending_transactions);
1048
1049 let proposed_batch = self.propose_transaction_batch(pending_transactions)?;
1052 let proven_batch = self.prove_transaction_batch(proposed_batch)?;
1053
1054 Ok(vec![proven_batch])
1055 }
1056
1057 fn prove_and_apply_block(
1067 &mut self,
1068 timestamp: Option<u32>,
1069 next_validator_keys: Option<ValidatorKeys>,
1070 ) -> anyhow::Result<ProvenBlock> {
1071 let mut batches = self.pending_transactions_to_batches()?;
1075 batches.extend(core::mem::take(&mut self.pending_batches));
1076
1077 let block_timestamp =
1081 timestamp.unwrap_or(self.latest_block_header().timestamp() + Self::TIMESTAMP_STEP_SECS);
1082
1083 let mut proposed_block = self
1084 .propose_block_at(batches.clone(), block_timestamp)
1085 .context("failed to create proposed block")?;
1086
1087 if let Some(next_validator_keys) = next_validator_keys {
1089 proposed_block = proposed_block.with_next_validator_keys(next_validator_keys);
1090 }
1091
1092 let proven_block = self.prove_block(proposed_block.clone())?;
1093
1094 self.apply_block(proven_block.clone()).context("failed to apply block")?;
1098
1099 Ok(proven_block)
1100 }
1101
1102 pub fn prove_block(&self, proposed_block: ProposedBlock) -> anyhow::Result<ProvenBlock> {
1104 let (header, body) = proposed_block.clone().into_header_and_body()?;
1105 let inputs = self.get_block_inputs(proposed_block.batches().as_slice())?;
1106 let block_proof = LocalBlockProver::new(MIN_PROOF_SECURITY_LEVEL).prove_dummy(
1107 proposed_block.batches().clone(),
1108 header.clone(),
1109 inputs,
1110 )?;
1111 let signatures = self.sign_block(header.commitment());
1112 Ok(ProvenBlock::new_unchecked(header, body, signatures, block_proof))
1113 }
1114}
1115
1116impl Default for MockChain {
1117 fn default() -> Self {
1118 MockChain::new()
1119 }
1120}
1121
1122impl Serializable for MockChain {
1126 fn write_into<W: ByteWriter>(&self, target: &mut W) {
1127 self.chain.write_into(target);
1128 self.blocks.write_into(target);
1129 self.nullifier_tree.write_into(target);
1130 self.account_tree.write_into(target);
1131 self.pending_transactions.write_into(target);
1132 self.committed_accounts.write_into(target);
1133 self.committed_notes.write_into(target);
1134 self.account_authenticators.write_into(target);
1135 self.validator_secret_keys.write_into(target);
1136 }
1137}
1138
1139impl Deserializable for MockChain {
1140 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
1141 let chain = Blockchain::read_from(source)?;
1142 let blocks = Vec::<ProvenBlock>::read_from(source)?;
1143 let nullifier_tree = NullifierTree::read_from(source)?;
1144 let account_tree = AccountTree::read_from(source)?;
1145 let pending_transactions = Vec::<ProvenTransaction>::read_from(source)?;
1146 let committed_accounts = BTreeMap::<AccountId, Account>::read_from(source)?;
1147 let committed_notes = BTreeMap::<NoteId, MockChainNote>::read_from(source)?;
1148 let account_authenticators =
1149 BTreeMap::<AccountId, AccountAuthenticator>::read_from(source)?;
1150 let secret_keys = Vec::<SigningKey>::read_from(source)?;
1151
1152 Ok(Self {
1153 chain,
1154 blocks,
1155 nullifier_tree,
1156 account_tree,
1157 pending_transactions,
1158 pending_batches: Vec::new(),
1159 committed_notes,
1160 committed_accounts,
1161 account_authenticators,
1162 validator_secret_keys: secret_keys,
1163 })
1164 }
1165}
1166
1167pub enum AccountState {
1173 New,
1174 Exists,
1175}
1176
1177#[derive(Debug, Clone)]
1182pub(super) struct AccountAuthenticator {
1183 authenticator: Option<BasicAuthenticator>,
1184}
1185
1186impl AccountAuthenticator {
1187 pub fn new(authenticator: Option<BasicAuthenticator>) -> Self {
1188 Self { authenticator }
1189 }
1190
1191 pub fn authenticator(&self) -> Option<&BasicAuthenticator> {
1192 self.authenticator.as_ref()
1193 }
1194}
1195
1196impl PartialEq for AccountAuthenticator {
1197 fn eq(&self, other: &Self) -> bool {
1198 match (&self.authenticator, &other.authenticator) {
1199 (Some(a), Some(b)) => {
1200 a.keys().keys().zip(b.keys().keys()).all(|(a_key, b_key)| a_key == b_key)
1201 },
1202 (None, None) => true,
1203 _ => false,
1204 }
1205 }
1206}
1207
1208impl Serializable for AccountAuthenticator {
1212 fn write_into<W: ByteWriter>(&self, target: &mut W) {
1213 self.authenticator
1214 .as_ref()
1215 .map(|auth| {
1216 auth.keys()
1217 .values()
1218 .map(|(secret_key, public_key)| (secret_key, public_key.as_ref().clone()))
1219 .collect::<Vec<_>>()
1220 })
1221 .write_into(target);
1222 }
1223}
1224
1225impl Deserializable for AccountAuthenticator {
1226 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
1227 let authenticator = Option::<Vec<(AuthSecretKey, PublicKey)>>::read_from(source)?;
1228
1229 let authenticator = authenticator.map(|keys| BasicAuthenticator::from_key_pairs(&keys));
1230
1231 Ok(Self { authenticator })
1232 }
1233}
1234
1235#[allow(clippy::large_enum_variant)]
1241#[derive(Debug, Clone)]
1242pub enum MockTransactionInput {
1243 AccountId(AccountId),
1244 Account(Account),
1245}
1246
1247impl MockTransactionInput {
1248 pub(crate) fn id(&self) -> AccountId {
1250 match self {
1251 MockTransactionInput::AccountId(account_id) => *account_id,
1252 MockTransactionInput::Account(account) => account.id(),
1253 }
1254 }
1255}
1256
1257impl From<AccountId> for MockTransactionInput {
1258 fn from(account: AccountId) -> Self {
1259 Self::AccountId(account)
1260 }
1261}
1262
1263impl From<Account> for MockTransactionInput {
1264 fn from(account: Account) -> Self {
1265 Self::Account(account)
1266 }
1267}
1268
1269#[cfg(test)]
1273mod tests {
1274 use miden_protocol::account::auth::AuthScheme;
1275 use miden_protocol::account::{AccountBuilder, AccountType};
1276 use miden_protocol::asset::{Asset, FungibleAsset};
1277 use miden_protocol::note::NoteType;
1278 use miden_protocol::testing::account_id::{
1279 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
1280 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
1281 ACCOUNT_ID_SENDER,
1282 };
1283 use miden_protocol::testing::random_secret_key::random_secret_key;
1284 use miden_standards::account::wallets::BasicWallet;
1285
1286 use super::*;
1287 use crate::Auth;
1288
1289 #[test]
1290 fn prove_until_block() -> anyhow::Result<()> {
1291 let mut chain = MockChain::new();
1292 let block = chain.prove_until_block(5)?;
1293 assert_eq!(block.header().block_num(), 5u32.into());
1294 assert_eq!(chain.proven_blocks().len(), 6);
1295
1296 Ok(())
1297 }
1298
1299 #[test]
1300 fn validator_keys_rotation_across_blocks() -> anyhow::Result<()> {
1301 let mut chain = MockChain::new();
1302 let original_keys = chain.validator_keys();
1303
1304 chain.prove_next_block()?;
1307 chain.prove_next_block()?;
1308 assert_eq!(chain.validator_keys(), original_keys);
1309
1310 let new_signers: Vec<SigningKey> = (0..4).map(|_| random_secret_key()).collect();
1312 let new_keys =
1313 ValidatorKeys::new(new_signers.iter().map(|sk| sk.public_key()).collect()).unwrap();
1314 let rotation_block = chain.prove_next_block_with_validator_keys_rotation(new_signers)?;
1315
1316 assert_eq!(rotation_block.header().validator_keys(), &new_keys);
1319 assert_eq!(chain.validator_keys(), new_keys);
1320
1321 chain.prove_next_block()?;
1324 assert_eq!(chain.validator_keys(), new_keys);
1325
1326 Ok(())
1327 }
1328
1329 #[test]
1330 fn proposed_block_serialization_round_trip() -> anyhow::Result<()> {
1331 let chain = MockChain::new();
1332 let timestamp = chain.latest_block_header().timestamp() + 1;
1333 let next_keys = ValidatorKeys::new(alloc::vec![random_secret_key().public_key()]).unwrap();
1334 let proposed = chain
1335 .propose_block_at(Vec::<ProvenBatch>::new(), timestamp)?
1336 .with_next_validator_keys(next_keys.clone());
1337
1338 let bytes = proposed.to_bytes();
1339 let deserialized = ProposedBlock::read_from_bytes(&bytes).unwrap();
1340
1341 assert_eq!(deserialized.to_bytes(), bytes);
1344 assert_eq!(deserialized.next_validator_keys(), &next_keys);
1345
1346 Ok(())
1347 }
1348
1349 #[tokio::test]
1350 async fn private_account_state_update() -> anyhow::Result<()> {
1351 let faucet_id = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into()?;
1352 let account_builder = AccountBuilder::new([4; 32])
1353 .account_type(AccountType::Private)
1354 .with_component(BasicWallet);
1355
1356 let mut builder = MockChain::builder();
1357 let auth_scheme = AuthScheme::EcdsaK256Keccak;
1358 let account = builder.add_account_from_builder(
1359 Auth::BasicAuth { auth_scheme },
1360 account_builder,
1361 AccountState::New,
1362 )?;
1363
1364 let account_id = account.id();
1365 assert_eq!(account.nonce().as_canonical_u64(), 0);
1366
1367 let note_1 = builder.add_p2id_note(
1368 ACCOUNT_ID_SENDER.try_into().unwrap(),
1369 account.id(),
1370 &[Asset::Fungible(FungibleAsset::new(faucet_id, 1000u64).unwrap())],
1371 NoteType::Private,
1372 )?;
1373
1374 let mut mock_chain = builder.build()?;
1375 mock_chain.prove_next_block()?;
1376
1377 let tx = mock_chain
1378 .build_transaction(account)
1379 .unauthenticated_input_note(note_1)
1380 .build()?
1381 .execute()
1382 .await?;
1383
1384 mock_chain.add_pending_executed_transaction(&tx)?;
1385 mock_chain.prove_next_block()?;
1386
1387 assert!(tx.final_account().nonce().as_canonical_u64() > 0);
1388 assert_eq!(
1389 tx.final_account().to_commitment(),
1390 mock_chain.account_tree.open(account_id).state_commitment()
1391 );
1392
1393 Ok(())
1394 }
1395
1396 #[tokio::test]
1397 async fn mock_chain_serialization() {
1398 let mut builder = MockChain::builder();
1399
1400 let mut notes = vec![];
1401 for i in 0..10 {
1402 let account = builder
1403 .add_account_from_builder(
1404 Auth::BasicAuth {
1405 auth_scheme: AuthScheme::Falcon512Poseidon2,
1406 },
1407 AccountBuilder::new([i; 32]).with_component(BasicWallet),
1408 AccountState::New,
1409 )
1410 .unwrap();
1411 let note = builder
1412 .add_p2id_note(
1413 ACCOUNT_ID_SENDER.try_into().unwrap(),
1414 account.id(),
1415 &[Asset::Fungible(
1416 FungibleAsset::new(
1417 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET.try_into().unwrap(),
1418 1000u64,
1419 )
1420 .unwrap(),
1421 )],
1422 NoteType::Private,
1423 )
1424 .unwrap();
1425 notes.push((account, note));
1426 }
1427
1428 let mut chain = builder.build().unwrap();
1429 for (account, note) in notes {
1430 let tx = chain
1431 .build_transaction(account)
1432 .unauthenticated_input_note(note)
1433 .build()
1434 .unwrap()
1435 .execute()
1436 .await
1437 .unwrap();
1438 chain.add_pending_executed_transaction(&tx).unwrap();
1439 chain.prove_next_block().unwrap();
1440 }
1441
1442 let bytes = chain.to_bytes();
1443
1444 let deserialized = MockChain::read_from_bytes(&bytes).unwrap();
1445
1446 assert_eq!(chain.chain.as_mmr().peaks(), deserialized.chain.as_mmr().peaks());
1447 assert_eq!(chain.blocks, deserialized.blocks);
1448 assert_eq!(chain.nullifier_tree, deserialized.nullifier_tree);
1449 assert_eq!(chain.account_tree, deserialized.account_tree);
1450 assert_eq!(chain.pending_transactions, deserialized.pending_transactions);
1451 assert_eq!(chain.committed_accounts, deserialized.committed_accounts);
1452 assert_eq!(chain.committed_notes, deserialized.committed_notes);
1453 assert_eq!(chain.account_authenticators, deserialized.account_authenticators);
1454 }
1455
1456 #[test]
1457 fn mock_chain_block_signatures() -> anyhow::Result<()> {
1458 let mut builder = MockChain::builder();
1459 builder.add_existing_mock_account(Auth::IncrNonce)?;
1460 let mut chain = builder.build()?;
1461
1462 let genesis_block = chain.latest_block();
1465 let genesis_validator_keys = genesis_block.header().validator_keys().clone();
1466 genesis_block
1467 .signatures()
1468 .verify_against(genesis_block.header().commitment(), &genesis_validator_keys)
1469 .unwrap();
1470
1471 chain.prove_next_block()?;
1473
1474 let next_block = chain.latest_block();
1477 next_block
1478 .signatures()
1479 .verify_against(next_block.header().commitment(), &genesis_validator_keys)
1480 .unwrap();
1481
1482 assert_eq!(next_block.header().validator_keys(), &genesis_validator_keys);
1485
1486 Ok(())
1487 }
1488
1489 #[tokio::test]
1490 async fn add_pending_batch() -> anyhow::Result<()> {
1491 let mut builder = MockChain::builder();
1492 let account = builder.add_existing_mock_account(Auth::IncrNonce)?;
1493 let mut chain = builder.build()?;
1494
1495 let tx = chain.build_transaction(account.id()).build()?.execute().await?;
1497 let proven_tx = LocalTransactionProver::default().prove_dummy(tx)?;
1498 let proposed_batch = chain.propose_transaction_batch(vec![proven_tx])?;
1499 let proven_batch = chain.prove_transaction_batch(proposed_batch)?;
1500
1501 let num_blocks_before = chain.proven_blocks().len();
1503 chain.add_pending_batch(proven_batch);
1504 chain.prove_next_block()?;
1505
1506 assert_eq!(chain.proven_blocks().len(), num_blocks_before + 1);
1507
1508 Ok(())
1509 }
1510}