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::SecretKey;
23use miden_protocol::note::{Note, NoteHeader, NoteId, NoteInclusionProof, Nullifier};
24use miden_protocol::transaction::{
25 ExecutedTransaction,
26 InputNote,
27 InputNotes,
28 OutputNote,
29 PartialBlockchain,
30 ProvenTransaction,
31 TransactionInputs,
32};
33use miden_tx::LocalTransactionProver;
34use miden_tx::auth::BasicAuthenticator;
35use miden_tx::utils::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: SecretKey,
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: SecretKey,
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 native_asset_id(&self) -> AccountId {
442 self.genesis_block_header().fee_parameters().native_asset_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 get_public_note(&self, note_id: &NoteId) -> Option<InputNote> {
460 let note = self.committed_notes.get(note_id)?;
461 note.clone().try_into().ok()
462 }
463
464 pub fn committed_account(&self, account_id: AccountId) -> anyhow::Result<&Account> {
468 self.committed_accounts
469 .get(&account_id)
470 .with_context(|| format!("account {account_id} not found in committed accounts"))
471 }
472
473 pub fn account_tree(&self) -> &AccountTree {
475 &self.account_tree
476 }
477
478 pub fn propose_transaction_batch<I>(
485 &self,
486 txs: impl IntoIterator<Item = ProvenTransaction, IntoIter = I>,
487 ) -> anyhow::Result<ProposedBatch>
488 where
489 I: Iterator<Item = ProvenTransaction> + Clone,
490 {
491 let transactions: Vec<_> = txs.into_iter().map(alloc::sync::Arc::new).collect();
492
493 let (batch_reference_block, partial_blockchain, unauthenticated_note_proofs) = self
494 .get_batch_inputs(
495 transactions.iter().map(|tx| tx.ref_block_num()),
496 transactions
497 .iter()
498 .flat_map(|tx| tx.unauthenticated_notes().map(NoteHeader::id)),
499 )?;
500
501 Ok(ProposedBatch::new(
502 transactions,
503 batch_reference_block,
504 partial_blockchain,
505 unauthenticated_note_proofs,
506 )?)
507 }
508
509 pub fn prove_transaction_batch(
513 &self,
514 proposed_batch: ProposedBatch,
515 ) -> anyhow::Result<ProvenBatch> {
516 let batch_prover = LocalBatchProver::new(0);
517 Ok(batch_prover.prove_dummy(proposed_batch)?)
518 }
519
520 pub fn propose_block_at<I>(
527 &self,
528 batches: impl IntoIterator<Item = ProvenBatch, IntoIter = I>,
529 timestamp: u32,
530 ) -> anyhow::Result<ProposedBlock>
531 where
532 I: Iterator<Item = ProvenBatch> + Clone,
533 {
534 let batches: Vec<_> = batches.into_iter().collect();
535
536 let block_inputs = self
537 .get_block_inputs(batches.iter())
538 .context("could not retrieve block inputs")?;
539
540 let proposed_block = ProposedBlock::new_at(block_inputs, batches, timestamp)
541 .context("failed to create proposed block")?;
542
543 Ok(proposed_block)
544 }
545
546 pub fn propose_block<I>(
550 &self,
551 batches: impl IntoIterator<Item = ProvenBatch, IntoIter = I>,
552 ) -> anyhow::Result<ProposedBlock>
553 where
554 I: Iterator<Item = ProvenBatch> + Clone,
555 {
556 let timestamp = self.latest_block_header().timestamp() + 1;
559
560 self.propose_block_at(batches, timestamp)
561 }
562
563 pub fn build_tx_context_at(
581 &self,
582 reference_block: impl Into<BlockNumber>,
583 input: impl Into<TxContextInput>,
584 note_ids: &[NoteId],
585 unauthenticated_notes: &[Note],
586 ) -> anyhow::Result<TransactionContextBuilder> {
587 let input = input.into();
588 let reference_block = reference_block.into();
589
590 let authenticator = self.account_authenticators.get(&input.id());
591 let authenticator =
592 authenticator.and_then(|authenticator| authenticator.authenticator().cloned());
593
594 anyhow::ensure!(
595 reference_block.as_usize() < self.blocks.len(),
596 "reference block {reference_block} is out of range (latest {})",
597 self.latest_block_header().block_num()
598 );
599
600 let account = match input {
601 TxContextInput::AccountId(account_id) => {
602 if account_id.is_private() {
603 return Err(anyhow::anyhow!(
604 "transaction contexts for private accounts should be created with TxContextInput::Account"
605 ));
606 }
607
608 self.committed_accounts
609 .get(&account_id)
610 .with_context(|| {
611 format!("account {account_id} not found in committed accounts")
612 })?
613 .clone()
614 },
615 TxContextInput::Account(account) => account,
616 };
617
618 let tx_inputs = self
619 .get_transaction_inputs_at(reference_block, &account, note_ids, unauthenticated_notes)
620 .context("failed to gather transaction inputs")?;
621
622 let tx_context_builder = TransactionContextBuilder::new(account)
623 .authenticator(authenticator)
624 .tx_inputs(tx_inputs);
625
626 Ok(tx_context_builder)
627 }
628
629 pub fn build_tx_context(
634 &self,
635 input: impl Into<TxContextInput>,
636 note_ids: &[NoteId],
637 unauthenticated_notes: &[Note],
638 ) -> anyhow::Result<TransactionContextBuilder> {
639 let reference_block = self.latest_block_header().block_num();
640 self.build_tx_context_at(reference_block, input, note_ids, unauthenticated_notes)
641 }
642
643 pub fn get_transaction_inputs_at(
649 &self,
650 reference_block: BlockNumber,
651 account: impl Into<PartialAccount>,
652 notes: &[NoteId],
653 unauthenticated_notes: &[Note],
654 ) -> anyhow::Result<TransactionInputs> {
655 let ref_block = self.block_header(reference_block.as_usize());
656
657 let mut input_notes = vec![];
658 let mut block_headers_map: BTreeMap<BlockNumber, BlockHeader> = BTreeMap::new();
659 for note in notes {
660 let input_note: InputNote = self
661 .committed_notes
662 .get(note)
663 .with_context(|| format!("note with id {note} not found"))?
664 .clone()
665 .try_into()
666 .with_context(|| {
667 format!("failed to convert mock chain note with id {note} into input note")
668 })?;
669
670 let note_block_num = input_note
671 .location()
672 .with_context(|| format!("note location not available: {note}"))?
673 .block_num();
674
675 if note_block_num > ref_block.block_num() {
676 anyhow::bail!(
677 "note with ID {note} was created in block {note_block_num} which is larger than the reference block number {}",
678 ref_block.block_num()
679 )
680 }
681
682 if note_block_num != ref_block.block_num() {
683 let block_header = self
684 .blocks
685 .get(note_block_num.as_usize())
686 .with_context(|| format!("block {note_block_num} not found in chain"))?
687 .header()
688 .clone();
689 block_headers_map.insert(note_block_num, block_header);
690 }
691
692 input_notes.push(input_note);
693 }
694
695 for note in unauthenticated_notes {
696 input_notes.push(InputNote::Unauthenticated { note: note.clone() })
697 }
698
699 let block_headers = block_headers_map.values();
700 let (_, partial_blockchain) = self.selective_partial_blockchain(
701 reference_block,
702 block_headers.map(BlockHeader::block_num),
703 )?;
704
705 let input_notes = InputNotes::new(input_notes)?;
706
707 Ok(TransactionInputs::new(
708 account.into(),
709 ref_block.clone(),
710 partial_blockchain,
711 input_notes,
712 )?)
713 }
714
715 pub fn get_transaction_inputs(
717 &self,
718 account: impl Into<PartialAccount>,
719 notes: &[NoteId],
720 unauthenticated_notes: &[Note],
721 ) -> anyhow::Result<TransactionInputs> {
722 let latest_block_num = self.latest_block_header().block_num();
723 self.get_transaction_inputs_at(latest_block_num, account, notes, unauthenticated_notes)
724 }
725
726 pub fn get_batch_inputs(
729 &self,
730 tx_reference_blocks: impl IntoIterator<Item = BlockNumber>,
731 unauthenticated_notes: impl Iterator<Item = NoteId>,
732 ) -> anyhow::Result<(BlockHeader, PartialBlockchain, BTreeMap<NoteId, NoteInclusionProof>)>
733 {
734 let unauthenticated_note_proofs = self.unauthenticated_note_proofs(unauthenticated_notes);
736
737 let required_blocks = tx_reference_blocks.into_iter().chain(
740 unauthenticated_note_proofs
741 .values()
742 .map(|note_proof| note_proof.location().block_num()),
743 );
744
745 let (batch_reference_block, partial_block_chain) =
746 self.latest_selective_partial_blockchain(required_blocks)?;
747
748 Ok((batch_reference_block, partial_block_chain, unauthenticated_note_proofs))
749 }
750
751 pub fn get_foreign_account_inputs(
755 &self,
756 account_id: AccountId,
757 ) -> anyhow::Result<(Account, AccountWitness)> {
758 let account = self.committed_account(account_id)?.clone();
759
760 let account_witness = self.account_tree().open(account_id);
761 assert_eq!(account_witness.state_commitment(), account.to_commitment());
762
763 Ok((account, account_witness))
764 }
765
766 pub fn get_block_inputs<'batch, I>(
768 &self,
769 batch_iter: impl IntoIterator<Item = &'batch ProvenBatch, IntoIter = I>,
770 ) -> anyhow::Result<BlockInputs>
771 where
772 I: Iterator<Item = &'batch ProvenBatch> + Clone,
773 {
774 let batch_iterator = batch_iter.into_iter();
775
776 let unauthenticated_note_proofs =
777 self.unauthenticated_note_proofs(batch_iterator.clone().flat_map(|batch| {
778 batch.input_notes().iter().filter_map(|note| note.header().map(NoteHeader::id))
779 }));
780
781 let (block_reference_block, partial_blockchain) = self
782 .latest_selective_partial_blockchain(
783 batch_iterator.clone().map(ProvenBatch::reference_block_num).chain(
784 unauthenticated_note_proofs.values().map(|proof| proof.location().block_num()),
785 ),
786 )?;
787
788 let account_witnesses =
789 self.account_witnesses(batch_iterator.clone().flat_map(ProvenBatch::updated_accounts));
790
791 let nullifier_proofs =
792 self.nullifier_witnesses(batch_iterator.flat_map(ProvenBatch::created_nullifiers));
793
794 Ok(BlockInputs::new(
795 block_reference_block,
796 partial_blockchain,
797 account_witnesses,
798 nullifier_proofs,
799 unauthenticated_note_proofs,
800 ))
801 }
802
803 pub fn prove_next_block(&mut self) -> anyhow::Result<ProvenBlock> {
810 self.prove_and_apply_block(None)
811 }
812
813 pub fn prove_next_block_at(&mut self, timestamp: u32) -> anyhow::Result<ProvenBlock> {
817 self.prove_and_apply_block(Some(timestamp))
818 }
819
820 pub fn prove_until_block(
830 &mut self,
831 target_block_num: impl Into<BlockNumber>,
832 ) -> anyhow::Result<ProvenBlock> {
833 let target_block_num = target_block_num.into();
834 let latest_block_num = self.latest_block_header().block_num();
835 assert!(
836 target_block_num > latest_block_num,
837 "target block number must be greater than the number of the latest block in the chain"
838 );
839
840 let mut last_block = None;
841 for _ in latest_block_num.as_usize()..target_block_num.as_usize() {
842 last_block = Some(self.prove_next_block()?);
843 }
844
845 Ok(last_block.expect("at least one block should have been created"))
846 }
847
848 pub fn add_pending_executed_transaction(
856 &mut self,
857 transaction: &ExecutedTransaction,
858 ) -> anyhow::Result<()> {
859 let proven_tx = LocalTransactionProver::default()
861 .prove_dummy(transaction.clone())
862 .context("failed to dummy-prove executed transaction into proven transaction")?;
863
864 self.pending_transactions.push(proven_tx);
865
866 Ok(())
867 }
868
869 pub fn add_pending_proven_transaction(&mut self, transaction: ProvenTransaction) {
874 self.pending_transactions.push(transaction);
875 }
876
877 pub fn add_pending_batch(&mut self, batch: ProvenBatch) {
882 self.pending_batches.push(batch);
883 }
884
885 fn apply_block(&mut self, proven_block: ProvenBlock) -> anyhow::Result<()> {
896 for account_update in proven_block.body().updated_accounts() {
897 self.account_tree
898 .insert(account_update.account_id(), account_update.final_state_commitment())
899 .context("failed to insert account update into account tree")?;
900 }
901
902 for nullifier in proven_block.body().created_nullifiers() {
903 self.nullifier_tree
904 .mark_spent(*nullifier, proven_block.header().block_num())
905 .context("failed to mark block nullifier as spent")?;
906
907 }
911
912 for account_update in proven_block.body().updated_accounts() {
913 match account_update.details() {
914 AccountUpdateDetails::Delta(account_delta) => {
915 if account_delta.is_full_state() {
916 let account = Account::try_from(account_delta)
917 .context("failed to convert full state delta into full account")?;
918 self.committed_accounts.insert(account.id(), account.clone());
919 } else {
920 let committed_account = self
921 .committed_accounts
922 .get_mut(&account_update.account_id())
923 .ok_or_else(|| {
924 anyhow::anyhow!("account delta in block for non-existent account")
925 })?;
926 committed_account
927 .apply_delta(account_delta)
928 .context("failed to apply account delta")?;
929 }
930 },
931 AccountUpdateDetails::Private => {},
934 }
935 }
936
937 let notes_tree = proven_block.body().compute_block_note_tree();
938 for (block_note_index, created_note) in proven_block.body().output_notes() {
939 let note_path = notes_tree.open(block_note_index);
940 let note_inclusion_proof = NoteInclusionProof::new(
941 proven_block.header().block_num(),
942 block_note_index.leaf_index_value(),
943 note_path,
944 )
945 .context("failed to create inclusion proof for output note")?;
946
947 if let OutputNote::Public(public_note) = created_note {
948 self.committed_notes.insert(
949 public_note.id(),
950 MockChainNote::Public(public_note.as_note().clone(), note_inclusion_proof),
951 );
952 } else {
953 self.committed_notes.insert(
954 created_note.id(),
955 MockChainNote::Private(
956 created_note.id(),
957 created_note.metadata().clone(),
958 note_inclusion_proof,
959 ),
960 );
961 }
962 }
963
964 debug_assert_eq!(
965 self.chain.commitment(),
966 proven_block.header().chain_commitment(),
967 "current mock chain commitment and new block's chain commitment should match"
968 );
969 debug_assert_eq!(
970 BlockNumber::from(self.chain.as_mmr().forest().num_leaves() as u32),
971 proven_block.header().block_num(),
972 "current mock chain length and new block's number should match"
973 );
974
975 self.chain.push(proven_block.header().commitment());
976 self.blocks.push(proven_block);
977
978 Ok(())
979 }
980
981 fn pending_transactions_to_batches(&mut self) -> anyhow::Result<Vec<ProvenBatch>> {
982 if self.pending_transactions.is_empty() {
985 return Ok(vec![]);
986 }
987
988 let pending_transactions = core::mem::take(&mut self.pending_transactions);
989
990 let proposed_batch = self.propose_transaction_batch(pending_transactions)?;
993 let proven_batch = self.prove_transaction_batch(proposed_batch)?;
994
995 Ok(vec![proven_batch])
996 }
997
998 fn prove_and_apply_block(&mut self, timestamp: Option<u32>) -> anyhow::Result<ProvenBlock> {
1008 let mut batches = self.pending_transactions_to_batches()?;
1012 batches.extend(core::mem::take(&mut self.pending_batches));
1013
1014 let block_timestamp =
1018 timestamp.unwrap_or(self.latest_block_header().timestamp() + Self::TIMESTAMP_STEP_SECS);
1019
1020 let proposed_block = self
1021 .propose_block_at(batches.clone(), block_timestamp)
1022 .context("failed to create proposed block")?;
1023 let proven_block = self.prove_block(proposed_block.clone())?;
1024
1025 self.apply_block(proven_block.clone()).context("failed to apply block")?;
1029
1030 Ok(proven_block)
1031 }
1032
1033 pub fn prove_block(&self, proposed_block: ProposedBlock) -> anyhow::Result<ProvenBlock> {
1035 let (header, body) = proposed_block.clone().into_header_and_body()?;
1036 let inputs = self.get_block_inputs(proposed_block.batches().as_slice())?;
1037 let block_proof = LocalBlockProver::new(MIN_PROOF_SECURITY_LEVEL).prove_dummy(
1038 proposed_block.batches().clone(),
1039 header.clone(),
1040 inputs,
1041 )?;
1042 let signature = self.validator_secret_key.sign(header.commitment());
1043 Ok(ProvenBlock::new_unchecked(header, body, signature, block_proof))
1044 }
1045}
1046
1047impl Default for MockChain {
1048 fn default() -> Self {
1049 MockChain::new()
1050 }
1051}
1052
1053impl Serializable for MockChain {
1057 fn write_into<W: ByteWriter>(&self, target: &mut W) {
1058 self.chain.write_into(target);
1059 self.blocks.write_into(target);
1060 self.nullifier_tree.write_into(target);
1061 self.account_tree.write_into(target);
1062 self.pending_transactions.write_into(target);
1063 self.committed_accounts.write_into(target);
1064 self.committed_notes.write_into(target);
1065 self.account_authenticators.write_into(target);
1066 self.validator_secret_key.write_into(target);
1067 }
1068}
1069
1070impl Deserializable for MockChain {
1071 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
1072 let chain = Blockchain::read_from(source)?;
1073 let blocks = Vec::<ProvenBlock>::read_from(source)?;
1074 let nullifier_tree = NullifierTree::read_from(source)?;
1075 let account_tree = AccountTree::read_from(source)?;
1076 let pending_transactions = Vec::<ProvenTransaction>::read_from(source)?;
1077 let committed_accounts = BTreeMap::<AccountId, Account>::read_from(source)?;
1078 let committed_notes = BTreeMap::<NoteId, MockChainNote>::read_from(source)?;
1079 let account_authenticators =
1080 BTreeMap::<AccountId, AccountAuthenticator>::read_from(source)?;
1081 let secret_key = SecretKey::read_from(source)?;
1082
1083 Ok(Self {
1084 chain,
1085 blocks,
1086 nullifier_tree,
1087 account_tree,
1088 pending_transactions,
1089 pending_batches: Vec::new(),
1090 committed_notes,
1091 committed_accounts,
1092 account_authenticators,
1093 validator_secret_key: secret_key,
1094 })
1095 }
1096}
1097
1098pub enum AccountState {
1104 New,
1105 Exists,
1106}
1107
1108#[derive(Debug, Clone)]
1113pub(super) struct AccountAuthenticator {
1114 authenticator: Option<BasicAuthenticator>,
1115}
1116
1117impl AccountAuthenticator {
1118 pub fn new(authenticator: Option<BasicAuthenticator>) -> Self {
1119 Self { authenticator }
1120 }
1121
1122 pub fn authenticator(&self) -> Option<&BasicAuthenticator> {
1123 self.authenticator.as_ref()
1124 }
1125}
1126
1127impl PartialEq for AccountAuthenticator {
1128 fn eq(&self, other: &Self) -> bool {
1129 match (&self.authenticator, &other.authenticator) {
1130 (Some(a), Some(b)) => {
1131 a.keys().keys().zip(b.keys().keys()).all(|(a_key, b_key)| a_key == b_key)
1132 },
1133 (None, None) => true,
1134 _ => false,
1135 }
1136 }
1137}
1138
1139impl Serializable for AccountAuthenticator {
1143 fn write_into<W: ByteWriter>(&self, target: &mut W) {
1144 self.authenticator
1145 .as_ref()
1146 .map(|auth| {
1147 auth.keys()
1148 .values()
1149 .map(|(secret_key, public_key)| (secret_key, public_key.as_ref().clone()))
1150 .collect::<Vec<_>>()
1151 })
1152 .write_into(target);
1153 }
1154}
1155
1156impl Deserializable for AccountAuthenticator {
1157 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
1158 let authenticator = Option::<Vec<(AuthSecretKey, PublicKey)>>::read_from(source)?;
1159
1160 let authenticator = authenticator.map(|keys| BasicAuthenticator::from_key_pairs(&keys));
1161
1162 Ok(Self { authenticator })
1163 }
1164}
1165
1166#[allow(clippy::large_enum_variant)]
1172#[derive(Debug, Clone)]
1173pub enum TxContextInput {
1174 AccountId(AccountId),
1175 Account(Account),
1176}
1177
1178impl TxContextInput {
1179 fn id(&self) -> AccountId {
1181 match self {
1182 TxContextInput::AccountId(account_id) => *account_id,
1183 TxContextInput::Account(account) => account.id(),
1184 }
1185 }
1186}
1187
1188impl From<AccountId> for TxContextInput {
1189 fn from(account: AccountId) -> Self {
1190 Self::AccountId(account)
1191 }
1192}
1193
1194impl From<Account> for TxContextInput {
1195 fn from(account: Account) -> Self {
1196 Self::Account(account)
1197 }
1198}
1199
1200#[cfg(test)]
1204mod tests {
1205 use miden_protocol::account::auth::AuthScheme;
1206 use miden_protocol::account::{AccountBuilder, AccountStorageMode};
1207 use miden_protocol::asset::{Asset, FungibleAsset};
1208 use miden_protocol::note::NoteType;
1209 use miden_protocol::testing::account_id::{
1210 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
1211 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
1212 ACCOUNT_ID_SENDER,
1213 };
1214 use miden_standards::account::wallets::BasicWallet;
1215
1216 use super::*;
1217 use crate::Auth;
1218
1219 #[test]
1220 fn prove_until_block() -> anyhow::Result<()> {
1221 let mut chain = MockChain::new();
1222 let block = chain.prove_until_block(5)?;
1223 assert_eq!(block.header().block_num(), 5u32.into());
1224 assert_eq!(chain.proven_blocks().len(), 6);
1225
1226 Ok(())
1227 }
1228
1229 #[tokio::test]
1230 async fn private_account_state_update() -> anyhow::Result<()> {
1231 let faucet_id = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into()?;
1232 let account_builder = AccountBuilder::new([4; 32])
1233 .storage_mode(AccountStorageMode::Private)
1234 .with_component(BasicWallet);
1235
1236 let mut builder = MockChain::builder();
1237 let auth_scheme = AuthScheme::EcdsaK256Keccak;
1238 let account = builder.add_account_from_builder(
1239 Auth::BasicAuth { auth_scheme },
1240 account_builder,
1241 AccountState::New,
1242 )?;
1243
1244 let account_id = account.id();
1245 assert_eq!(account.nonce().as_canonical_u64(), 0);
1246
1247 let note_1 = builder.add_p2id_note(
1248 ACCOUNT_ID_SENDER.try_into().unwrap(),
1249 account.id(),
1250 &[Asset::Fungible(FungibleAsset::new(faucet_id, 1000u64).unwrap())],
1251 NoteType::Private,
1252 )?;
1253
1254 let mut mock_chain = builder.build()?;
1255 mock_chain.prove_next_block()?;
1256
1257 let tx = mock_chain
1258 .build_tx_context(TxContextInput::Account(account), &[], &[note_1])?
1259 .build()?
1260 .execute()
1261 .await?;
1262
1263 mock_chain.add_pending_executed_transaction(&tx)?;
1264 mock_chain.prove_next_block()?;
1265
1266 assert!(tx.final_account().nonce().as_canonical_u64() > 0);
1267 assert_eq!(
1268 tx.final_account().to_commitment(),
1269 mock_chain.account_tree.open(account_id).state_commitment()
1270 );
1271
1272 Ok(())
1273 }
1274
1275 #[tokio::test]
1276 async fn mock_chain_serialization() {
1277 let mut builder = MockChain::builder();
1278
1279 let mut notes = vec![];
1280 for i in 0..10 {
1281 let account = builder
1282 .add_account_from_builder(
1283 Auth::BasicAuth {
1284 auth_scheme: AuthScheme::Falcon512Poseidon2,
1285 },
1286 AccountBuilder::new([i; 32]).with_component(BasicWallet),
1287 AccountState::New,
1288 )
1289 .unwrap();
1290 let note = builder
1291 .add_p2id_note(
1292 ACCOUNT_ID_SENDER.try_into().unwrap(),
1293 account.id(),
1294 &[Asset::Fungible(
1295 FungibleAsset::new(
1296 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET.try_into().unwrap(),
1297 1000u64,
1298 )
1299 .unwrap(),
1300 )],
1301 NoteType::Private,
1302 )
1303 .unwrap();
1304 notes.push((account, note));
1305 }
1306
1307 let mut chain = builder.build().unwrap();
1308 for (account, note) in notes {
1309 let tx = chain
1310 .build_tx_context(TxContextInput::Account(account), &[], &[note])
1311 .unwrap()
1312 .build()
1313 .unwrap()
1314 .execute()
1315 .await
1316 .unwrap();
1317 chain.add_pending_executed_transaction(&tx).unwrap();
1318 chain.prove_next_block().unwrap();
1319 }
1320
1321 let bytes = chain.to_bytes();
1322
1323 let deserialized = MockChain::read_from_bytes(&bytes).unwrap();
1324
1325 assert_eq!(chain.chain.as_mmr().peaks(), deserialized.chain.as_mmr().peaks());
1326 assert_eq!(chain.blocks, deserialized.blocks);
1327 assert_eq!(chain.nullifier_tree, deserialized.nullifier_tree);
1328 assert_eq!(chain.account_tree, deserialized.account_tree);
1329 assert_eq!(chain.pending_transactions, deserialized.pending_transactions);
1330 assert_eq!(chain.committed_accounts, deserialized.committed_accounts);
1331 assert_eq!(chain.committed_notes, deserialized.committed_notes);
1332 assert_eq!(chain.account_authenticators, deserialized.account_authenticators);
1333 }
1334
1335 #[test]
1336 fn mock_chain_block_signature() -> anyhow::Result<()> {
1337 let mut builder = MockChain::builder();
1338 builder.add_existing_mock_account(Auth::IncrNonce)?;
1339 let mut chain = builder.build()?;
1340
1341 let genesis_block = chain.latest_block();
1343 assert!(
1344 genesis_block.signature().verify(
1345 genesis_block.header().commitment(),
1346 genesis_block.header().validator_key()
1347 )
1348 );
1349
1350 chain.prove_next_block()?;
1352
1353 let next_block = chain.latest_block();
1355 assert!(
1356 next_block
1357 .signature()
1358 .verify(next_block.header().commitment(), next_block.header().validator_key())
1359 );
1360
1361 assert_eq!(next_block.header().validator_key(), next_block.header().validator_key());
1363
1364 Ok(())
1365 }
1366
1367 #[tokio::test]
1368 async fn add_pending_batch() -> anyhow::Result<()> {
1369 let mut builder = MockChain::builder();
1370 let account = builder.add_existing_mock_account(Auth::IncrNonce)?;
1371 let mut chain = builder.build()?;
1372
1373 let tx = chain.build_tx_context(account.id(), &[], &[])?.build()?.execute().await?;
1375 let proven_tx = LocalTransactionProver::default().prove_dummy(tx)?;
1376 let proposed_batch = chain.propose_transaction_batch(vec![proven_tx])?;
1377 let proven_batch = chain.prove_transaction_batch(proposed_batch)?;
1378
1379 let num_blocks_before = chain.proven_blocks().len();
1381 chain.add_pending_batch(proven_batch);
1382 chain.prove_next_block()?;
1383
1384 assert_eq!(chain.proven_blocks().len(), num_blocks_before + 1);
1385
1386 Ok(())
1387 }
1388}