miden_tx/executor/
mod.rs

1use alloc::{collections::BTreeSet, sync::Arc, vec::Vec};
2
3use miden_lib::transaction::TransactionKernel;
4use miden_objects::{
5    Felt, MAX_TX_EXECUTION_CYCLES, MIN_TX_EXECUTION_CYCLES, ZERO,
6    account::{AccountCode, AccountId},
7    assembly::Library,
8    block::BlockNumber,
9    note::NoteId,
10    transaction::{ExecutedTransaction, TransactionArgs, TransactionInputs, TransactionScript},
11    vm::StackOutputs,
12};
13use vm_processor::{AdviceInputs, ExecutionOptions, Process, RecAdviceProvider};
14use winter_maybe_async::{maybe_async, maybe_await};
15
16use super::{TransactionExecutorError, TransactionHost};
17use crate::auth::TransactionAuthenticator;
18
19mod data_store;
20pub use data_store::DataStore;
21
22mod mast_store;
23pub use mast_store::TransactionMastStore;
24
25// TRANSACTION EXECUTOR
26// ================================================================================================
27
28/// The transaction executor is responsible for executing Miden rollup transactions.
29///
30/// Transaction execution consists of the following steps:
31/// - Fetch the data required to execute a transaction from the [DataStore].
32/// - Load the code associated with the transaction into the [TransactionMastStore].
33/// - Execute the transaction program and create an [ExecutedTransaction].
34///
35/// The transaction executor uses dynamic dispatch with trait objects for the [DataStore] and
36/// [TransactionAuthenticator], allowing it to be used with different backend implementations.
37pub struct TransactionExecutor {
38    data_store: Arc<dyn DataStore>,
39    mast_store: Arc<TransactionMastStore>,
40    authenticator: Option<Arc<dyn TransactionAuthenticator>>,
41    /// Holds the code of all accounts loaded into this transaction executor via the
42    /// [Self::load_account_code()] method.
43    account_codes: BTreeSet<AccountCode>,
44    exec_options: ExecutionOptions,
45}
46
47impl TransactionExecutor {
48    // CONSTRUCTOR
49    // --------------------------------------------------------------------------------------------
50
51    /// Creates a new [TransactionExecutor] instance with the specified [DataStore] and
52    /// [TransactionAuthenticator].
53    pub fn new(
54        data_store: Arc<dyn DataStore>,
55        authenticator: Option<Arc<dyn TransactionAuthenticator>>,
56    ) -> Self {
57        const _: () = assert!(MIN_TX_EXECUTION_CYCLES <= MAX_TX_EXECUTION_CYCLES);
58
59        Self {
60            data_store,
61            mast_store: Arc::new(TransactionMastStore::new()),
62            authenticator,
63            exec_options: ExecutionOptions::new(
64                Some(MAX_TX_EXECUTION_CYCLES),
65                MIN_TX_EXECUTION_CYCLES,
66                false,
67                false,
68            )
69            .expect("Must not fail while max cycles is more than min trace length"),
70            account_codes: BTreeSet::new(),
71        }
72    }
73
74    /// Puts the [TransactionExecutor] into debug mode.
75    ///
76    /// When transaction executor is in debug mode, all transaction-related code (note scripts,
77    /// account code) will be compiled and executed in debug mode. This will ensure that all debug
78    /// instructions present in the original source code are executed.
79    pub fn with_debug_mode(mut self) -> Self {
80        self.exec_options = self.exec_options.with_debugging();
81        self
82    }
83
84    /// Enables tracing for the created instance of [TransactionExecutor].
85    ///
86    /// When tracing is enabled, the executor will receive tracing events as various stages of the
87    /// transaction kernel complete. This enables collecting basic stats about how long different
88    /// stages of transaction execution take.
89    pub fn with_tracing(mut self) -> Self {
90        self.exec_options = self.exec_options.with_tracing();
91        self
92    }
93
94    // STATE MUTATORS
95    // --------------------------------------------------------------------------------------------
96
97    /// Loads the provided account code into the internal MAST forest store and adds the commitment
98    /// of the provided code to the commitments set.
99    pub fn load_account_code(&mut self, code: &AccountCode) {
100        // load the code mast forest to the mast store
101        self.mast_store.load_account_code(code);
102
103        // store the commitment of the foreign account code in the set
104        self.account_codes.insert(code.clone());
105    }
106
107    /// Loads the provided library code into the internal MAST forest store.
108    ///
109    /// TODO: this is a work-around to support accounts which were complied with user-defined
110    /// libraries. Once Miden Assembler supports library vendoring, this should go away.
111    pub fn load_library(&mut self, library: &Library) {
112        self.mast_store.insert(library.mast_forest().clone());
113    }
114
115    // TRANSACTION EXECUTION
116    // --------------------------------------------------------------------------------------------
117
118    /// Prepares and executes a transaction specified by the provided arguments and returns an
119    /// [ExecutedTransaction].
120    ///
121    /// The method first fetches the data required to execute the transaction from the [DataStore]
122    /// and compile the transaction into an executable program. Then, it executes the transaction
123    /// program and creates an [ExecutedTransaction] object.
124    ///
125    /// # Errors:
126    /// Returns an error if:
127    /// - If required data can not be fetched from the [DataStore].
128    #[maybe_async]
129    pub fn execute_transaction(
130        &self,
131        account_id: AccountId,
132        block_ref: BlockNumber,
133        notes: &[NoteId],
134        tx_args: TransactionArgs,
135    ) -> Result<ExecutedTransaction, TransactionExecutorError> {
136        let tx_inputs =
137            maybe_await!(self.data_store.get_transaction_inputs(account_id, block_ref, notes))
138                .map_err(TransactionExecutorError::FetchTransactionInputsFailed)?;
139
140        let (stack_inputs, advice_inputs) =
141            TransactionKernel::prepare_inputs(&tx_inputs, &tx_args, None);
142        let advice_recorder: RecAdviceProvider = advice_inputs.into();
143
144        // load note script MAST into the MAST store
145        self.mast_store.load_transaction_code(&tx_inputs, &tx_args);
146
147        let mut host = TransactionHost::new(
148            tx_inputs.account().into(),
149            advice_recorder,
150            self.mast_store.clone(),
151            self.authenticator.clone(),
152            self.account_codes.iter().map(|code| code.commitment()).collect(),
153        )
154        .map_err(TransactionExecutorError::TransactionHostCreationFailed)?;
155
156        // execute the transaction kernel
157        let result = vm_processor::execute(
158            &TransactionKernel::main(),
159            stack_inputs,
160            &mut host,
161            self.exec_options,
162        )
163        .map_err(TransactionExecutorError::TransactionProgramExecutionFailed)?;
164
165        // Attempt to retrieve used account codes based on the advice map
166        let account_codes = self
167            .account_codes
168            .iter()
169            .filter_map(|code| {
170                tx_args
171                    .advice_inputs()
172                    .mapped_values(&code.commitment())
173                    .and(Some(code.clone()))
174            })
175            .collect();
176
177        build_executed_transaction(
178            tx_args,
179            tx_inputs,
180            result.stack_outputs().clone(),
181            host,
182            account_codes,
183        )
184    }
185
186    // SCRIPT EXECUTION
187    // --------------------------------------------------------------------------------------------
188
189    /// Executes an arbitrary script against the given account and returns the stack state at the
190    /// end of execution.
191    ///
192    /// # Errors:
193    /// Returns an error if:
194    /// - If required data can not be fetched from the [DataStore].
195    /// - If the transaction host can not be created from the provided values.
196    /// - If the execution of the provided program fails.
197    #[maybe_async]
198    pub fn execute_tx_view_script(
199        &self,
200        account_id: AccountId,
201        block_ref: BlockNumber,
202        tx_script: TransactionScript,
203        advice_inputs: AdviceInputs,
204    ) -> Result<[Felt; 16], TransactionExecutorError> {
205        let tx_inputs =
206            maybe_await!(self.data_store.get_transaction_inputs(account_id, block_ref, &[]))
207                .map_err(TransactionExecutorError::FetchTransactionInputsFailed)?;
208
209        let tx_args = TransactionArgs::new(Some(tx_script.clone()), None, Default::default());
210
211        let (stack_inputs, advice_inputs) =
212            TransactionKernel::prepare_inputs(&tx_inputs, &tx_args, Some(advice_inputs));
213        let advice_recorder: RecAdviceProvider = advice_inputs.into();
214
215        // load transaction script MAST into the MAST store
216        self.mast_store.load_transaction_code(&tx_inputs, &tx_args);
217
218        let mut host = TransactionHost::new(
219            tx_inputs.account().into(),
220            advice_recorder,
221            self.mast_store.clone(),
222            self.authenticator.clone(),
223            self.account_codes.iter().map(|code| code.commitment()).collect(),
224        )
225        .map_err(TransactionExecutorError::TransactionHostCreationFailed)?;
226
227        let mut process = Process::new(
228            TransactionKernel::tx_script_main().kernel().clone(),
229            stack_inputs,
230            self.exec_options,
231        );
232        let stack_outputs = process
233            .execute(&TransactionKernel::tx_script_main(), &mut host)
234            .map_err(TransactionExecutorError::TransactionProgramExecutionFailed)?;
235
236        Ok(*stack_outputs)
237    }
238}
239
240// HELPER FUNCTIONS
241// ================================================================================================
242
243/// Creates a new [ExecutedTransaction] from the provided data.
244fn build_executed_transaction(
245    tx_args: TransactionArgs,
246    tx_inputs: TransactionInputs,
247    stack_outputs: StackOutputs,
248    host: TransactionHost<RecAdviceProvider>,
249    account_codes: Vec<AccountCode>,
250) -> Result<ExecutedTransaction, TransactionExecutorError> {
251    let (advice_recorder, account_delta, output_notes, generated_signatures, tx_progress) =
252        host.into_parts();
253
254    let (mut advice_witness, _, map, _store) = advice_recorder.finalize();
255
256    let tx_outputs =
257        TransactionKernel::from_transaction_parts(&stack_outputs, &map.into(), output_notes)
258            .map_err(TransactionExecutorError::TransactionOutputConstructionFailed)?;
259
260    let final_account = &tx_outputs.account;
261
262    let initial_account = tx_inputs.account();
263
264    if initial_account.id() != final_account.id() {
265        return Err(TransactionExecutorError::InconsistentAccountId {
266            input_id: initial_account.id(),
267            output_id: final_account.id(),
268        });
269    }
270
271    // make sure nonce delta was computed correctly
272    let nonce_delta = final_account.nonce() - initial_account.nonce();
273    if nonce_delta == ZERO {
274        if account_delta.nonce().is_some() {
275            return Err(TransactionExecutorError::InconsistentAccountNonceDelta {
276                expected: None,
277                actual: account_delta.nonce(),
278            });
279        }
280    } else if final_account.nonce() != account_delta.nonce().unwrap_or_default() {
281        return Err(TransactionExecutorError::InconsistentAccountNonceDelta {
282            expected: Some(final_account.nonce()),
283            actual: account_delta.nonce(),
284        });
285    }
286
287    // introduce generated signatures into the witness inputs
288    advice_witness.extend_map(generated_signatures);
289
290    Ok(ExecutedTransaction::new(
291        tx_inputs,
292        tx_outputs,
293        account_codes,
294        account_delta,
295        tx_args,
296        advice_witness,
297        tx_progress.into(),
298    ))
299}