1use alloc::collections::{BTreeMap, BTreeSet};
2use alloc::vec::Vec;
3
4use anyhow::Context;
5use miden_block_prover::{LocalBlockProver, ProvenBlockError};
6use miden_objects::account::auth::AuthSecretKey;
7use miden_objects::account::delta::AccountUpdateDetails;
8use miden_objects::account::{Account, AccountId, PartialAccount};
9use miden_objects::batch::{ProposedBatch, ProvenBatch};
10use miden_objects::block::account_tree::AccountTree;
11use miden_objects::block::{
12 AccountWitness,
13 BlockHeader,
14 BlockInputs,
15 BlockNumber,
16 Blockchain,
17 NullifierTree,
18 NullifierWitness,
19 ProposedBlock,
20 ProvenBlock,
21};
22use miden_objects::note::{Note, NoteHeader, NoteId, NoteInclusionProof, Nullifier};
23use miden_objects::transaction::{
24 ExecutedTransaction,
25 InputNote,
26 InputNotes,
27 OutputNote,
28 PartialBlockchain,
29 ProvenTransaction,
30 TransactionInputs,
31};
32use miden_processor::DeserializationError;
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)]
155pub struct MockChain {
156 chain: Blockchain,
158
159 blocks: Vec<ProvenBlock>,
161
162 nullifier_tree: NullifierTree,
164
165 account_tree: AccountTree,
167
168 pending_transactions: Vec<ProvenTransaction>,
171
172 committed_notes: BTreeMap<NoteId, MockChainNote>,
174
175 committed_accounts: BTreeMap<AccountId, Account>,
182
183 account_authenticators: BTreeMap<AccountId, AccountAuthenticator>,
186}
187
188impl MockChain {
189 pub const TIMESTAMP_START_SECS: u32 = 1700000000;
194
195 pub const TIMESTAMP_STEP_SECS: u32 = 10;
198
199 pub fn new() -> Self {
204 Self::builder().build().expect("empty chain should be valid")
205 }
206
207 pub fn builder() -> MockChainBuilder {
209 MockChainBuilder::new()
210 }
211
212 pub(super) fn from_genesis_block(
214 genesis_block: ProvenBlock,
215 account_tree: AccountTree,
216 account_authenticators: BTreeMap<AccountId, AccountAuthenticator>,
217 ) -> anyhow::Result<Self> {
218 let mut chain = MockChain {
219 chain: Blockchain::default(),
220 blocks: vec![],
221 nullifier_tree: NullifierTree::default(),
222 account_tree,
223 pending_transactions: Vec::new(),
224 committed_notes: BTreeMap::new(),
225 committed_accounts: BTreeMap::new(),
226 account_authenticators,
227 };
228
229 chain
232 .apply_block(genesis_block)
233 .context("failed to build account from builder")?;
234
235 debug_assert_eq!(chain.blocks.len(), 1);
236 debug_assert_eq!(chain.committed_accounts.len(), chain.account_tree.num_accounts());
237
238 Ok(chain)
239 }
240
241 pub fn blockchain(&self) -> &Blockchain {
246 &self.chain
247 }
248
249 pub fn latest_partial_blockchain(&self) -> PartialBlockchain {
252 let block_headers =
255 self.blocks.iter().map(|b| b.header()).take(self.blocks.len() - 1).cloned();
256
257 PartialBlockchain::from_blockchain(&self.chain, block_headers)
258 .expect("blockchain should be valid by construction")
259 }
260
261 pub fn latest_selective_partial_blockchain(
267 &self,
268 reference_blocks: impl IntoIterator<Item = BlockNumber>,
269 ) -> anyhow::Result<(BlockHeader, PartialBlockchain)> {
270 let latest_block_header = self.latest_block_header();
271
272 self.selective_partial_blockchain(latest_block_header.block_num(), reference_blocks)
273 }
274
275 pub fn selective_partial_blockchain(
281 &self,
282 reference_block: BlockNumber,
283 reference_blocks: impl IntoIterator<Item = BlockNumber>,
284 ) -> anyhow::Result<(BlockHeader, PartialBlockchain)> {
285 let reference_block_header = self.block_header(reference_block.as_usize());
286 let reference_blocks: BTreeSet<_> = reference_blocks.into_iter().collect();
289
290 let mut block_headers = Vec::new();
292
293 for block_ref_num in &reference_blocks {
294 let block_index = block_ref_num.as_usize();
295 let block = self
296 .blocks
297 .get(block_index)
298 .ok_or_else(|| anyhow::anyhow!("block {} not found in chain", block_ref_num))?;
299 let block_header = block.header().clone();
300 if block_header.commitment() != reference_block_header.commitment() {
302 block_headers.push(block_header);
303 }
304 }
305
306 let partial_blockchain =
307 PartialBlockchain::from_blockchain_at(&self.chain, reference_block, block_headers)?;
308
309 Ok((reference_block_header, partial_blockchain))
310 }
311
312 pub fn account_witnesses(
315 &self,
316 account_ids: impl IntoIterator<Item = AccountId>,
317 ) -> BTreeMap<AccountId, AccountWitness> {
318 let mut account_witnesses = BTreeMap::new();
319
320 for account_id in account_ids {
321 let witness = self.account_tree.open(account_id);
322 account_witnesses.insert(account_id, witness);
323 }
324
325 account_witnesses
326 }
327
328 pub fn nullifier_witnesses(
331 &self,
332 nullifiers: impl IntoIterator<Item = Nullifier>,
333 ) -> BTreeMap<Nullifier, NullifierWitness> {
334 let mut nullifier_proofs = BTreeMap::new();
335
336 for nullifier in nullifiers {
337 let witness = self.nullifier_tree.open(&nullifier);
338 nullifier_proofs.insert(nullifier, witness);
339 }
340
341 nullifier_proofs
342 }
343
344 pub fn unauthenticated_note_proofs(
348 &self,
349 notes: impl IntoIterator<Item = NoteId>,
350 ) -> BTreeMap<NoteId, NoteInclusionProof> {
351 let mut proofs = BTreeMap::default();
352 for note in notes {
353 if let Some(input_note) = self.committed_notes.get(¬e) {
354 proofs.insert(note, input_note.inclusion_proof().clone());
355 }
356 }
357
358 proofs
359 }
360
361 pub fn genesis_block_header(&self) -> BlockHeader {
363 self.block_header(BlockNumber::GENESIS.as_usize())
364 }
365
366 pub fn latest_block_header(&self) -> BlockHeader {
368 let chain_tip =
369 self.chain.chain_tip().expect("chain should contain at least the genesis block");
370 self.blocks[chain_tip.as_usize()].header().clone()
371 }
372
373 pub fn block_header(&self, block_number: usize) -> BlockHeader {
379 self.blocks[block_number].header().clone()
380 }
381
382 pub fn proven_blocks(&self) -> &[ProvenBlock] {
384 &self.blocks
385 }
386
387 pub fn native_asset_id(&self) -> AccountId {
393 self.genesis_block_header().fee_parameters().native_asset_id()
394 }
395
396 pub fn nullifier_tree(&self) -> &NullifierTree {
398 &self.nullifier_tree
399 }
400
401 pub fn committed_notes(&self) -> &BTreeMap<NoteId, MockChainNote> {
405 &self.committed_notes
406 }
407
408 pub fn get_public_note(&self, note_id: &NoteId) -> Option<InputNote> {
411 let note = self.committed_notes.get(note_id)?;
412 note.clone().try_into().ok()
413 }
414
415 pub fn committed_account(&self, account_id: AccountId) -> anyhow::Result<&Account> {
419 self.committed_accounts
420 .get(&account_id)
421 .with_context(|| format!("account {account_id} not found in committed accounts"))
422 }
423
424 pub fn account_tree(&self) -> &AccountTree {
426 &self.account_tree
427 }
428
429 pub fn propose_transaction_batch<I>(
436 &self,
437 txs: impl IntoIterator<Item = ProvenTransaction, IntoIter = I>,
438 ) -> anyhow::Result<ProposedBatch>
439 where
440 I: Iterator<Item = ProvenTransaction> + Clone,
441 {
442 let transactions: Vec<_> = txs.into_iter().map(alloc::sync::Arc::new).collect();
443
444 let (batch_reference_block, partial_blockchain, unauthenticated_note_proofs) = self
445 .get_batch_inputs(
446 transactions.iter().map(|tx| tx.ref_block_num()),
447 transactions
448 .iter()
449 .flat_map(|tx| tx.unauthenticated_notes().map(NoteHeader::id)),
450 )?;
451
452 Ok(ProposedBatch::new(
453 transactions,
454 batch_reference_block,
455 partial_blockchain,
456 unauthenticated_note_proofs,
457 )?)
458 }
459
460 pub fn prove_transaction_batch(
464 &self,
465 proposed_batch: ProposedBatch,
466 ) -> anyhow::Result<ProvenBatch> {
467 let batch_prover = LocalBatchProver::new(0);
468 Ok(batch_prover.prove_dummy(proposed_batch)?)
469 }
470
471 pub fn propose_block_at<I>(
478 &self,
479 batches: impl IntoIterator<Item = ProvenBatch, IntoIter = I>,
480 timestamp: u32,
481 ) -> anyhow::Result<ProposedBlock>
482 where
483 I: Iterator<Item = ProvenBatch> + Clone,
484 {
485 let batches: Vec<_> = batches.into_iter().collect();
486
487 let block_inputs = self
488 .get_block_inputs(batches.iter())
489 .context("could not retrieve block inputs")?;
490
491 let proposed_block = ProposedBlock::new_at(block_inputs, batches, timestamp)
492 .context("failed to create proposed block")?;
493
494 Ok(proposed_block)
495 }
496
497 pub fn propose_block<I>(
501 &self,
502 batches: impl IntoIterator<Item = ProvenBatch, IntoIter = I>,
503 ) -> anyhow::Result<ProposedBlock>
504 where
505 I: Iterator<Item = ProvenBatch> + Clone,
506 {
507 let timestamp = self.latest_block_header().timestamp() + 1;
510
511 self.propose_block_at(batches, timestamp)
512 }
513
514 pub fn prove_block(
518 &self,
519 proposed_block: ProposedBlock,
520 ) -> Result<ProvenBlock, ProvenBlockError> {
521 LocalBlockProver::new(0).prove_dummy(proposed_block)
522 }
523
524 pub fn build_tx_context_at(
542 &self,
543 reference_block: impl Into<BlockNumber>,
544 input: impl Into<TxContextInput>,
545 note_ids: &[NoteId],
546 unauthenticated_notes: &[Note],
547 ) -> anyhow::Result<TransactionContextBuilder> {
548 let input = input.into();
549 let reference_block = reference_block.into();
550
551 let authenticator = self.account_authenticators.get(&input.id());
552 let authenticator =
553 authenticator.and_then(|authenticator| authenticator.authenticator().cloned());
554
555 anyhow::ensure!(
556 reference_block.as_usize() < self.blocks.len(),
557 "reference block {reference_block} is out of range (latest {})",
558 self.latest_block_header().block_num()
559 );
560
561 let account = match input {
562 TxContextInput::AccountId(account_id) => {
563 if account_id.is_private() {
564 return Err(anyhow::anyhow!(
565 "transaction contexts for private accounts should be created with TxContextInput::Account"
566 ));
567 }
568
569 self.committed_accounts
570 .get(&account_id)
571 .with_context(|| {
572 format!("account {account_id} not found in committed accounts")
573 })?
574 .clone()
575 },
576 TxContextInput::Account(account) => account,
577 };
578
579 let tx_inputs = self
580 .get_transaction_inputs_at(reference_block, &account, note_ids, unauthenticated_notes)
581 .context("failed to gather transaction inputs")?;
582
583 let tx_context_builder = TransactionContextBuilder::new(account)
584 .authenticator(authenticator)
585 .tx_inputs(tx_inputs);
586
587 Ok(tx_context_builder)
588 }
589
590 pub fn build_tx_context(
595 &self,
596 input: impl Into<TxContextInput>,
597 note_ids: &[NoteId],
598 unauthenticated_notes: &[Note],
599 ) -> anyhow::Result<TransactionContextBuilder> {
600 let reference_block = self.latest_block_header().block_num();
601 self.build_tx_context_at(reference_block, input, note_ids, unauthenticated_notes)
602 }
603
604 pub fn get_transaction_inputs_at(
610 &self,
611 reference_block: BlockNumber,
612 account: impl Into<PartialAccount>,
613 notes: &[NoteId],
614 unauthenticated_notes: &[Note],
615 ) -> anyhow::Result<TransactionInputs> {
616 let ref_block = self.block_header(reference_block.as_usize());
617
618 let mut input_notes = vec![];
619 let mut block_headers_map: BTreeMap<BlockNumber, BlockHeader> = BTreeMap::new();
620 for note in notes {
621 let input_note: InputNote = self
622 .committed_notes
623 .get(note)
624 .with_context(|| format!("note with id {note} not found"))?
625 .clone()
626 .try_into()
627 .with_context(|| {
628 format!("failed to convert mock chain note with id {note} into input note")
629 })?;
630
631 let note_block_num = input_note
632 .location()
633 .with_context(|| format!("note location not available: {note}"))?
634 .block_num();
635
636 if note_block_num > ref_block.block_num() {
637 anyhow::bail!(
638 "note with ID {note} was created in block {note_block_num} which is larger than the reference block number {}",
639 ref_block.block_num()
640 )
641 }
642
643 if note_block_num != ref_block.block_num() {
644 let block_header = self
645 .blocks
646 .get(note_block_num.as_usize())
647 .with_context(|| format!("block {note_block_num} not found in chain"))?
648 .header()
649 .clone();
650 block_headers_map.insert(note_block_num, block_header);
651 }
652
653 input_notes.push(input_note);
654 }
655
656 for note in unauthenticated_notes {
657 input_notes.push(InputNote::Unauthenticated { note: note.clone() })
658 }
659
660 let block_headers = block_headers_map.values();
661 let (_, partial_blockchain) = self.selective_partial_blockchain(
662 reference_block,
663 block_headers.map(BlockHeader::block_num),
664 )?;
665
666 let input_notes = InputNotes::new(input_notes)?;
667
668 Ok(TransactionInputs::new(
669 account.into(),
670 ref_block.clone(),
671 partial_blockchain,
672 input_notes,
673 )?)
674 }
675
676 pub fn get_transaction_inputs(
678 &self,
679 account: impl Into<PartialAccount>,
680 notes: &[NoteId],
681 unauthenticated_notes: &[Note],
682 ) -> anyhow::Result<TransactionInputs> {
683 let latest_block_num = self.latest_block_header().block_num();
684 self.get_transaction_inputs_at(latest_block_num, account, notes, unauthenticated_notes)
685 }
686
687 pub fn get_batch_inputs(
690 &self,
691 tx_reference_blocks: impl IntoIterator<Item = BlockNumber>,
692 unauthenticated_notes: impl Iterator<Item = NoteId>,
693 ) -> anyhow::Result<(BlockHeader, PartialBlockchain, BTreeMap<NoteId, NoteInclusionProof>)>
694 {
695 let unauthenticated_note_proofs = self.unauthenticated_note_proofs(unauthenticated_notes);
697
698 let required_blocks = tx_reference_blocks.into_iter().chain(
701 unauthenticated_note_proofs
702 .values()
703 .map(|note_proof| note_proof.location().block_num()),
704 );
705
706 let (batch_reference_block, partial_block_chain) =
707 self.latest_selective_partial_blockchain(required_blocks)?;
708
709 Ok((batch_reference_block, partial_block_chain, unauthenticated_note_proofs))
710 }
711
712 #[cfg(test)]
716 pub(crate) fn get_foreign_account_inputs(
717 &self,
718 account_id: AccountId,
719 ) -> anyhow::Result<(Account, AccountWitness)> {
720 let account = self.committed_account(account_id)?.clone();
721
722 let account_witness = self.account_tree().open(account_id);
723 assert_eq!(account_witness.state_commitment(), account.commitment());
724
725 Ok((account, account_witness))
726 }
727
728 pub fn get_block_inputs<'batch, I>(
730 &self,
731 batch_iter: impl IntoIterator<Item = &'batch ProvenBatch, IntoIter = I>,
732 ) -> anyhow::Result<BlockInputs>
733 where
734 I: Iterator<Item = &'batch ProvenBatch> + Clone,
735 {
736 let batch_iterator = batch_iter.into_iter();
737
738 let unauthenticated_note_proofs =
739 self.unauthenticated_note_proofs(batch_iterator.clone().flat_map(|batch| {
740 batch.input_notes().iter().filter_map(|note| note.header().map(NoteHeader::id))
741 }));
742
743 let (block_reference_block, partial_blockchain) = self
744 .latest_selective_partial_blockchain(
745 batch_iterator.clone().map(ProvenBatch::reference_block_num).chain(
746 unauthenticated_note_proofs.values().map(|proof| proof.location().block_num()),
747 ),
748 )?;
749
750 let account_witnesses =
751 self.account_witnesses(batch_iterator.clone().flat_map(ProvenBatch::updated_accounts));
752
753 let nullifier_proofs =
754 self.nullifier_witnesses(batch_iterator.flat_map(ProvenBatch::created_nullifiers));
755
756 Ok(BlockInputs::new(
757 block_reference_block,
758 partial_blockchain,
759 account_witnesses,
760 nullifier_proofs,
761 unauthenticated_note_proofs,
762 ))
763 }
764
765 pub fn prove_next_block(&mut self) -> anyhow::Result<ProvenBlock> {
772 self.prove_and_apply_block(None)
773 }
774
775 pub fn prove_next_block_at(&mut self, timestamp: u32) -> anyhow::Result<ProvenBlock> {
779 self.prove_and_apply_block(Some(timestamp))
780 }
781
782 pub fn prove_until_block(
792 &mut self,
793 target_block_num: impl Into<BlockNumber>,
794 ) -> anyhow::Result<ProvenBlock> {
795 let target_block_num = target_block_num.into();
796 let latest_block_num = self.latest_block_header().block_num();
797 assert!(
798 target_block_num > latest_block_num,
799 "target block number must be greater than the number of the latest block in the chain"
800 );
801
802 let mut last_block = None;
803 for _ in latest_block_num.as_usize()..target_block_num.as_usize() {
804 last_block = Some(self.prove_next_block()?);
805 }
806
807 Ok(last_block.expect("at least one block should have been created"))
808 }
809
810 pub fn add_pending_executed_transaction(
818 &mut self,
819 transaction: &ExecutedTransaction,
820 ) -> anyhow::Result<()> {
821 let proven_tx = LocalTransactionProver::default()
823 .prove_dummy(transaction.clone())
824 .context("failed to dummy-prove executed transaction into proven transaction")?;
825
826 self.pending_transactions.push(proven_tx);
827
828 Ok(())
829 }
830
831 pub fn add_pending_proven_transaction(&mut self, transaction: ProvenTransaction) {
836 self.pending_transactions.push(transaction);
837 }
838
839 fn apply_block(&mut self, proven_block: ProvenBlock) -> anyhow::Result<()> {
850 for account_update in proven_block.updated_accounts() {
851 self.account_tree
852 .insert(account_update.account_id(), account_update.final_state_commitment())
853 .context("failed to insert account update into account tree")?;
854 }
855
856 for nullifier in proven_block.created_nullifiers() {
857 self.nullifier_tree
858 .mark_spent(*nullifier, proven_block.header().block_num())
859 .context("failed to mark block nullifier as spent")?;
860
861 }
865
866 for account_update in proven_block.updated_accounts() {
867 match account_update.details() {
868 AccountUpdateDetails::Delta(account_delta) => {
869 if account_delta.is_full_state() {
870 let account = Account::try_from(account_delta)
871 .context("failed to convert full state delta into full account")?;
872 self.committed_accounts.insert(account.id(), account.clone());
873 } else {
874 let committed_account = self
875 .committed_accounts
876 .get_mut(&account_update.account_id())
877 .ok_or_else(|| {
878 anyhow::anyhow!("account delta in block for non-existent account")
879 })?;
880 committed_account
881 .apply_delta(account_delta)
882 .context("failed to apply account delta")?;
883 }
884 },
885 AccountUpdateDetails::Private => {},
888 }
889 }
890
891 let notes_tree = proven_block.build_output_note_tree();
892 for (block_note_index, created_note) in proven_block.output_notes() {
893 let note_path = notes_tree.open(block_note_index);
894 let note_inclusion_proof = NoteInclusionProof::new(
895 proven_block.header().block_num(),
896 block_note_index.leaf_index_value(),
897 note_path,
898 )
899 .context("failed to create inclusion proof for output note")?;
900
901 if let OutputNote::Full(note) = created_note {
902 self.committed_notes
903 .insert(note.id(), MockChainNote::Public(note.clone(), note_inclusion_proof));
904 } else {
905 self.committed_notes.insert(
906 created_note.id(),
907 MockChainNote::Private(
908 created_note.id(),
909 *created_note.metadata(),
910 note_inclusion_proof,
911 ),
912 );
913 }
914 }
915
916 debug_assert_eq!(
917 self.chain.commitment(),
918 proven_block.header().chain_commitment(),
919 "current mock chain commitment and new block's chain commitment should match"
920 );
921 debug_assert_eq!(
922 BlockNumber::from(self.chain.as_mmr().forest().num_leaves() as u32),
923 proven_block.header().block_num(),
924 "current mock chain length and new block's number should match"
925 );
926
927 self.chain.push(proven_block.header().commitment());
928 self.blocks.push(proven_block);
929
930 Ok(())
931 }
932
933 fn pending_transactions_to_batches(&mut self) -> anyhow::Result<Vec<ProvenBatch>> {
934 if self.pending_transactions.is_empty() {
937 return Ok(vec![]);
938 }
939
940 let pending_transactions = core::mem::take(&mut self.pending_transactions);
941
942 let proposed_batch = self.propose_transaction_batch(pending_transactions)?;
945 let proven_batch = self.prove_transaction_batch(proposed_batch)?;
946
947 Ok(vec![proven_batch])
948 }
949
950 fn prove_and_apply_block(&mut self, timestamp: Option<u32>) -> anyhow::Result<ProvenBlock> {
960 let batches = self.pending_transactions_to_batches()?;
964
965 let block_timestamp =
969 timestamp.unwrap_or(self.latest_block_header().timestamp() + Self::TIMESTAMP_STEP_SECS);
970
971 let proposed_block = self
972 .propose_block_at(batches, block_timestamp)
973 .context("failed to create proposed block")?;
974 let proven_block = self.prove_block(proposed_block).context("failed to prove block")?;
975
976 self.apply_block(proven_block.clone()).context("failed to apply block")?;
980
981 Ok(proven_block)
982 }
983}
984
985impl Default for MockChain {
986 fn default() -> Self {
987 MockChain::new()
988 }
989}
990
991impl Serializable for MockChain {
995 fn write_into<W: ByteWriter>(&self, target: &mut W) {
996 self.chain.write_into(target);
997 self.blocks.write_into(target);
998 self.nullifier_tree.write_into(target);
999 self.account_tree.write_into(target);
1000 self.pending_transactions.write_into(target);
1001 self.committed_accounts.write_into(target);
1002 self.committed_notes.write_into(target);
1003 self.account_authenticators.write_into(target);
1004 }
1005}
1006
1007impl Deserializable for MockChain {
1008 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
1009 let chain = Blockchain::read_from(source)?;
1010 let blocks = Vec::<ProvenBlock>::read_from(source)?;
1011 let nullifier_tree = NullifierTree::read_from(source)?;
1012 let account_tree = AccountTree::read_from(source)?;
1013 let pending_transactions = Vec::<ProvenTransaction>::read_from(source)?;
1014 let committed_accounts = BTreeMap::<AccountId, Account>::read_from(source)?;
1015 let committed_notes = BTreeMap::<NoteId, MockChainNote>::read_from(source)?;
1016 let account_authenticators =
1017 BTreeMap::<AccountId, AccountAuthenticator>::read_from(source)?;
1018
1019 Ok(Self {
1020 chain,
1021 blocks,
1022 nullifier_tree,
1023 account_tree,
1024 pending_transactions,
1025 committed_notes,
1026 committed_accounts,
1027 account_authenticators,
1028 })
1029 }
1030}
1031
1032pub enum AccountState {
1038 New,
1039 Exists,
1040}
1041
1042#[derive(Debug, Clone)]
1047pub(super) struct AccountAuthenticator {
1048 authenticator: Option<BasicAuthenticator>,
1049}
1050
1051impl AccountAuthenticator {
1052 pub fn new(authenticator: Option<BasicAuthenticator>) -> Self {
1053 Self { authenticator }
1054 }
1055
1056 pub fn authenticator(&self) -> Option<&BasicAuthenticator> {
1057 self.authenticator.as_ref()
1058 }
1059}
1060
1061impl PartialEq for AccountAuthenticator {
1062 fn eq(&self, other: &Self) -> bool {
1063 match (&self.authenticator, &other.authenticator) {
1064 (Some(a), Some(b)) => {
1065 a.keys().keys().zip(b.keys().keys()).all(|(a_key, b_key)| a_key == b_key)
1066 },
1067 (None, None) => true,
1068 _ => false,
1069 }
1070 }
1071}
1072
1073impl Serializable for AccountAuthenticator {
1077 fn write_into<W: ByteWriter>(&self, target: &mut W) {
1078 self.authenticator
1079 .as_ref()
1080 .map(|auth| auth.keys().values().collect::<Vec<_>>())
1081 .write_into(target);
1082 }
1083}
1084
1085impl Deserializable for AccountAuthenticator {
1086 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
1087 let authenticator = Option::<Vec<AuthSecretKey>>::read_from(source)?;
1088
1089 let authenticator = authenticator.map(|keys| BasicAuthenticator::new(&keys));
1090
1091 Ok(Self { authenticator })
1092 }
1093}
1094
1095#[allow(clippy::large_enum_variant)]
1101#[derive(Debug, Clone)]
1102pub enum TxContextInput {
1103 AccountId(AccountId),
1104 Account(Account),
1105}
1106
1107impl TxContextInput {
1108 fn id(&self) -> AccountId {
1110 match self {
1111 TxContextInput::AccountId(account_id) => *account_id,
1112 TxContextInput::Account(account) => account.id(),
1113 }
1114 }
1115}
1116
1117impl From<AccountId> for TxContextInput {
1118 fn from(account: AccountId) -> Self {
1119 Self::AccountId(account)
1120 }
1121}
1122
1123impl From<Account> for TxContextInput {
1124 fn from(account: Account) -> Self {
1125 Self::Account(account)
1126 }
1127}
1128
1129#[cfg(test)]
1133mod tests {
1134 use miden_lib::account::wallets::BasicWallet;
1135 use miden_objects::account::{AccountBuilder, AccountStorageMode};
1136 use miden_objects::asset::{Asset, FungibleAsset};
1137 use miden_objects::note::NoteType;
1138 use miden_objects::testing::account_id::{
1139 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
1140 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
1141 ACCOUNT_ID_SENDER,
1142 };
1143
1144 use super::*;
1145 use crate::Auth;
1146
1147 #[test]
1148 fn prove_until_block() -> anyhow::Result<()> {
1149 let mut chain = MockChain::new();
1150 let block = chain.prove_until_block(5)?;
1151 assert_eq!(block.header().block_num(), 5u32.into());
1152 assert_eq!(chain.proven_blocks().len(), 6);
1153
1154 Ok(())
1155 }
1156
1157 #[tokio::test]
1158 async fn private_account_state_update() -> anyhow::Result<()> {
1159 let faucet_id = ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET.try_into()?;
1160 let account_builder = AccountBuilder::new([4; 32])
1161 .storage_mode(AccountStorageMode::Private)
1162 .with_component(BasicWallet);
1163
1164 let mut builder = MockChain::builder();
1165 let account = builder.add_account_from_builder(
1166 Auth::BasicAuth,
1167 account_builder,
1168 AccountState::New,
1169 )?;
1170
1171 let account_id = account.id();
1172 assert_eq!(account.nonce().as_int(), 0);
1173
1174 let note_1 = builder.add_p2id_note(
1175 ACCOUNT_ID_SENDER.try_into().unwrap(),
1176 account.id(),
1177 &[Asset::Fungible(FungibleAsset::new(faucet_id, 1000u64).unwrap())],
1178 NoteType::Private,
1179 )?;
1180
1181 let mut mock_chain = builder.build()?;
1182 mock_chain.prove_next_block()?;
1183
1184 let tx = mock_chain
1185 .build_tx_context(TxContextInput::Account(account), &[], &[note_1])?
1186 .build()?
1187 .execute()
1188 .await?;
1189
1190 mock_chain.add_pending_executed_transaction(&tx)?;
1191 mock_chain.prove_next_block()?;
1192
1193 assert!(tx.final_account().nonce().as_int() > 0);
1194 assert_eq!(
1195 tx.final_account().commitment(),
1196 mock_chain.account_tree.open(account_id).state_commitment()
1197 );
1198
1199 Ok(())
1200 }
1201
1202 #[tokio::test]
1203 async fn mock_chain_serialization() {
1204 let mut builder = MockChain::builder();
1205
1206 let mut notes = vec![];
1207 for i in 0..10 {
1208 let account = builder
1209 .add_account_from_builder(
1210 Auth::BasicAuth,
1211 AccountBuilder::new([i; 32]).with_component(BasicWallet),
1212 AccountState::New,
1213 )
1214 .unwrap();
1215 let note = builder
1216 .add_p2id_note(
1217 ACCOUNT_ID_SENDER.try_into().unwrap(),
1218 account.id(),
1219 &[Asset::Fungible(
1220 FungibleAsset::new(
1221 ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET.try_into().unwrap(),
1222 1000u64,
1223 )
1224 .unwrap(),
1225 )],
1226 NoteType::Private,
1227 )
1228 .unwrap();
1229 notes.push((account, note));
1230 }
1231
1232 let mut chain = builder.build().unwrap();
1233 for (account, note) in notes {
1234 let tx = chain
1235 .build_tx_context(TxContextInput::Account(account), &[], &[note])
1236 .unwrap()
1237 .build()
1238 .unwrap()
1239 .execute()
1240 .await
1241 .unwrap();
1242 chain.add_pending_executed_transaction(&tx).unwrap();
1243 chain.prove_next_block().unwrap();
1244 }
1245
1246 let bytes = chain.to_bytes();
1247
1248 let deserialized = MockChain::read_from_bytes(&bytes).unwrap();
1249
1250 assert_eq!(chain.chain.as_mmr().peaks(), deserialized.chain.as_mmr().peaks());
1251 assert_eq!(chain.blocks, deserialized.blocks);
1252 assert_eq!(chain.nullifier_tree, deserialized.nullifier_tree);
1253 assert_eq!(chain.account_tree, deserialized.account_tree);
1254 assert_eq!(chain.pending_transactions, deserialized.pending_transactions);
1255 assert_eq!(chain.committed_accounts, deserialized.committed_accounts);
1256 assert_eq!(chain.committed_notes, deserialized.committed_notes);
1257 assert_eq!(chain.account_authenticators, deserialized.account_authenticators);
1258 }
1259}