miden_testing/tx_context/
context.rs

1use alloc::borrow::ToOwned;
2use alloc::collections::BTreeSet;
3use alloc::rc::Rc;
4use alloc::sync::Arc;
5use alloc::vec::Vec;
6
7use miden_lib::transaction::TransactionKernel;
8use miden_objects::account::{Account, AccountId};
9use miden_objects::assembly::debuginfo::{SourceLanguage, Uri};
10use miden_objects::assembly::{SourceManager, SourceManagerSync};
11use miden_objects::block::{BlockHeader, BlockNumber};
12use miden_objects::note::Note;
13use miden_objects::transaction::{
14    ExecutedTransaction,
15    InputNote,
16    InputNotes,
17    PartialBlockchain,
18    TransactionArgs,
19    TransactionInputs,
20};
21use miden_processor::{
22    AdviceInputs,
23    ExecutionError,
24    FutureMaybeSend,
25    MastForest,
26    MastForestStore,
27    Process,
28    Word,
29};
30use miden_tx::auth::BasicAuthenticator;
31use miden_tx::{
32    DataStore,
33    DataStoreError,
34    TransactionExecutor,
35    TransactionExecutorError,
36    TransactionMastStore,
37};
38use rand_chacha::ChaCha20Rng;
39
40use crate::MockHost;
41use crate::executor::CodeExecutor;
42use crate::tx_context::builder::MockAuthenticator;
43
44// TRANSACTION CONTEXT
45// ================================================================================================
46
47/// Represents all needed data for executing a transaction, or arbitrary code.
48///
49/// It implements [`DataStore`], so transactions may be executed with
50/// [TransactionExecutor](miden_tx::TransactionExecutor)
51pub struct TransactionContext {
52    pub(super) expected_output_notes: Vec<Note>,
53    pub(super) tx_args: TransactionArgs,
54    pub(super) tx_inputs: TransactionInputs,
55    pub(super) mast_store: TransactionMastStore,
56    pub(super) advice_inputs: AdviceInputs,
57    pub(super) authenticator: Option<MockAuthenticator>,
58    pub(super) source_manager: Arc<dyn SourceManagerSync>,
59}
60
61impl TransactionContext {
62    /// Executes arbitrary code within the context of a mocked transaction environment and returns
63    /// the resulting [Process].
64    ///
65    /// The code is compiled with the assembler returned by
66    /// [`TransactionKernel::with_mock_libraries`] and executed with advice inputs constructed from
67    /// the data stored in the context. The program is run on a [`MockHost`] which is loaded with
68    /// the procedures exposed by the transaction kernel, and also individual kernel functions (not
69    /// normally exposed).
70    ///
71    /// To improve the error message quality, convert the returned [`ExecutionError`] into a
72    /// [`Report`](miden_objects::assembly::diagnostics::Report).
73    ///
74    /// # Errors
75    ///
76    /// Returns an error if the assembly or execution of the provided code fails.
77    ///
78    /// # Panics
79    ///
80    /// - If the provided `code` is not a valid program.
81    pub fn execute_code(&self, code: &str) -> Result<Process, ExecutionError> {
82        let (stack_inputs, advice_inputs) = TransactionKernel::prepare_inputs(
83            &self.tx_inputs,
84            &self.tx_args,
85            Some(self.advice_inputs.clone()),
86        )
87        .expect("error initializing transaction inputs");
88
89        // Virtual file name should be unique.
90        let virtual_source_file = self.source_manager.load(
91            SourceLanguage::Masm,
92            Uri::new("_tx_context_code"),
93            code.to_owned(),
94        );
95
96        let assembler = TransactionKernel::with_mock_libraries(self.source_manager.clone())
97            .with_debug_mode(true);
98        let program = assembler
99            .with_debug_mode(true)
100            .assemble_program(virtual_source_file)
101            .expect("code was not well formed");
102
103        let mast_store = Rc::new(TransactionMastStore::new());
104
105        mast_store.insert(program.mast_forest().clone());
106        mast_store.insert(TransactionKernel::library().mast_forest().clone());
107        mast_store.load_account_code(self.account().code());
108        for acc_inputs in self.tx_args.foreign_account_inputs() {
109            mast_store.load_account_code(acc_inputs.code());
110        }
111
112        let advice_inputs = advice_inputs.into_advice_inputs();
113        CodeExecutor::new(
114            MockHost::new(
115                self.tx_inputs.account().into(),
116                &advice_inputs,
117                mast_store,
118                self.tx_args.to_foreign_account_code_commitments(),
119            )
120            .with_source_manager(self.source_manager()),
121        )
122        .stack_inputs(stack_inputs)
123        .extend_advice_inputs(advice_inputs)
124        .execute_program(program)
125    }
126
127    /// Executes the transaction through a [TransactionExecutor]
128    pub async fn execute(self) -> Result<ExecutedTransaction, TransactionExecutorError> {
129        let account_id = self.account().id();
130        let block_num = self.tx_inputs().block_header().block_num();
131        let notes = self.tx_inputs().input_notes().clone();
132        let tx_args = self.tx_args().clone();
133
134        let mut tx_executor = TransactionExecutor::new(&self)
135            .with_source_manager(self.source_manager.clone())
136            .with_debug_mode();
137        if let Some(authenticator) = self.authenticator() {
138            tx_executor = tx_executor.with_authenticator(authenticator);
139        }
140
141        tx_executor.execute_transaction(account_id, block_num, notes, tx_args).await
142    }
143
144    /// Executes the transaction through a [TransactionExecutor]
145    ///
146    /// TODO: This is a temporary workaround to avoid having to update each test to use tokio::test.
147    /// Eventually we should get rid of this method and use tokio::test + execute, but for the POC
148    /// stage this is easier.
149    pub fn execute_blocking(self) -> Result<ExecutedTransaction, TransactionExecutorError> {
150        tokio::runtime::Builder::new_current_thread()
151            .build()
152            .unwrap()
153            .block_on(self.execute())
154    }
155
156    pub fn account(&self) -> &Account {
157        self.tx_inputs.account()
158    }
159
160    pub fn expected_output_notes(&self) -> &[Note] {
161        &self.expected_output_notes
162    }
163
164    pub fn tx_args(&self) -> &TransactionArgs {
165        &self.tx_args
166    }
167
168    pub fn input_notes(&self) -> &InputNotes<InputNote> {
169        self.tx_inputs.input_notes()
170    }
171
172    pub fn set_tx_args(&mut self, tx_args: TransactionArgs) {
173        self.tx_args = tx_args;
174    }
175
176    pub fn tx_inputs(&self) -> &TransactionInputs {
177        &self.tx_inputs
178    }
179
180    pub fn authenticator(&self) -> Option<&BasicAuthenticator<ChaCha20Rng>> {
181        self.authenticator.as_ref()
182    }
183
184    /// Returns the source manager used in the assembler of the transaction context builder.
185    pub fn source_manager(&self) -> Arc<dyn SourceManagerSync> {
186        Arc::clone(&self.source_manager)
187    }
188}
189
190impl DataStore for TransactionContext {
191    fn get_transaction_inputs(
192        &self,
193        account_id: AccountId,
194        _ref_blocks: BTreeSet<BlockNumber>,
195    ) -> impl FutureMaybeSend<
196        Result<(Account, Option<Word>, BlockHeader, PartialBlockchain), DataStoreError>,
197    > {
198        assert_eq!(account_id, self.account().id());
199        let (account, seed, header, mmr, _) = self.tx_inputs.clone().into_parts();
200        async move { Ok((account, seed, header, mmr)) }
201    }
202}
203
204impl MastForestStore for TransactionContext {
205    fn get(&self, procedure_hash: &Word) -> Option<Arc<MastForest>> {
206        self.mast_store.get(procedure_hash)
207    }
208}