miden_testing/tx_context/
context.rs

1#[cfg(any(feature = "async", target_family = "wasm"))]
2use alloc::boxed::Box;
3use alloc::{collections::BTreeSet, rc::Rc, sync::Arc, vec::Vec};
4
5use miden_lib::transaction::TransactionKernel;
6use miden_objects::{
7    account::{Account, AccountId},
8    assembly::{Assembler, SourceManager},
9    block::{BlockHeader, BlockNumber},
10    note::Note,
11    transaction::{
12        ExecutedTransaction, InputNote, InputNotes, PartialBlockchain, TransactionArgs,
13        TransactionInputs,
14    },
15};
16use miden_tx::{
17    DataStore, DataStoreError, TransactionExecutor, TransactionExecutorError, TransactionMastStore,
18    auth::{BasicAuthenticator, TransactionAuthenticator},
19};
20use rand_chacha::ChaCha20Rng;
21use vm_processor::{
22    AdviceInputs, Digest, ExecutionError, MastForest, MastForestStore, Process, Word,
23};
24use winter_maybe_async::*;
25
26use crate::{MockHost, executor::CodeExecutor, tx_context::builder::MockAuthenticator};
27
28// TRANSACTION CONTEXT
29// ================================================================================================
30
31/// Represents all needed data for executing a transaction, or arbitrary code.
32///
33/// It implements [`DataStore`], so transactions may be executed with
34/// [TransactionExecutor](miden_tx::TransactionExecutor)
35pub struct TransactionContext {
36    pub(super) expected_output_notes: Vec<Note>,
37    pub(super) tx_args: TransactionArgs,
38    pub(super) tx_inputs: TransactionInputs,
39    pub(super) mast_store: TransactionMastStore,
40    pub(super) advice_inputs: AdviceInputs,
41    pub(super) authenticator: Option<MockAuthenticator>,
42    pub(super) source_manager: Arc<dyn SourceManager>,
43}
44
45impl TransactionContext {
46    /// Executes arbitrary code within the context of a mocked transaction environment and returns
47    /// the resulting [Process].
48    ///
49    /// The code is compiled with the assembler attached to this context and executed with advice
50    /// inputs constructed from the data stored in the context. The program is run on a [MockHost]
51    /// which is loaded with the procedures exposed by the transaction kernel, and also individual
52    /// kernel functions (not normally exposed).
53    ///
54    /// # Errors
55    /// Returns an error if the assembly or execution of the provided code fails.
56    pub fn execute_code_with_assembler(
57        &self,
58        code: &str,
59        assembler: Assembler,
60    ) -> Result<Process, ExecutionError> {
61        let (stack_inputs, mut advice_inputs) = TransactionKernel::prepare_inputs(
62            &self.tx_inputs,
63            &self.tx_args,
64            Some(self.advice_inputs.clone()),
65        )
66        .unwrap();
67        advice_inputs.extend(self.advice_inputs.clone());
68
69        let test_lib = TransactionKernel::kernel_as_library();
70
71        let source_manager = assembler.source_manager();
72        let program = assembler
73            .with_debug_mode(true)
74            .assemble_program(code)
75            .expect("compilation of the provided code failed");
76
77        let mast_store = Rc::new(TransactionMastStore::new());
78
79        mast_store.insert(program.mast_forest().clone());
80        mast_store.insert(test_lib.mast_forest().clone());
81        mast_store.load_transaction_code(self.account().code(), self.input_notes(), &self.tx_args);
82
83        CodeExecutor::new(MockHost::new(
84            self.tx_inputs.account().into(),
85            advice_inputs,
86            mast_store,
87            self.tx_args.foreign_account_code_commitments(),
88        ))
89        .stack_inputs(stack_inputs)
90        .execute_program(program, source_manager)
91    }
92
93    /// Executes arbitrary code with a testing assembler ([TransactionKernel::testing_assembler()]).
94    ///
95    /// For more information, see the docs for [TransactionContext::execute_code_with_assembler()].
96    pub fn execute_code(&self, code: &str) -> Result<Process, ExecutionError> {
97        let assembler = TransactionKernel::testing_assembler();
98        self.execute_code_with_assembler(code, assembler)
99    }
100
101    /// Executes the transaction through a [TransactionExecutor]
102    #[allow(clippy::arc_with_non_send_sync)]
103    #[maybe_async]
104    pub fn execute(self) -> Result<ExecutedTransaction, TransactionExecutorError> {
105        let account_id = self.account().id();
106        let block_num = self.tx_inputs().block_header().block_num();
107        let notes = self.tx_inputs().input_notes().clone();
108        let tx_args = self.tx_args().clone();
109
110        let authenticator = self
111            .authenticator()
112            .cloned()
113            .map(|auth| Arc::new(auth) as Arc<dyn TransactionAuthenticator>);
114
115        let source_manager = Arc::clone(&self.source_manager);
116        let tx_executor = TransactionExecutor::new(Arc::new(self), authenticator).with_debug_mode();
117
118        maybe_await!(tx_executor.execute_transaction(
119            account_id,
120            block_num,
121            notes,
122            tx_args,
123            source_manager
124        ))
125    }
126
127    pub fn account(&self) -> &Account {
128        self.tx_inputs.account()
129    }
130
131    pub fn expected_output_notes(&self) -> &[Note] {
132        &self.expected_output_notes
133    }
134
135    pub fn tx_args(&self) -> &TransactionArgs {
136        &self.tx_args
137    }
138
139    pub fn input_notes(&self) -> &InputNotes<InputNote> {
140        self.tx_inputs.input_notes()
141    }
142
143    pub fn set_tx_args(&mut self, tx_args: TransactionArgs) {
144        self.tx_args = tx_args;
145    }
146
147    pub fn tx_inputs(&self) -> &TransactionInputs {
148        &self.tx_inputs
149    }
150
151    pub fn authenticator(&self) -> Option<&BasicAuthenticator<ChaCha20Rng>> {
152        self.authenticator.as_ref()
153    }
154
155    /// Returns the source manager used in the assembler of the transaction context builder.
156    pub fn source_manager(&self) -> Arc<dyn SourceManager> {
157        Arc::clone(&self.source_manager)
158    }
159}
160
161#[maybe_async_trait]
162impl DataStore for TransactionContext {
163    #[maybe_async]
164    fn get_transaction_inputs(
165        &self,
166        account_id: AccountId,
167        _ref_blocks: BTreeSet<BlockNumber>,
168    ) -> Result<(Account, Option<Word>, BlockHeader, PartialBlockchain), DataStoreError> {
169        assert_eq!(account_id, self.account().id());
170        let (account, seed, header, mmr, _) = self.tx_inputs.clone().into_parts();
171
172        Ok((account, seed, header, mmr))
173    }
174}
175
176impl MastForestStore for TransactionContext {
177    fn get(&self, procedure_hash: &Digest) -> Option<Arc<MastForest>> {
178        self.mast_store.get(procedure_hash)
179    }
180}