miden_testing/tx_context/
context.rs

1#[cfg(feature = "async")]
2use alloc::boxed::Box;
3use alloc::{borrow::ToOwned, 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    /// To improve the error message quality, convert the returned [`ExecutionError`] into a
55    /// [`Report`](miden_objects::assembly::diagnostics::Report).
56    ///
57    /// # Errors
58    ///
59    /// Returns an error if the assembly or execution of the provided code fails.
60    ///
61    /// # Panics
62    ///
63    /// - If the provided `code` is not a valid program.
64    pub fn execute_code_with_assembler(
65        &self,
66        code: &str,
67        assembler: Assembler,
68    ) -> Result<Process, ExecutionError> {
69        let (stack_inputs, advice_inputs) = TransactionKernel::prepare_inputs(
70            &self.tx_inputs,
71            &self.tx_args,
72            Some(self.advice_inputs.clone()),
73        );
74
75        let test_lib = TransactionKernel::kernel_as_library();
76
77        let source_manager = assembler.source_manager();
78
79        // Virtual file name should be unique.
80        let virtual_source_file = source_manager.load("_tx_context_code", code.to_owned());
81
82        let program = assembler
83            .with_debug_mode(true)
84            .assemble_program(virtual_source_file)
85            .expect("code was not well formed");
86
87        let mast_store = Rc::new(TransactionMastStore::new());
88
89        mast_store.insert(program.mast_forest().clone());
90        mast_store.insert(test_lib.mast_forest().clone());
91        mast_store.load_account_code(self.account().code());
92        for acc_inputs in self.tx_args.foreign_account_inputs() {
93            mast_store.load_account_code(acc_inputs.code());
94        }
95
96        CodeExecutor::new(MockHost::new(
97            self.tx_inputs.account().into(),
98            advice_inputs,
99            mast_store,
100            self.tx_args.foreign_account_code_commitments(),
101        ))
102        .stack_inputs(stack_inputs)
103        .execute_program(program, source_manager)
104    }
105
106    /// Executes arbitrary code with a testing assembler ([TransactionKernel::testing_assembler()]).
107    ///
108    /// For more information, see the docs for [TransactionContext::execute_code_with_assembler()].
109    pub fn execute_code(&self, code: &str) -> Result<Process, ExecutionError> {
110        let assembler = TransactionKernel::testing_assembler();
111        self.execute_code_with_assembler(code, assembler)
112    }
113
114    /// Executes the transaction through a [TransactionExecutor]
115    #[allow(clippy::arc_with_non_send_sync)]
116    #[maybe_async]
117    pub fn execute(self) -> Result<ExecutedTransaction, TransactionExecutorError> {
118        let account_id = self.account().id();
119        let block_num = self.tx_inputs().block_header().block_num();
120        let notes = self.tx_inputs().input_notes().clone();
121        let tx_args = self.tx_args().clone();
122        let authenticator = self.authenticator().map(|x| x as &dyn TransactionAuthenticator);
123
124        let source_manager = Arc::clone(&self.source_manager);
125        let tx_executor = TransactionExecutor::new(&self, authenticator).with_debug_mode();
126
127        maybe_await!(tx_executor.execute_transaction(
128            account_id,
129            block_num,
130            notes,
131            tx_args,
132            source_manager
133        ))
134    }
135
136    pub fn account(&self) -> &Account {
137        self.tx_inputs.account()
138    }
139
140    pub fn expected_output_notes(&self) -> &[Note] {
141        &self.expected_output_notes
142    }
143
144    pub fn tx_args(&self) -> &TransactionArgs {
145        &self.tx_args
146    }
147
148    pub fn input_notes(&self) -> &InputNotes<InputNote> {
149        self.tx_inputs.input_notes()
150    }
151
152    pub fn set_tx_args(&mut self, tx_args: TransactionArgs) {
153        self.tx_args = tx_args;
154    }
155
156    pub fn tx_inputs(&self) -> &TransactionInputs {
157        &self.tx_inputs
158    }
159
160    pub fn authenticator(&self) -> Option<&BasicAuthenticator<ChaCha20Rng>> {
161        self.authenticator.as_ref()
162    }
163
164    /// Returns the source manager used in the assembler of the transaction context builder.
165    pub fn source_manager(&self) -> Arc<dyn SourceManager> {
166        Arc::clone(&self.source_manager)
167    }
168}
169
170#[maybe_async_trait]
171impl DataStore for TransactionContext {
172    #[maybe_async]
173    fn get_transaction_inputs(
174        &self,
175        account_id: AccountId,
176        _ref_blocks: BTreeSet<BlockNumber>,
177    ) -> Result<(Account, Option<Word>, BlockHeader, PartialBlockchain), DataStoreError> {
178        assert_eq!(account_id, self.account().id());
179        let (account, seed, header, mmr, _) = self.tx_inputs.clone().into_parts();
180
181        Ok((account, seed, header, mmr))
182    }
183}
184
185impl MastForestStore for TransactionContext {
186    fn get(&self, procedure_hash: &Digest) -> Option<Arc<MastForest>> {
187        self.mast_store.get(procedure_hash)
188    }
189}