1use alloc::collections::{BTreeMap, BTreeSet};
2use alloc::vec::Vec;
3
4use anyhow::Context;
5use miden_block_prover::LocalBlockProver;
6use miden_processor::DeserializationError;
7use miden_protocol::MIN_PROOF_SECURITY_LEVEL;
8use miden_protocol::account::auth::{AuthSecretKey, PublicKey};
9use miden_protocol::account::delta::AccountUpdateDetails;
10use miden_protocol::account::{Account, AccountId, PartialAccount};
11use miden_protocol::batch::{ProposedBatch, ProvenBatch};
12use miden_protocol::block::account_tree::{AccountTree, AccountWitness};
13use miden_protocol::block::nullifier_tree::{NullifierTree, NullifierWitness};
14use miden_protocol::block::{
15 BlockHeader,
16 BlockInputs,
17 BlockNumber,
18 Blockchain,
19 ProposedBlock,
20 ProvenBlock,
21};
22use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey;
23use miden_protocol::note::{Note, NoteHeader, NoteId, NoteInclusionProof, Nullifier};
24use miden_protocol::transaction::{
25 ExecutedTransaction,
26 InputNote,
27 InputNotes,
28 OutputNote,
29 PartialBlockchain,
30 ProvenTransaction,
31 TransactionInputs,
32};
33use miden_tx::LocalTransactionProver;
34use miden_tx::auth::BasicAuthenticator;
35use miden_tx::utils::{ByteReader, Deserializable, Serializable};
36use miden_tx_batch_prover::LocalBatchProver;
37use winterfell::ByteWriter;
38
39use super::note::MockChainNote;
40use crate::{MockChainBuilder, TransactionContextBuilder};
41
42#[derive(Debug, Clone)]
169pub struct MockChain {
170 chain: Blockchain,
172
173 blocks: Vec<ProvenBlock>,
175
176 nullifier_tree: NullifierTree,
178
179 account_tree: AccountTree,
181
182 pending_transactions: Vec<ProvenTransaction>,
185
186 committed_notes: BTreeMap<NoteId, MockChainNote>,
188
189 committed_accounts: BTreeMap<AccountId, Account>,
196
197 account_authenticators: BTreeMap<AccountId, AccountAuthenticator>,
200
201 validator_secret_key: SecretKey,
203}
204
205impl MockChain {
206 pub const TIMESTAMP_START_SECS: u32 = 1700000000;
211
212 pub const TIMESTAMP_STEP_SECS: u32 = 10;
215
216 pub fn new() -> Self {
221 Self::builder().build().expect("empty chain should be valid")
222 }
223
224 pub fn builder() -> MockChainBuilder {
226 MockChainBuilder::new()
227 }
228
229 pub(super) fn from_genesis_block(
231 genesis_block: ProvenBlock,
232 account_tree: AccountTree,
233 account_authenticators: BTreeMap<AccountId, AccountAuthenticator>,
234 secret_key: SecretKey,
235 ) -> anyhow::Result<Self> {
236 let mut chain = MockChain {
237 chain: Blockchain::default(),
238 blocks: vec![],
239 nullifier_tree: NullifierTree::default(),
240 account_tree,
241 pending_transactions: Vec::new(),
242 committed_notes: BTreeMap::new(),
243 committed_accounts: BTreeMap::new(),
244 account_authenticators,
245 validator_secret_key: secret_key,
246 };
247
248 chain
251 .apply_block(genesis_block)
252 .context("failed to build account from builder")?;
253
254 debug_assert_eq!(chain.blocks.len(), 1);
255 debug_assert_eq!(chain.committed_accounts.len(), chain.account_tree.num_accounts());
256
257 Ok(chain)
258 }
259
260 pub fn blockchain(&self) -> &Blockchain {
265 &self.chain
266 }
267
268 pub fn latest_partial_blockchain(&self) -> PartialBlockchain {
271 let block_headers =
274 self.blocks.iter().map(|b| b.header()).take(self.blocks.len() - 1).cloned();
275
276 PartialBlockchain::from_blockchain(&self.chain, block_headers)
277 .expect("blockchain should be valid by construction")
278 }
279
280 pub fn latest_selective_partial_blockchain(
286 &self,
287 reference_blocks: impl IntoIterator<Item = BlockNumber>,
288 ) -> anyhow::Result<(BlockHeader, PartialBlockchain)> {
289 let latest_block_header = self.latest_block_header();
290
291 self.selective_partial_blockchain(latest_block_header.block_num(), reference_blocks)
292 }
293
294 pub fn selective_partial_blockchain(
300 &self,
301 reference_block: BlockNumber,
302 reference_blocks: impl IntoIterator<Item = BlockNumber>,
303 ) -> anyhow::Result<(BlockHeader, PartialBlockchain)> {
304 let reference_block_header = self.block_header(reference_block.as_usize());
305 let reference_blocks: BTreeSet<_> = reference_blocks.into_iter().collect();
308
309 let mut block_headers = Vec::new();
311
312 for block_ref_num in &reference_blocks {
313 let block_index = block_ref_num.as_usize();
314 let block = self
315 .blocks
316 .get(block_index)
317 .ok_or_else(|| anyhow::anyhow!("block {} not found in chain", block_ref_num))?;
318 let block_header = block.header().clone();
319 if block_header.commitment() != reference_block_header.commitment() {
321 block_headers.push(block_header);
322 }
323 }
324
325 let partial_blockchain =
326 PartialBlockchain::from_blockchain_at(&self.chain, reference_block, block_headers)?;
327
328 Ok((reference_block_header, partial_blockchain))
329 }
330
331 pub fn account_witnesses(
334 &self,
335 account_ids: impl IntoIterator<Item = AccountId>,
336 ) -> BTreeMap<AccountId, AccountWitness> {
337 let mut account_witnesses = BTreeMap::new();
338
339 for account_id in account_ids {
340 let witness = self.account_tree.open(account_id);
341 account_witnesses.insert(account_id, witness);
342 }
343
344 account_witnesses
345 }
346
347 pub fn nullifier_witnesses(
350 &self,
351 nullifiers: impl IntoIterator<Item = Nullifier>,
352 ) -> BTreeMap<Nullifier, NullifierWitness> {
353 let mut nullifier_proofs = BTreeMap::new();
354
355 for nullifier in nullifiers {
356 let witness = self.nullifier_tree.open(&nullifier);
357 nullifier_proofs.insert(nullifier, witness);
358 }
359
360 nullifier_proofs
361 }
362
363 pub fn unauthenticated_note_proofs(
367 &self,
368 notes: impl IntoIterator<Item = NoteId>,
369 ) -> BTreeMap<NoteId, NoteInclusionProof> {
370 let mut proofs = BTreeMap::default();
371 for note in notes {
372 if let Some(input_note) = self.committed_notes.get(¬e) {
373 proofs.insert(note, input_note.inclusion_proof().clone());
374 }
375 }
376
377 proofs
378 }
379
380 pub fn genesis_block_header(&self) -> BlockHeader {
382 self.block_header(BlockNumber::GENESIS.as_usize())
383 }
384
385 pub fn latest_block_header(&self) -> BlockHeader {
387 let chain_tip =
388 self.chain.chain_tip().expect("chain should contain at least the genesis block");
389 self.blocks[chain_tip.as_usize()].header().clone()
390 }
391
392 pub fn latest_block(&self) -> ProvenBlock {
394 let chain_tip =
395 self.chain.chain_tip().expect("chain should contain at least the genesis block");
396 self.blocks[chain_tip.as_usize()].clone()
397 }
398
399 pub fn block_header(&self, block_number: usize) -> BlockHeader {
405 self.blocks[block_number].header().clone()
406 }
407
408 pub fn proven_blocks(&self) -> &[ProvenBlock] {
410 &self.blocks
411 }
412
413 pub fn native_asset_id(&self) -> AccountId {
419 self.genesis_block_header().fee_parameters().native_asset_id()
420 }
421
422 pub fn nullifier_tree(&self) -> &NullifierTree {
424 &self.nullifier_tree
425 }
426
427 pub fn committed_notes(&self) -> &BTreeMap<NoteId, MockChainNote> {
431 &self.committed_notes
432 }
433
434 pub fn get_public_note(&self, note_id: &NoteId) -> Option<InputNote> {
437 let note = self.committed_notes.get(note_id)?;
438 note.clone().try_into().ok()
439 }
440
441 pub fn committed_account(&self, account_id: AccountId) -> anyhow::Result<&Account> {
445 self.committed_accounts
446 .get(&account_id)
447 .with_context(|| format!("account {account_id} not found in committed accounts"))
448 }
449
450 pub fn account_tree(&self) -> &AccountTree {
452 &self.account_tree
453 }
454
455 pub fn propose_transaction_batch<I>(
462 &self,
463 txs: impl IntoIterator<Item = ProvenTransaction, IntoIter = I>,
464 ) -> anyhow::Result<ProposedBatch>
465 where
466 I: Iterator<Item = ProvenTransaction> + Clone,
467 {
468 let transactions: Vec<_> = txs.into_iter().map(alloc::sync::Arc::new).collect();
469
470 let (batch_reference_block, partial_blockchain, unauthenticated_note_proofs) = self
471 .get_batch_inputs(
472 transactions.iter().map(|tx| tx.ref_block_num()),
473 transactions
474 .iter()
475 .flat_map(|tx| tx.unauthenticated_notes().map(NoteHeader::id)),
476 )?;
477
478 Ok(ProposedBatch::new(
479 transactions,
480 batch_reference_block,
481 partial_blockchain,
482 unauthenticated_note_proofs,
483 )?)
484 }
485
486 pub fn prove_transaction_batch(
490 &self,
491 proposed_batch: ProposedBatch,
492 ) -> anyhow::Result<ProvenBatch> {
493 let batch_prover = LocalBatchProver::new(0);
494 Ok(batch_prover.prove_dummy(proposed_batch)?)
495 }
496
497 pub fn propose_block_at<I>(
504 &self,
505 batches: impl IntoIterator<Item = ProvenBatch, IntoIter = I>,
506 timestamp: u32,
507 ) -> anyhow::Result<ProposedBlock>
508 where
509 I: Iterator<Item = ProvenBatch> + Clone,
510 {
511 let batches: Vec<_> = batches.into_iter().collect();
512
513 let block_inputs = self
514 .get_block_inputs(batches.iter())
515 .context("could not retrieve block inputs")?;
516
517 let proposed_block = ProposedBlock::new_at(block_inputs, batches, timestamp)
518 .context("failed to create proposed block")?;
519
520 Ok(proposed_block)
521 }
522
523 pub fn propose_block<I>(
527 &self,
528 batches: impl IntoIterator<Item = ProvenBatch, IntoIter = I>,
529 ) -> anyhow::Result<ProposedBlock>
530 where
531 I: Iterator<Item = ProvenBatch> + Clone,
532 {
533 let timestamp = self.latest_block_header().timestamp() + 1;
536
537 self.propose_block_at(batches, timestamp)
538 }
539
540 pub fn build_tx_context_at(
558 &self,
559 reference_block: impl Into<BlockNumber>,
560 input: impl Into<TxContextInput>,
561 note_ids: &[NoteId],
562 unauthenticated_notes: &[Note],
563 ) -> anyhow::Result<TransactionContextBuilder> {
564 let input = input.into();
565 let reference_block = reference_block.into();
566
567 let authenticator = self.account_authenticators.get(&input.id());
568 let authenticator =
569 authenticator.and_then(|authenticator| authenticator.authenticator().cloned());
570
571 anyhow::ensure!(
572 reference_block.as_usize() < self.blocks.len(),
573 "reference block {reference_block} is out of range (latest {})",
574 self.latest_block_header().block_num()
575 );
576
577 let account = match input {
578 TxContextInput::AccountId(account_id) => {
579 if account_id.is_private() {
580 return Err(anyhow::anyhow!(
581 "transaction contexts for private accounts should be created with TxContextInput::Account"
582 ));
583 }
584
585 self.committed_accounts
586 .get(&account_id)
587 .with_context(|| {
588 format!("account {account_id} not found in committed accounts")
589 })?
590 .clone()
591 },
592 TxContextInput::Account(account) => account,
593 };
594
595 let tx_inputs = self
596 .get_transaction_inputs_at(reference_block, &account, note_ids, unauthenticated_notes)
597 .context("failed to gather transaction inputs")?;
598
599 let tx_context_builder = TransactionContextBuilder::new(account)
600 .authenticator(authenticator)
601 .tx_inputs(tx_inputs);
602
603 Ok(tx_context_builder)
604 }
605
606 pub fn build_tx_context(
611 &self,
612 input: impl Into<TxContextInput>,
613 note_ids: &[NoteId],
614 unauthenticated_notes: &[Note],
615 ) -> anyhow::Result<TransactionContextBuilder> {
616 let reference_block = self.latest_block_header().block_num();
617 self.build_tx_context_at(reference_block, input, note_ids, unauthenticated_notes)
618 }
619
620 pub fn get_transaction_inputs_at(
626 &self,
627 reference_block: BlockNumber,
628 account: impl Into<PartialAccount>,
629 notes: &[NoteId],
630 unauthenticated_notes: &[Note],
631 ) -> anyhow::Result<TransactionInputs> {
632 let ref_block = self.block_header(reference_block.as_usize());
633
634 let mut input_notes = vec![];
635 let mut block_headers_map: BTreeMap<BlockNumber, BlockHeader> = BTreeMap::new();
636 for note in notes {
637 let input_note: InputNote = self
638 .committed_notes
639 .get(note)
640 .with_context(|| format!("note with id {note} not found"))?
641 .clone()
642 .try_into()
643 .with_context(|| {
644 format!("failed to convert mock chain note with id {note} into input note")
645 })?;
646
647 let note_block_num = input_note
648 .location()
649 .with_context(|| format!("note location not available: {note}"))?
650 .block_num();
651
652 if note_block_num > ref_block.block_num() {
653 anyhow::bail!(
654 "note with ID {note} was created in block {note_block_num} which is larger than the reference block number {}",
655 ref_block.block_num()
656 )
657 }
658
659 if note_block_num != ref_block.block_num() {
660 let block_header = self
661 .blocks
662 .get(note_block_num.as_usize())
663 .with_context(|| format!("block {note_block_num} not found in chain"))?
664 .header()
665 .clone();
666 block_headers_map.insert(note_block_num, block_header);
667 }
668
669 input_notes.push(input_note);
670 }
671
672 for note in unauthenticated_notes {
673 input_notes.push(InputNote::Unauthenticated { note: note.clone() })
674 }
675
676 let block_headers = block_headers_map.values();
677 let (_, partial_blockchain) = self.selective_partial_blockchain(
678 reference_block,
679 block_headers.map(BlockHeader::block_num),
680 )?;
681
682 let input_notes = InputNotes::new(input_notes)?;
683
684 Ok(TransactionInputs::new(
685 account.into(),
686 ref_block.clone(),
687 partial_blockchain,
688 input_notes,
689 )?)
690 }
691
692 pub fn get_transaction_inputs(
694 &self,
695 account: impl Into<PartialAccount>,
696 notes: &[NoteId],
697 unauthenticated_notes: &[Note],
698 ) -> anyhow::Result<TransactionInputs> {
699 let latest_block_num = self.latest_block_header().block_num();
700 self.get_transaction_inputs_at(latest_block_num, account, notes, unauthenticated_notes)
701 }
702
703 pub fn get_batch_inputs(
706 &self,
707 tx_reference_blocks: impl IntoIterator<Item = BlockNumber>,
708 unauthenticated_notes: impl Iterator<Item = NoteId>,
709 ) -> anyhow::Result<(BlockHeader, PartialBlockchain, BTreeMap<NoteId, NoteInclusionProof>)>
710 {
711 let unauthenticated_note_proofs = self.unauthenticated_note_proofs(unauthenticated_notes);
713
714 let required_blocks = tx_reference_blocks.into_iter().chain(
717 unauthenticated_note_proofs
718 .values()
719 .map(|note_proof| note_proof.location().block_num()),
720 );
721
722 let (batch_reference_block, partial_block_chain) =
723 self.latest_selective_partial_blockchain(required_blocks)?;
724
725 Ok((batch_reference_block, partial_block_chain, unauthenticated_note_proofs))
726 }
727
728 pub fn get_foreign_account_inputs(
732 &self,
733 account_id: AccountId,
734 ) -> anyhow::Result<(Account, AccountWitness)> {
735 let account = self.committed_account(account_id)?.clone();
736
737 let account_witness = self.account_tree().open(account_id);
738 assert_eq!(account_witness.state_commitment(), account.to_commitment());
739
740 Ok((account, account_witness))
741 }
742
743 pub fn get_block_inputs<'batch, I>(
745 &self,
746 batch_iter: impl IntoIterator<Item = &'batch ProvenBatch, IntoIter = I>,
747 ) -> anyhow::Result<BlockInputs>
748 where
749 I: Iterator<Item = &'batch ProvenBatch> + Clone,
750 {
751 let batch_iterator = batch_iter.into_iter();
752
753 let unauthenticated_note_proofs =
754 self.unauthenticated_note_proofs(batch_iterator.clone().flat_map(|batch| {
755 batch.input_notes().iter().filter_map(|note| note.header().map(NoteHeader::id))
756 }));
757
758 let (block_reference_block, partial_blockchain) = self
759 .latest_selective_partial_blockchain(
760 batch_iterator.clone().map(ProvenBatch::reference_block_num).chain(
761 unauthenticated_note_proofs.values().map(|proof| proof.location().block_num()),
762 ),
763 )?;
764
765 let account_witnesses =
766 self.account_witnesses(batch_iterator.clone().flat_map(ProvenBatch::updated_accounts));
767
768 let nullifier_proofs =
769 self.nullifier_witnesses(batch_iterator.flat_map(ProvenBatch::created_nullifiers));
770
771 Ok(BlockInputs::new(
772 block_reference_block,
773 partial_blockchain,
774 account_witnesses,
775 nullifier_proofs,
776 unauthenticated_note_proofs,
777 ))
778 }
779
780 pub fn prove_next_block(&mut self) -> anyhow::Result<ProvenBlock> {
787 self.prove_and_apply_block(None)
788 }
789
790 pub fn prove_next_block_at(&mut self, timestamp: u32) -> anyhow::Result<ProvenBlock> {
794 self.prove_and_apply_block(Some(timestamp))
795 }
796
797 pub fn prove_until_block(
807 &mut self,
808 target_block_num: impl Into<BlockNumber>,
809 ) -> anyhow::Result<ProvenBlock> {
810 let target_block_num = target_block_num.into();
811 let latest_block_num = self.latest_block_header().block_num();
812 assert!(
813 target_block_num > latest_block_num,
814 "target block number must be greater than the number of the latest block in the chain"
815 );
816
817 let mut last_block = None;
818 for _ in latest_block_num.as_usize()..target_block_num.as_usize() {
819 last_block = Some(self.prove_next_block()?);
820 }
821
822 Ok(last_block.expect("at least one block should have been created"))
823 }
824
825 pub fn add_pending_executed_transaction(
833 &mut self,
834 transaction: &ExecutedTransaction,
835 ) -> anyhow::Result<()> {
836 let proven_tx = LocalTransactionProver::default()
838 .prove_dummy(transaction.clone())
839 .context("failed to dummy-prove executed transaction into proven transaction")?;
840
841 self.pending_transactions.push(proven_tx);
842
843 Ok(())
844 }
845
846 pub fn add_pending_proven_transaction(&mut self, transaction: ProvenTransaction) {
851 self.pending_transactions.push(transaction);
852 }
853
854 fn apply_block(&mut self, proven_block: ProvenBlock) -> anyhow::Result<()> {
865 for account_update in proven_block.body().updated_accounts() {
866 self.account_tree
867 .insert(account_update.account_id(), account_update.final_state_commitment())
868 .context("failed to insert account update into account tree")?;
869 }
870
871 for nullifier in proven_block.body().created_nullifiers() {
872 self.nullifier_tree
873 .mark_spent(*nullifier, proven_block.header().block_num())
874 .context("failed to mark block nullifier as spent")?;
875
876 }
880
881 for account_update in proven_block.body().updated_accounts() {
882 match account_update.details() {
883 AccountUpdateDetails::Delta(account_delta) => {
884 if account_delta.is_full_state() {
885 let account = Account::try_from(account_delta)
886 .context("failed to convert full state delta into full account")?;
887 self.committed_accounts.insert(account.id(), account.clone());
888 } else {
889 let committed_account = self
890 .committed_accounts
891 .get_mut(&account_update.account_id())
892 .ok_or_else(|| {
893 anyhow::anyhow!("account delta in block for non-existent account")
894 })?;
895 committed_account
896 .apply_delta(account_delta)
897 .context("failed to apply account delta")?;
898 }
899 },
900 AccountUpdateDetails::Private => {},
903 }
904 }
905
906 let notes_tree = proven_block.body().compute_block_note_tree();
907 for (block_note_index, created_note) in proven_block.body().output_notes() {
908 let note_path = notes_tree.open(block_note_index);
909 let note_inclusion_proof = NoteInclusionProof::new(
910 proven_block.header().block_num(),
911 block_note_index.leaf_index_value(),
912 note_path,
913 )
914 .context("failed to create inclusion proof for output note")?;
915
916 if let OutputNote::Full(note) = created_note {
917 self.committed_notes
918 .insert(note.id(), MockChainNote::Public(note.clone(), note_inclusion_proof));
919 } else {
920 self.committed_notes.insert(
921 created_note.id(),
922 MockChainNote::Private(
923 created_note.id(),
924 created_note.metadata().clone(),
925 note_inclusion_proof,
926 ),
927 );
928 }
929 }
930
931 debug_assert_eq!(
932 self.chain.commitment(),
933 proven_block.header().chain_commitment(),
934 "current mock chain commitment and new block's chain commitment should match"
935 );
936 debug_assert_eq!(
937 BlockNumber::from(self.chain.as_mmr().forest().num_leaves() as u32),
938 proven_block.header().block_num(),
939 "current mock chain length and new block's number should match"
940 );
941
942 self.chain.push(proven_block.header().commitment());
943 self.blocks.push(proven_block);
944
945 Ok(())
946 }
947
948 fn pending_transactions_to_batches(&mut self) -> anyhow::Result<Vec<ProvenBatch>> {
949 if self.pending_transactions.is_empty() {
952 return Ok(vec![]);
953 }
954
955 let pending_transactions = core::mem::take(&mut self.pending_transactions);
956
957 let proposed_batch = self.propose_transaction_batch(pending_transactions)?;
960 let proven_batch = self.prove_transaction_batch(proposed_batch)?;
961
962 Ok(vec![proven_batch])
963 }
964
965 fn prove_and_apply_block(&mut self, timestamp: Option<u32>) -> anyhow::Result<ProvenBlock> {
975 let batches = self.pending_transactions_to_batches()?;
979
980 let block_timestamp =
984 timestamp.unwrap_or(self.latest_block_header().timestamp() + Self::TIMESTAMP_STEP_SECS);
985
986 let proposed_block = self
987 .propose_block_at(batches.clone(), block_timestamp)
988 .context("failed to create proposed block")?;
989 let proven_block = self.prove_block(proposed_block.clone())?;
990
991 self.apply_block(proven_block.clone()).context("failed to apply block")?;
995
996 Ok(proven_block)
997 }
998
999 pub fn prove_block(&self, proposed_block: ProposedBlock) -> anyhow::Result<ProvenBlock> {
1001 let (header, body) = proposed_block.clone().into_header_and_body()?;
1002 let inputs = self.get_block_inputs(proposed_block.batches().as_slice())?;
1003 let block_proof = LocalBlockProver::new(MIN_PROOF_SECURITY_LEVEL).prove_dummy(
1004 proposed_block.batches().clone(),
1005 header.clone(),
1006 inputs,
1007 )?;
1008 let signature = self.validator_secret_key.sign(header.commitment());
1009 Ok(ProvenBlock::new_unchecked(header, body, signature, block_proof))
1010 }
1011}
1012
1013impl Default for MockChain {
1014 fn default() -> Self {
1015 MockChain::new()
1016 }
1017}
1018
1019impl Serializable for MockChain {
1023 fn write_into<W: ByteWriter>(&self, target: &mut W) {
1024 self.chain.write_into(target);
1025 self.blocks.write_into(target);
1026 self.nullifier_tree.write_into(target);
1027 self.account_tree.write_into(target);
1028 self.pending_transactions.write_into(target);
1029 self.committed_accounts.write_into(target);
1030 self.committed_notes.write_into(target);
1031 self.account_authenticators.write_into(target);
1032 self.validator_secret_key.write_into(target);
1033 }
1034}
1035
1036impl Deserializable for MockChain {
1037 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
1038 let chain = Blockchain::read_from(source)?;
1039 let blocks = Vec::<ProvenBlock>::read_from(source)?;
1040 let nullifier_tree = NullifierTree::read_from(source)?;
1041 let account_tree = AccountTree::read_from(source)?;
1042 let pending_transactions = Vec::<ProvenTransaction>::read_from(source)?;
1043 let committed_accounts = BTreeMap::<AccountId, Account>::read_from(source)?;
1044 let committed_notes = BTreeMap::<NoteId, MockChainNote>::read_from(source)?;
1045 let account_authenticators =
1046 BTreeMap::<AccountId, AccountAuthenticator>::read_from(source)?;
1047 let secret_key = SecretKey::read_from(source)?;
1048
1049 Ok(Self {
1050 chain,
1051 blocks,
1052 nullifier_tree,
1053 account_tree,
1054 pending_transactions,
1055 committed_notes,
1056 committed_accounts,
1057 account_authenticators,
1058 validator_secret_key: secret_key,
1059 })
1060 }
1061}
1062
1063pub enum AccountState {
1069 New,
1070 Exists,
1071}
1072
1073#[derive(Debug, Clone)]
1078pub(super) struct AccountAuthenticator {
1079 authenticator: Option<BasicAuthenticator>,
1080}
1081
1082impl AccountAuthenticator {
1083 pub fn new(authenticator: Option<BasicAuthenticator>) -> Self {
1084 Self { authenticator }
1085 }
1086
1087 pub fn authenticator(&self) -> Option<&BasicAuthenticator> {
1088 self.authenticator.as_ref()
1089 }
1090}
1091
1092impl PartialEq for AccountAuthenticator {
1093 fn eq(&self, other: &Self) -> bool {
1094 match (&self.authenticator, &other.authenticator) {
1095 (Some(a), Some(b)) => {
1096 a.keys().keys().zip(b.keys().keys()).all(|(a_key, b_key)| a_key == b_key)
1097 },
1098 (None, None) => true,
1099 _ => false,
1100 }
1101 }
1102}
1103
1104impl Serializable for AccountAuthenticator {
1108 fn write_into<W: ByteWriter>(&self, target: &mut W) {
1109 self.authenticator
1110 .as_ref()
1111 .map(|auth| {
1112 auth.keys()
1113 .values()
1114 .map(|(secret_key, public_key)| (secret_key, public_key.as_ref().clone()))
1115 .collect::<Vec<_>>()
1116 })
1117 .write_into(target);
1118 }
1119}
1120
1121impl Deserializable for AccountAuthenticator {
1122 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
1123 let authenticator = Option::<Vec<(AuthSecretKey, PublicKey)>>::read_from(source)?;
1124
1125 let authenticator = authenticator.map(|keys| BasicAuthenticator::from_key_pairs(&keys));
1126
1127 Ok(Self { authenticator })
1128 }
1129}
1130
1131#[allow(clippy::large_enum_variant)]
1137#[derive(Debug, Clone)]
1138pub enum TxContextInput {
1139 AccountId(AccountId),
1140 Account(Account),
1141}
1142
1143impl TxContextInput {
1144 fn id(&self) -> AccountId {
1146 match self {
1147 TxContextInput::AccountId(account_id) => *account_id,
1148 TxContextInput::Account(account) => account.id(),
1149 }
1150 }
1151}
1152
1153impl From<AccountId> for TxContextInput {
1154 fn from(account: AccountId) -> Self {
1155 Self::AccountId(account)
1156 }
1157}
1158
1159impl From<Account> for TxContextInput {
1160 fn from(account: Account) -> Self {
1161 Self::Account(account)
1162 }
1163}
1164
1165#[cfg(test)]
1169mod tests {
1170 use miden_protocol::account::auth::AuthScheme;
1171 use miden_protocol::account::{AccountBuilder, AccountStorageMode};
1172 use miden_protocol::asset::{Asset, FungibleAsset};
1173 use miden_protocol::note::NoteType;
1174 use miden_protocol::testing::account_id::{
1175 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
1176 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
1177 ACCOUNT_ID_SENDER,
1178 };
1179 use miden_standards::account::wallets::BasicWallet;
1180
1181 use super::*;
1182 use crate::Auth;
1183
1184 #[test]
1185 fn prove_until_block() -> anyhow::Result<()> {
1186 let mut chain = MockChain::new();
1187 let block = chain.prove_until_block(5)?;
1188 assert_eq!(block.header().block_num(), 5u32.into());
1189 assert_eq!(chain.proven_blocks().len(), 6);
1190
1191 Ok(())
1192 }
1193
1194 #[tokio::test]
1195 async fn private_account_state_update() -> anyhow::Result<()> {
1196 let faucet_id = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into()?;
1197 let account_builder = AccountBuilder::new([4; 32])
1198 .storage_mode(AccountStorageMode::Private)
1199 .with_component(BasicWallet);
1200
1201 let mut builder = MockChain::builder();
1202 let auth_scheme = AuthScheme::EcdsaK256Keccak;
1203 let account = builder.add_account_from_builder(
1204 Auth::BasicAuth { auth_scheme },
1205 account_builder,
1206 AccountState::New,
1207 )?;
1208
1209 let account_id = account.id();
1210 assert_eq!(account.nonce().as_int(), 0);
1211
1212 let note_1 = builder.add_p2id_note(
1213 ACCOUNT_ID_SENDER.try_into().unwrap(),
1214 account.id(),
1215 &[Asset::Fungible(FungibleAsset::new(faucet_id, 1000u64).unwrap())],
1216 NoteType::Private,
1217 )?;
1218
1219 let mut mock_chain = builder.build()?;
1220 mock_chain.prove_next_block()?;
1221
1222 let tx = mock_chain
1223 .build_tx_context(TxContextInput::Account(account), &[], &[note_1])?
1224 .build()?
1225 .execute()
1226 .await?;
1227
1228 mock_chain.add_pending_executed_transaction(&tx)?;
1229 mock_chain.prove_next_block()?;
1230
1231 assert!(tx.final_account().nonce().as_int() > 0);
1232 assert_eq!(
1233 tx.final_account().to_commitment(),
1234 mock_chain.account_tree.open(account_id).state_commitment()
1235 );
1236
1237 Ok(())
1238 }
1239
1240 #[tokio::test]
1241 async fn mock_chain_serialization() {
1242 let mut builder = MockChain::builder();
1243
1244 let mut notes = vec![];
1245 for i in 0..10 {
1246 let account = builder
1247 .add_account_from_builder(
1248 Auth::BasicAuth { auth_scheme: AuthScheme::Falcon512Rpo },
1249 AccountBuilder::new([i; 32]).with_component(BasicWallet),
1250 AccountState::New,
1251 )
1252 .unwrap();
1253 let note = builder
1254 .add_p2id_note(
1255 ACCOUNT_ID_SENDER.try_into().unwrap(),
1256 account.id(),
1257 &[Asset::Fungible(
1258 FungibleAsset::new(
1259 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET.try_into().unwrap(),
1260 1000u64,
1261 )
1262 .unwrap(),
1263 )],
1264 NoteType::Private,
1265 )
1266 .unwrap();
1267 notes.push((account, note));
1268 }
1269
1270 let mut chain = builder.build().unwrap();
1271 for (account, note) in notes {
1272 let tx = chain
1273 .build_tx_context(TxContextInput::Account(account), &[], &[note])
1274 .unwrap()
1275 .build()
1276 .unwrap()
1277 .execute()
1278 .await
1279 .unwrap();
1280 chain.add_pending_executed_transaction(&tx).unwrap();
1281 chain.prove_next_block().unwrap();
1282 }
1283
1284 let bytes = chain.to_bytes();
1285
1286 let deserialized = MockChain::read_from_bytes(&bytes).unwrap();
1287
1288 assert_eq!(chain.chain.as_mmr().peaks(), deserialized.chain.as_mmr().peaks());
1289 assert_eq!(chain.blocks, deserialized.blocks);
1290 assert_eq!(chain.nullifier_tree, deserialized.nullifier_tree);
1291 assert_eq!(chain.account_tree, deserialized.account_tree);
1292 assert_eq!(chain.pending_transactions, deserialized.pending_transactions);
1293 assert_eq!(chain.committed_accounts, deserialized.committed_accounts);
1294 assert_eq!(chain.committed_notes, deserialized.committed_notes);
1295 assert_eq!(chain.account_authenticators, deserialized.account_authenticators);
1296 }
1297
1298 #[test]
1299 fn mock_chain_block_signature() -> anyhow::Result<()> {
1300 let mut builder = MockChain::builder();
1301 builder.add_existing_mock_account(Auth::IncrNonce)?;
1302 let mut chain = builder.build()?;
1303
1304 let genesis_block = chain.latest_block();
1306 assert!(
1307 genesis_block.signature().verify(
1308 genesis_block.header().commitment(),
1309 genesis_block.header().validator_key()
1310 )
1311 );
1312
1313 chain.prove_next_block()?;
1315
1316 let next_block = chain.latest_block();
1318 assert!(
1319 next_block
1320 .signature()
1321 .verify(next_block.header().commitment(), next_block.header().validator_key())
1322 );
1323
1324 assert_eq!(next_block.header().validator_key(), next_block.header().validator_key());
1326
1327 Ok(())
1328 }
1329}