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
25pub struct TransactionExecutor {
38 data_store: Arc<dyn DataStore>,
39 mast_store: Arc<TransactionMastStore>,
40 authenticator: Option<Arc<dyn TransactionAuthenticator>>,
41 account_codes: BTreeSet<AccountCode>,
44 exec_options: ExecutionOptions,
45}
46
47impl TransactionExecutor {
48 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 pub fn with_debug_mode(mut self) -> Self {
80 self.exec_options = self.exec_options.with_debugging();
81 self
82 }
83
84 pub fn with_tracing(mut self) -> Self {
90 self.exec_options = self.exec_options.with_tracing();
91 self
92 }
93
94 pub fn load_account_code(&mut self, code: &AccountCode) {
100 self.mast_store.load_account_code(code);
102
103 self.account_codes.insert(code.clone());
105 }
106
107 pub fn load_library(&mut self, library: &Library) {
112 self.mast_store.insert(library.mast_forest().clone());
113 }
114
115 #[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 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 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 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 #[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 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
240fn 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 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 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}