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::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::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_tx::LocalTransactionProver;
34use miden_tx::auth::BasicAuthenticator;
35use miden_tx::utils::serde::{ByteReader, ByteWriter, Deserializable, Serializable};
36use miden_tx_batch_prover::LocalBatchProver;
37
38use super::note::MockChainNote;
39use crate::{MockChainBuilder, TransactionContextBuilder};
40
41#[derive(Debug, Clone)]
173pub struct MockChain {
174 chain: Blockchain,
176
177 blocks: Vec<ProvenBlock>,
179
180 nullifier_tree: NullifierTree,
182
183 account_tree: AccountTree,
185
186 pending_transactions: Vec<ProvenTransaction>,
189
190 pending_batches: Vec<ProvenBatch>,
192
193 committed_notes: BTreeMap<NoteId, MockChainNote>,
195
196 committed_accounts: BTreeMap<AccountId, Account>,
203
204 account_authenticators: BTreeMap<AccountId, AccountAuthenticator>,
207
208 validator_secret_key: SigningKey,
210}
211
212impl MockChain {
213 pub const TIMESTAMP_START_SECS: u32 = 1700000000;
218
219 pub const TIMESTAMP_STEP_SECS: u32 = 10;
222
223 pub fn new() -> Self {
228 Self::builder().build().expect("empty chain should be valid")
229 }
230
231 pub fn builder() -> MockChainBuilder {
233 MockChainBuilder::new()
234 }
235
236 pub(super) fn from_genesis_block(
238 genesis_block: ProvenBlock,
239 account_tree: AccountTree,
240 account_authenticators: BTreeMap<AccountId, AccountAuthenticator>,
241 secret_key: SigningKey,
242 genesis_notes: Vec<Note>,
243 ) -> anyhow::Result<Self> {
244 let mut chain = MockChain {
245 chain: Blockchain::default(),
246 blocks: vec![],
247 nullifier_tree: NullifierTree::default(),
248 account_tree,
249 pending_transactions: Vec::new(),
250 pending_batches: Vec::new(),
251 committed_notes: BTreeMap::new(),
252 committed_accounts: BTreeMap::new(),
253 account_authenticators,
254 validator_secret_key: secret_key,
255 };
256
257 chain
260 .apply_block(genesis_block)
261 .context("failed to build account from builder")?;
262
263 for note in genesis_notes {
267 if let Some(MockChainNote::Private(_, _, inclusion_proof)) =
268 chain.committed_notes.get(¬e.id())
269 {
270 chain.committed_notes.insert(
271 note.id(),
272 MockChainNote::Public(note.clone(), inclusion_proof.clone()),
273 );
274 }
275 }
276
277 debug_assert_eq!(chain.blocks.len(), 1);
278 debug_assert_eq!(chain.committed_accounts.len(), chain.account_tree.num_accounts());
279
280 Ok(chain)
281 }
282
283 pub fn blockchain(&self) -> &Blockchain {
288 &self.chain
289 }
290
291 pub fn latest_partial_blockchain(&self) -> PartialBlockchain {
294 let block_headers =
297 self.blocks.iter().map(|b| b.header()).take(self.blocks.len() - 1).cloned();
298
299 PartialBlockchain::from_blockchain(&self.chain, block_headers)
300 .expect("blockchain should be valid by construction")
301 }
302
303 pub fn latest_selective_partial_blockchain(
309 &self,
310 reference_blocks: impl IntoIterator<Item = BlockNumber>,
311 ) -> anyhow::Result<(BlockHeader, PartialBlockchain)> {
312 let latest_block_header = self.latest_block_header();
313
314 self.selective_partial_blockchain(latest_block_header.block_num(), reference_blocks)
315 }
316
317 pub fn selective_partial_blockchain(
323 &self,
324 reference_block: BlockNumber,
325 reference_blocks: impl IntoIterator<Item = BlockNumber>,
326 ) -> anyhow::Result<(BlockHeader, PartialBlockchain)> {
327 let reference_block_header = self.block_header(reference_block.as_usize());
328 let reference_blocks: BTreeSet<_> = reference_blocks.into_iter().collect();
331
332 let mut block_headers = Vec::new();
334
335 for block_ref_num in &reference_blocks {
336 let block_index = block_ref_num.as_usize();
337 let block = self
338 .blocks
339 .get(block_index)
340 .ok_or_else(|| anyhow::anyhow!("block {} not found in chain", block_ref_num))?;
341 let block_header = block.header().clone();
342 if block_header.commitment() != reference_block_header.commitment() {
344 block_headers.push(block_header);
345 }
346 }
347
348 let partial_blockchain =
349 PartialBlockchain::from_blockchain_at(&self.chain, reference_block, block_headers)?;
350
351 Ok((reference_block_header, partial_blockchain))
352 }
353
354 pub fn account_witnesses(
357 &self,
358 account_ids: impl IntoIterator<Item = AccountId>,
359 ) -> BTreeMap<AccountId, AccountWitness> {
360 let mut account_witnesses = BTreeMap::new();
361
362 for account_id in account_ids {
363 let witness = self.account_tree.open(account_id);
364 account_witnesses.insert(account_id, witness);
365 }
366
367 account_witnesses
368 }
369
370 pub fn nullifier_witnesses(
373 &self,
374 nullifiers: impl IntoIterator<Item = Nullifier>,
375 ) -> BTreeMap<Nullifier, NullifierWitness> {
376 let mut nullifier_proofs = BTreeMap::new();
377
378 for nullifier in nullifiers {
379 let witness = self.nullifier_tree.open(&nullifier);
380 nullifier_proofs.insert(nullifier, witness);
381 }
382
383 nullifier_proofs
384 }
385
386 pub fn unauthenticated_note_proofs(
390 &self,
391 notes: impl IntoIterator<Item = NoteId>,
392 ) -> BTreeMap<NoteId, NoteInclusionProof> {
393 let mut proofs = BTreeMap::default();
394 for note in notes {
395 if let Some(input_note) = self.committed_notes.get(¬e) {
396 proofs.insert(note, input_note.inclusion_proof().clone());
397 }
398 }
399
400 proofs
401 }
402
403 pub fn genesis_block_header(&self) -> BlockHeader {
405 self.block_header(BlockNumber::GENESIS.as_usize())
406 }
407
408 pub fn latest_block_header(&self) -> BlockHeader {
410 let chain_tip =
411 self.chain.chain_tip().expect("chain should contain at least the genesis block");
412 self.blocks[chain_tip.as_usize()].header().clone()
413 }
414
415 pub fn latest_block(&self) -> ProvenBlock {
417 let chain_tip =
418 self.chain.chain_tip().expect("chain should contain at least the genesis block");
419 self.blocks[chain_tip.as_usize()].clone()
420 }
421
422 pub fn block_header(&self, block_number: usize) -> BlockHeader {
428 self.blocks[block_number].header().clone()
429 }
430
431 pub fn proven_blocks(&self) -> &[ProvenBlock] {
433 &self.blocks
434 }
435
436 pub fn fee_faucet_id(&self) -> AccountId {
442 self.genesis_block_header().fee_parameters().fee_faucet_id()
443 }
444
445 pub fn nullifier_tree(&self) -> &NullifierTree {
447 &self.nullifier_tree
448 }
449
450 pub fn committed_notes(&self) -> &BTreeMap<NoteId, MockChainNote> {
454 &self.committed_notes
455 }
456
457 pub fn is_note_committed(&self, note_id: &NoteId) -> bool {
459 self.committed_notes.contains_key(note_id)
460 }
461
462 pub fn is_note_consumed(&self, nullifier: &Nullifier) -> bool {
464 self.nullifier_tree.get_block_num(nullifier).is_some()
465 }
466
467 pub fn is_note_unspent(&self, nullifier: &Nullifier) -> bool {
472 !self.is_note_consumed(nullifier)
473 }
474
475 pub fn get_public_note(&self, note_id: &NoteId) -> Option<InputNote> {
478 let note = self.committed_notes.get(note_id)?;
479 note.clone().try_into().ok()
480 }
481
482 pub fn committed_account(&self, account_id: AccountId) -> anyhow::Result<&Account> {
486 self.committed_accounts
487 .get(&account_id)
488 .with_context(|| format!("account {account_id} not found in committed accounts"))
489 }
490
491 pub fn account_tree(&self) -> &AccountTree {
493 &self.account_tree
494 }
495
496 pub fn propose_transaction_batch<I>(
503 &self,
504 txs: impl IntoIterator<Item = ProvenTransaction, IntoIter = I>,
505 ) -> anyhow::Result<ProposedBatch>
506 where
507 I: Iterator<Item = ProvenTransaction> + Clone,
508 {
509 let transactions: Vec<_> = txs.into_iter().map(alloc::sync::Arc::new).collect();
510
511 let (batch_reference_block, partial_blockchain, unauthenticated_note_proofs) = self
512 .get_batch_inputs(
513 transactions.iter().map(|tx| tx.ref_block_num()),
514 transactions
515 .iter()
516 .flat_map(|tx| tx.unauthenticated_notes().map(NoteHeader::id)),
517 )?;
518
519 Ok(ProposedBatch::new(
520 transactions,
521 batch_reference_block,
522 partial_blockchain,
523 unauthenticated_note_proofs,
524 )?)
525 }
526
527 pub fn prove_transaction_batch(
531 &self,
532 proposed_batch: ProposedBatch,
533 ) -> anyhow::Result<ProvenBatch> {
534 let batch_prover = LocalBatchProver::new(0);
535 Ok(batch_prover.prove_dummy(proposed_batch)?)
536 }
537
538 pub fn propose_block_at<I>(
545 &self,
546 batches: impl IntoIterator<Item = ProvenBatch, IntoIter = I>,
547 timestamp: u32,
548 ) -> anyhow::Result<ProposedBlock>
549 where
550 I: Iterator<Item = ProvenBatch> + Clone,
551 {
552 let batches: Vec<_> = batches.into_iter().collect();
553
554 let block_inputs = self
555 .get_block_inputs(batches.iter())
556 .context("could not retrieve block inputs")?;
557
558 let proposed_block = ProposedBlock::new_at(block_inputs, batches, timestamp)
559 .context("failed to create proposed block")?;
560
561 Ok(proposed_block)
562 }
563
564 pub fn propose_block<I>(
568 &self,
569 batches: impl IntoIterator<Item = ProvenBatch, IntoIter = I>,
570 ) -> anyhow::Result<ProposedBlock>
571 where
572 I: Iterator<Item = ProvenBatch> + Clone,
573 {
574 let timestamp = self.latest_block_header().timestamp() + 1;
577
578 self.propose_block_at(batches, timestamp)
579 }
580
581 pub fn build_tx_context_at(
599 &self,
600 reference_block: impl Into<BlockNumber>,
601 input: impl Into<TxContextInput>,
602 note_ids: &[NoteId],
603 unauthenticated_notes: &[Note],
604 ) -> anyhow::Result<TransactionContextBuilder> {
605 let input = input.into();
606 let reference_block = reference_block.into();
607
608 let authenticator = self.account_authenticators.get(&input.id());
609 let authenticator =
610 authenticator.and_then(|authenticator| authenticator.authenticator().cloned());
611
612 anyhow::ensure!(
613 reference_block.as_usize() < self.blocks.len(),
614 "reference block {reference_block} is out of range (latest {})",
615 self.latest_block_header().block_num()
616 );
617
618 let account = match input {
619 TxContextInput::AccountId(account_id) => {
620 if account_id.is_private() {
621 return Err(anyhow::anyhow!(
622 "transaction contexts for private accounts should be created with TxContextInput::Account"
623 ));
624 }
625
626 self.committed_accounts
627 .get(&account_id)
628 .with_context(|| {
629 format!("account {account_id} not found in committed accounts")
630 })?
631 .clone()
632 },
633 TxContextInput::Account(account) => account,
634 };
635
636 let tx_inputs = self
637 .get_transaction_inputs_at(reference_block, &account, note_ids, unauthenticated_notes)
638 .context("failed to gather transaction inputs")?;
639
640 let tx_context_builder = TransactionContextBuilder::new(account)
641 .authenticator(authenticator)
642 .tx_inputs(tx_inputs);
643
644 Ok(tx_context_builder)
645 }
646
647 pub fn build_tx_context(
652 &self,
653 input: impl Into<TxContextInput>,
654 note_ids: &[NoteId],
655 unauthenticated_notes: &[Note],
656 ) -> anyhow::Result<TransactionContextBuilder> {
657 let reference_block = self.latest_block_header().block_num();
658 self.build_tx_context_at(reference_block, input, note_ids, unauthenticated_notes)
659 }
660
661 pub fn get_transaction_inputs_at(
667 &self,
668 reference_block: BlockNumber,
669 account: impl Into<PartialAccount>,
670 notes: &[NoteId],
671 unauthenticated_notes: &[Note],
672 ) -> anyhow::Result<TransactionInputs> {
673 let ref_block = self.block_header(reference_block.as_usize());
674
675 let mut input_notes = vec![];
676 let mut block_headers_map: BTreeMap<BlockNumber, BlockHeader> = BTreeMap::new();
677 for note in notes {
678 let input_note: InputNote = self
679 .committed_notes
680 .get(note)
681 .with_context(|| format!("note with id {note} not found"))?
682 .clone()
683 .try_into()
684 .with_context(|| {
685 format!("failed to convert mock chain note with id {note} into input note")
686 })?;
687
688 let note_block_num = input_note
689 .location()
690 .with_context(|| format!("note location not available: {note}"))?
691 .block_num();
692
693 if note_block_num > ref_block.block_num() {
694 anyhow::bail!(
695 "note with ID {note} was created in block {note_block_num} which is larger than the reference block number {}",
696 ref_block.block_num()
697 )
698 }
699
700 if note_block_num != ref_block.block_num() {
701 let block_header = self
702 .blocks
703 .get(note_block_num.as_usize())
704 .with_context(|| format!("block {note_block_num} not found in chain"))?
705 .header()
706 .clone();
707 block_headers_map.insert(note_block_num, block_header);
708 }
709
710 input_notes.push(input_note);
711 }
712
713 for note in unauthenticated_notes {
714 input_notes.push(InputNote::Unauthenticated { note: note.clone() })
715 }
716
717 let block_headers = block_headers_map.values();
718 let (_, partial_blockchain) = self.selective_partial_blockchain(
719 reference_block,
720 block_headers.map(BlockHeader::block_num),
721 )?;
722
723 let input_notes = InputNotes::new(input_notes)?;
724
725 Ok(TransactionInputs::new(
726 account.into(),
727 ref_block.clone(),
728 partial_blockchain,
729 input_notes,
730 )?)
731 }
732
733 pub fn get_transaction_inputs(
735 &self,
736 account: impl Into<PartialAccount>,
737 notes: &[NoteId],
738 unauthenticated_notes: &[Note],
739 ) -> anyhow::Result<TransactionInputs> {
740 let latest_block_num = self.latest_block_header().block_num();
741 self.get_transaction_inputs_at(latest_block_num, account, notes, unauthenticated_notes)
742 }
743
744 pub fn get_batch_inputs(
747 &self,
748 tx_reference_blocks: impl IntoIterator<Item = BlockNumber>,
749 unauthenticated_notes: impl Iterator<Item = NoteId>,
750 ) -> anyhow::Result<(BlockHeader, PartialBlockchain, BTreeMap<NoteId, NoteInclusionProof>)>
751 {
752 let unauthenticated_note_proofs = self.unauthenticated_note_proofs(unauthenticated_notes);
754
755 let required_blocks = tx_reference_blocks.into_iter().chain(
758 unauthenticated_note_proofs
759 .values()
760 .map(|note_proof| note_proof.location().block_num()),
761 );
762
763 let (batch_reference_block, partial_block_chain) =
764 self.latest_selective_partial_blockchain(required_blocks)?;
765
766 Ok((batch_reference_block, partial_block_chain, unauthenticated_note_proofs))
767 }
768
769 pub fn get_foreign_account_inputs(
773 &self,
774 account_id: AccountId,
775 ) -> anyhow::Result<(Account, AccountWitness)> {
776 let account = self.committed_account(account_id)?.clone();
777
778 let account_witness = self.account_tree().open(account_id);
779 assert_eq!(account_witness.state_commitment(), account.to_commitment());
780
781 Ok((account, account_witness))
782 }
783
784 pub fn get_block_inputs<'batch, I>(
786 &self,
787 batch_iter: impl IntoIterator<Item = &'batch ProvenBatch, IntoIter = I>,
788 ) -> anyhow::Result<BlockInputs>
789 where
790 I: Iterator<Item = &'batch ProvenBatch> + Clone,
791 {
792 let batch_iterator = batch_iter.into_iter();
793
794 let unauthenticated_note_proofs =
795 self.unauthenticated_note_proofs(batch_iterator.clone().flat_map(|batch| {
796 batch.input_notes().iter().filter_map(|note| note.header().map(NoteHeader::id))
797 }));
798
799 let (block_reference_block, partial_blockchain) = self
800 .latest_selective_partial_blockchain(
801 batch_iterator.clone().map(ProvenBatch::reference_block_num).chain(
802 unauthenticated_note_proofs.values().map(|proof| proof.location().block_num()),
803 ),
804 )?;
805
806 let account_witnesses =
807 self.account_witnesses(batch_iterator.clone().flat_map(ProvenBatch::updated_accounts));
808
809 let nullifier_proofs =
810 self.nullifier_witnesses(batch_iterator.flat_map(ProvenBatch::created_nullifiers));
811
812 Ok(BlockInputs::new(
813 block_reference_block,
814 partial_blockchain,
815 account_witnesses,
816 nullifier_proofs,
817 unauthenticated_note_proofs,
818 ))
819 }
820
821 pub fn prove_next_block(&mut self) -> anyhow::Result<ProvenBlock> {
828 self.prove_and_apply_block(None)
829 }
830
831 pub fn prove_next_block_at(&mut self, timestamp: u32) -> anyhow::Result<ProvenBlock> {
835 self.prove_and_apply_block(Some(timestamp))
836 }
837
838 pub fn prove_until_block(
848 &mut self,
849 target_block_num: impl Into<BlockNumber>,
850 ) -> anyhow::Result<ProvenBlock> {
851 let target_block_num = target_block_num.into();
852 let latest_block_num = self.latest_block_header().block_num();
853 assert!(
854 target_block_num > latest_block_num,
855 "target block number must be greater than the number of the latest block in the chain"
856 );
857
858 let mut last_block = None;
859 for _ in latest_block_num.as_usize()..target_block_num.as_usize() {
860 last_block = Some(self.prove_next_block()?);
861 }
862
863 Ok(last_block.expect("at least one block should have been created"))
864 }
865
866 pub fn add_pending_executed_transaction(
874 &mut self,
875 transaction: &ExecutedTransaction,
876 ) -> anyhow::Result<()> {
877 let proven_tx = LocalTransactionProver::default()
879 .prove_dummy(transaction.clone())
880 .context("failed to dummy-prove executed transaction into proven transaction")?;
881
882 self.pending_transactions.push(proven_tx);
883
884 Ok(())
885 }
886
887 pub fn add_pending_proven_transaction(&mut self, transaction: ProvenTransaction) {
892 self.pending_transactions.push(transaction);
893 }
894
895 pub fn add_pending_batch(&mut self, batch: ProvenBatch) {
900 self.pending_batches.push(batch);
901 }
902
903 fn apply_block(&mut self, proven_block: ProvenBlock) -> anyhow::Result<()> {
914 for account_update in proven_block.body().updated_accounts() {
915 self.account_tree
916 .insert(account_update.account_id(), account_update.final_state_commitment())
917 .context("failed to insert account update into account tree")?;
918 }
919
920 for nullifier in proven_block.body().created_nullifiers() {
921 self.nullifier_tree
922 .mark_spent(*nullifier, proven_block.header().block_num())
923 .context("failed to mark block nullifier as spent")?;
924
925 }
929
930 for account_update in proven_block.body().updated_accounts() {
931 match account_update.details() {
932 AccountUpdateDetails::Delta(account_delta) => {
933 if account_delta.is_full_state() {
934 let account = Account::try_from(account_delta)
935 .context("failed to convert full state delta into full account")?;
936 self.committed_accounts.insert(account.id(), account.clone());
937 } else {
938 let committed_account = self
939 .committed_accounts
940 .get_mut(&account_update.account_id())
941 .ok_or_else(|| {
942 anyhow::anyhow!("account delta in block for non-existent account")
943 })?;
944 committed_account
945 .apply_delta(account_delta)
946 .context("failed to apply account delta")?;
947 }
948 },
949 AccountUpdateDetails::Private => {},
952 }
953 }
954
955 let notes_tree = proven_block.body().compute_block_note_tree();
956 for (block_note_index, created_note) in proven_block.body().output_notes() {
957 let note_path = notes_tree.open(block_note_index);
958 let note_inclusion_proof = NoteInclusionProof::new(
959 proven_block.header().block_num(),
960 block_note_index.leaf_index_value(),
961 note_path,
962 )
963 .context("failed to create inclusion proof for output note")?;
964
965 if let OutputNote::Public(public_note) = created_note {
966 self.committed_notes.insert(
967 public_note.id(),
968 MockChainNote::Public(public_note.as_note().clone(), note_inclusion_proof),
969 );
970 } else {
971 self.committed_notes.insert(
972 created_note.id(),
973 MockChainNote::Private(
974 created_note.id(),
975 *created_note.metadata(),
976 note_inclusion_proof,
977 ),
978 );
979 }
980 }
981
982 debug_assert_eq!(
983 self.chain.commitment(),
984 proven_block.header().chain_commitment(),
985 "current mock chain commitment and new block's chain commitment should match"
986 );
987 debug_assert_eq!(
988 BlockNumber::from(self.chain.as_mmr().forest().num_leaves() as u32),
989 proven_block.header().block_num(),
990 "current mock chain length and new block's number should match"
991 );
992
993 self.chain.push(proven_block.header().commitment());
994 self.blocks.push(proven_block);
995
996 Ok(())
997 }
998
999 fn pending_transactions_to_batches(&mut self) -> anyhow::Result<Vec<ProvenBatch>> {
1000 if self.pending_transactions.is_empty() {
1003 return Ok(vec![]);
1004 }
1005
1006 let pending_transactions = core::mem::take(&mut self.pending_transactions);
1007
1008 let proposed_batch = self.propose_transaction_batch(pending_transactions)?;
1011 let proven_batch = self.prove_transaction_batch(proposed_batch)?;
1012
1013 Ok(vec![proven_batch])
1014 }
1015
1016 fn prove_and_apply_block(&mut self, timestamp: Option<u32>) -> anyhow::Result<ProvenBlock> {
1026 let mut batches = self.pending_transactions_to_batches()?;
1030 batches.extend(core::mem::take(&mut self.pending_batches));
1031
1032 let block_timestamp =
1036 timestamp.unwrap_or(self.latest_block_header().timestamp() + Self::TIMESTAMP_STEP_SECS);
1037
1038 let proposed_block = self
1039 .propose_block_at(batches.clone(), block_timestamp)
1040 .context("failed to create proposed block")?;
1041 let proven_block = self.prove_block(proposed_block.clone())?;
1042
1043 self.apply_block(proven_block.clone()).context("failed to apply block")?;
1047
1048 Ok(proven_block)
1049 }
1050
1051 pub fn prove_block(&self, proposed_block: ProposedBlock) -> anyhow::Result<ProvenBlock> {
1053 let (header, body) = proposed_block.clone().into_header_and_body()?;
1054 let inputs = self.get_block_inputs(proposed_block.batches().as_slice())?;
1055 let block_proof = LocalBlockProver::new(MIN_PROOF_SECURITY_LEVEL).prove_dummy(
1056 proposed_block.batches().clone(),
1057 header.clone(),
1058 inputs,
1059 )?;
1060 let signature = self.validator_secret_key.sign(header.commitment());
1061 Ok(ProvenBlock::new_unchecked(header, body, signature, block_proof))
1062 }
1063}
1064
1065impl Default for MockChain {
1066 fn default() -> Self {
1067 MockChain::new()
1068 }
1069}
1070
1071impl Serializable for MockChain {
1075 fn write_into<W: ByteWriter>(&self, target: &mut W) {
1076 self.chain.write_into(target);
1077 self.blocks.write_into(target);
1078 self.nullifier_tree.write_into(target);
1079 self.account_tree.write_into(target);
1080 self.pending_transactions.write_into(target);
1081 self.committed_accounts.write_into(target);
1082 self.committed_notes.write_into(target);
1083 self.account_authenticators.write_into(target);
1084 self.validator_secret_key.write_into(target);
1085 }
1086}
1087
1088impl Deserializable for MockChain {
1089 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
1090 let chain = Blockchain::read_from(source)?;
1091 let blocks = Vec::<ProvenBlock>::read_from(source)?;
1092 let nullifier_tree = NullifierTree::read_from(source)?;
1093 let account_tree = AccountTree::read_from(source)?;
1094 let pending_transactions = Vec::<ProvenTransaction>::read_from(source)?;
1095 let committed_accounts = BTreeMap::<AccountId, Account>::read_from(source)?;
1096 let committed_notes = BTreeMap::<NoteId, MockChainNote>::read_from(source)?;
1097 let account_authenticators =
1098 BTreeMap::<AccountId, AccountAuthenticator>::read_from(source)?;
1099 let secret_key = SigningKey::read_from(source)?;
1100
1101 Ok(Self {
1102 chain,
1103 blocks,
1104 nullifier_tree,
1105 account_tree,
1106 pending_transactions,
1107 pending_batches: Vec::new(),
1108 committed_notes,
1109 committed_accounts,
1110 account_authenticators,
1111 validator_secret_key: secret_key,
1112 })
1113 }
1114}
1115
1116pub enum AccountState {
1122 New,
1123 Exists,
1124}
1125
1126#[derive(Debug, Clone)]
1131pub(super) struct AccountAuthenticator {
1132 authenticator: Option<BasicAuthenticator>,
1133}
1134
1135impl AccountAuthenticator {
1136 pub fn new(authenticator: Option<BasicAuthenticator>) -> Self {
1137 Self { authenticator }
1138 }
1139
1140 pub fn authenticator(&self) -> Option<&BasicAuthenticator> {
1141 self.authenticator.as_ref()
1142 }
1143}
1144
1145impl PartialEq for AccountAuthenticator {
1146 fn eq(&self, other: &Self) -> bool {
1147 match (&self.authenticator, &other.authenticator) {
1148 (Some(a), Some(b)) => {
1149 a.keys().keys().zip(b.keys().keys()).all(|(a_key, b_key)| a_key == b_key)
1150 },
1151 (None, None) => true,
1152 _ => false,
1153 }
1154 }
1155}
1156
1157impl Serializable for AccountAuthenticator {
1161 fn write_into<W: ByteWriter>(&self, target: &mut W) {
1162 self.authenticator
1163 .as_ref()
1164 .map(|auth| {
1165 auth.keys()
1166 .values()
1167 .map(|(secret_key, public_key)| (secret_key, public_key.as_ref().clone()))
1168 .collect::<Vec<_>>()
1169 })
1170 .write_into(target);
1171 }
1172}
1173
1174impl Deserializable for AccountAuthenticator {
1175 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
1176 let authenticator = Option::<Vec<(AuthSecretKey, PublicKey)>>::read_from(source)?;
1177
1178 let authenticator = authenticator.map(|keys| BasicAuthenticator::from_key_pairs(&keys));
1179
1180 Ok(Self { authenticator })
1181 }
1182}
1183
1184#[allow(clippy::large_enum_variant)]
1190#[derive(Debug, Clone)]
1191pub enum TxContextInput {
1192 AccountId(AccountId),
1193 Account(Account),
1194}
1195
1196impl TxContextInput {
1197 fn id(&self) -> AccountId {
1199 match self {
1200 TxContextInput::AccountId(account_id) => *account_id,
1201 TxContextInput::Account(account) => account.id(),
1202 }
1203 }
1204}
1205
1206impl From<AccountId> for TxContextInput {
1207 fn from(account: AccountId) -> Self {
1208 Self::AccountId(account)
1209 }
1210}
1211
1212impl From<Account> for TxContextInput {
1213 fn from(account: Account) -> Self {
1214 Self::Account(account)
1215 }
1216}
1217
1218#[cfg(test)]
1222mod tests {
1223 use miden_protocol::account::auth::AuthScheme;
1224 use miden_protocol::account::{AccountBuilder, AccountType};
1225 use miden_protocol::asset::{Asset, FungibleAsset};
1226 use miden_protocol::note::NoteType;
1227 use miden_protocol::testing::account_id::{
1228 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
1229 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
1230 ACCOUNT_ID_SENDER,
1231 };
1232 use miden_standards::account::wallets::BasicWallet;
1233
1234 use super::*;
1235 use crate::Auth;
1236
1237 #[test]
1238 fn prove_until_block() -> anyhow::Result<()> {
1239 let mut chain = MockChain::new();
1240 let block = chain.prove_until_block(5)?;
1241 assert_eq!(block.header().block_num(), 5u32.into());
1242 assert_eq!(chain.proven_blocks().len(), 6);
1243
1244 Ok(())
1245 }
1246
1247 #[tokio::test]
1248 async fn private_account_state_update() -> anyhow::Result<()> {
1249 let faucet_id = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into()?;
1250 let account_builder = AccountBuilder::new([4; 32])
1251 .account_type(AccountType::Private)
1252 .with_component(BasicWallet);
1253
1254 let mut builder = MockChain::builder();
1255 let auth_scheme = AuthScheme::EcdsaK256Keccak;
1256 let account = builder.add_account_from_builder(
1257 Auth::BasicAuth { auth_scheme },
1258 account_builder,
1259 AccountState::New,
1260 )?;
1261
1262 let account_id = account.id();
1263 assert_eq!(account.nonce().as_canonical_u64(), 0);
1264
1265 let note_1 = builder.add_p2id_note(
1266 ACCOUNT_ID_SENDER.try_into().unwrap(),
1267 account.id(),
1268 &[Asset::Fungible(FungibleAsset::new(faucet_id, 1000u64).unwrap())],
1269 NoteType::Private,
1270 )?;
1271
1272 let mut mock_chain = builder.build()?;
1273 mock_chain.prove_next_block()?;
1274
1275 let tx = mock_chain
1276 .build_tx_context(TxContextInput::Account(account), &[], &[note_1])?
1277 .build()?
1278 .execute()
1279 .await?;
1280
1281 mock_chain.add_pending_executed_transaction(&tx)?;
1282 mock_chain.prove_next_block()?;
1283
1284 assert!(tx.final_account().nonce().as_canonical_u64() > 0);
1285 assert_eq!(
1286 tx.final_account().to_commitment(),
1287 mock_chain.account_tree.open(account_id).state_commitment()
1288 );
1289
1290 Ok(())
1291 }
1292
1293 #[tokio::test]
1294 async fn mock_chain_serialization() {
1295 let mut builder = MockChain::builder();
1296
1297 let mut notes = vec![];
1298 for i in 0..10 {
1299 let account = builder
1300 .add_account_from_builder(
1301 Auth::BasicAuth {
1302 auth_scheme: AuthScheme::Falcon512Poseidon2,
1303 },
1304 AccountBuilder::new([i; 32]).with_component(BasicWallet),
1305 AccountState::New,
1306 )
1307 .unwrap();
1308 let note = builder
1309 .add_p2id_note(
1310 ACCOUNT_ID_SENDER.try_into().unwrap(),
1311 account.id(),
1312 &[Asset::Fungible(
1313 FungibleAsset::new(
1314 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET.try_into().unwrap(),
1315 1000u64,
1316 )
1317 .unwrap(),
1318 )],
1319 NoteType::Private,
1320 )
1321 .unwrap();
1322 notes.push((account, note));
1323 }
1324
1325 let mut chain = builder.build().unwrap();
1326 for (account, note) in notes {
1327 let tx = chain
1328 .build_tx_context(TxContextInput::Account(account), &[], &[note])
1329 .unwrap()
1330 .build()
1331 .unwrap()
1332 .execute()
1333 .await
1334 .unwrap();
1335 chain.add_pending_executed_transaction(&tx).unwrap();
1336 chain.prove_next_block().unwrap();
1337 }
1338
1339 let bytes = chain.to_bytes();
1340
1341 let deserialized = MockChain::read_from_bytes(&bytes).unwrap();
1342
1343 assert_eq!(chain.chain.as_mmr().peaks(), deserialized.chain.as_mmr().peaks());
1344 assert_eq!(chain.blocks, deserialized.blocks);
1345 assert_eq!(chain.nullifier_tree, deserialized.nullifier_tree);
1346 assert_eq!(chain.account_tree, deserialized.account_tree);
1347 assert_eq!(chain.pending_transactions, deserialized.pending_transactions);
1348 assert_eq!(chain.committed_accounts, deserialized.committed_accounts);
1349 assert_eq!(chain.committed_notes, deserialized.committed_notes);
1350 assert_eq!(chain.account_authenticators, deserialized.account_authenticators);
1351 }
1352
1353 #[test]
1354 fn mock_chain_block_signature() -> anyhow::Result<()> {
1355 let mut builder = MockChain::builder();
1356 builder.add_existing_mock_account(Auth::IncrNonce)?;
1357 let mut chain = builder.build()?;
1358
1359 let genesis_block = chain.latest_block();
1361 assert!(
1362 genesis_block.signature().verify(
1363 genesis_block.header().commitment(),
1364 genesis_block.header().validator_key()
1365 )
1366 );
1367
1368 chain.prove_next_block()?;
1370
1371 let next_block = chain.latest_block();
1373 assert!(
1374 next_block
1375 .signature()
1376 .verify(next_block.header().commitment(), next_block.header().validator_key())
1377 );
1378
1379 assert_eq!(next_block.header().validator_key(), next_block.header().validator_key());
1381
1382 Ok(())
1383 }
1384
1385 #[tokio::test]
1386 async fn add_pending_batch() -> anyhow::Result<()> {
1387 let mut builder = MockChain::builder();
1388 let account = builder.add_existing_mock_account(Auth::IncrNonce)?;
1389 let mut chain = builder.build()?;
1390
1391 let tx = chain.build_tx_context(account.id(), &[], &[])?.build()?.execute().await?;
1393 let proven_tx = LocalTransactionProver::default().prove_dummy(tx)?;
1394 let proposed_batch = chain.propose_transaction_batch(vec![proven_tx])?;
1395 let proven_batch = chain.prove_transaction_batch(proposed_batch)?;
1396
1397 let num_blocks_before = chain.proven_blocks().len();
1399 chain.add_pending_batch(proven_batch);
1400 chain.prove_next_block()?;
1401
1402 assert_eq!(chain.proven_blocks().len(), num_blocks_before + 1);
1403
1404 Ok(())
1405 }
1406}