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,
6 account::AccountId,
7 assembly::SourceManager,
8 block::{BlockHeader, BlockNumber},
9 note::{NoteId, NoteScript},
10 transaction::{
11 AccountInputs, ExecutedTransaction, InputNote, InputNotes, TransactionArgs,
12 TransactionInputs, TransactionScript,
13 },
14 vm::{AdviceMap, StackOutputs},
15};
16use vm_processor::{AdviceInputs, MemAdviceProvider, Process, RecAdviceProvider};
17pub use vm_processor::{ExecutionOptions, MastForestStore};
18use winter_maybe_async::{maybe_async, maybe_await};
19
20use super::{TransactionExecutorError, TransactionHost};
21use crate::{auth::TransactionAuthenticator, host::ScriptMastForestStore};
22
23mod data_store;
24pub use data_store::DataStore;
25
26mod notes_checker;
27pub use notes_checker::{NoteConsumptionChecker, NoteInputsCheck};
28
29pub struct TransactionExecutor<'store, 'auth> {
42 data_store: &'store dyn DataStore,
43 authenticator: Option<&'auth dyn TransactionAuthenticator>,
44 exec_options: ExecutionOptions,
45}
46
47impl<'store, 'auth> TransactionExecutor<'store, 'auth> {
48 pub fn new(
54 data_store: &'store dyn DataStore,
55 authenticator: Option<&'auth dyn TransactionAuthenticator>,
56 ) -> Self {
57 const _: () = assert!(MIN_TX_EXECUTION_CYCLES <= MAX_TX_EXECUTION_CYCLES);
58
59 Self {
60 data_store,
61 authenticator,
62 exec_options: ExecutionOptions::new(
63 Some(MAX_TX_EXECUTION_CYCLES),
64 MIN_TX_EXECUTION_CYCLES,
65 false,
66 false,
67 )
68 .expect("Must not fail while max cycles is more than min trace length"),
69 }
70 }
71
72 pub fn with_options(
78 data_store: &'store dyn DataStore,
79 authenticator: Option<&'auth dyn TransactionAuthenticator>,
80 exec_options: ExecutionOptions,
81 ) -> Result<Self, TransactionExecutorError> {
82 validate_num_cycles(exec_options.max_cycles())?;
83 validate_num_cycles(exec_options.expected_cycles())?;
84
85 Ok(Self { data_store, authenticator, exec_options })
86 }
87
88 pub fn with_debug_mode(mut self) -> Self {
94 self.exec_options = self.exec_options.with_debugging(true);
95 self
96 }
97
98 pub fn with_tracing(mut self) -> Self {
104 self.exec_options = self.exec_options.with_tracing();
105 self
106 }
107
108 #[maybe_async]
136 pub fn execute_transaction(
137 &self,
138 account_id: AccountId,
139 block_ref: BlockNumber,
140 notes: InputNotes<InputNote>,
141 tx_args: TransactionArgs,
142 source_manager: Arc<dyn SourceManager>,
143 ) -> Result<ExecutedTransaction, TransactionExecutorError> {
144 let mut ref_blocks = validate_input_notes(¬es, block_ref)?;
145 ref_blocks.insert(block_ref);
146
147 let (account, seed, ref_block, mmr) =
148 maybe_await!(self.data_store.get_transaction_inputs(account_id, ref_blocks))
149 .map_err(TransactionExecutorError::FetchTransactionInputsFailed)?;
150
151 validate_account_inputs(&tx_args, &ref_block)?;
152
153 let tx_inputs = TransactionInputs::new(account, seed, ref_block, mmr, notes)
154 .map_err(TransactionExecutorError::InvalidTransactionInputs)?;
155
156 let (stack_inputs, advice_inputs) =
157 TransactionKernel::prepare_inputs(&tx_inputs, &tx_args, None);
158
159 let advice_recorder = RecAdviceProvider::from(advice_inputs.into_inner());
160
161 let script_mast_store = ScriptMastForestStore::new(
162 tx_args.tx_script(),
163 tx_inputs.input_notes().iter().map(|n| n.note().script()),
164 );
165
166 let mut host = TransactionHost::new(
167 &tx_inputs.account().into(),
168 advice_recorder,
169 self.data_store,
170 script_mast_store,
171 self.authenticator,
172 tx_args.foreign_account_code_commitments(),
173 )
174 .map_err(TransactionExecutorError::TransactionHostCreationFailed)?;
175
176 let trace = vm_processor::execute(
178 &TransactionKernel::main(),
179 stack_inputs,
180 &mut host,
181 self.exec_options,
182 source_manager,
183 )
184 .map_err(TransactionExecutorError::TransactionProgramExecutionFailed)?;
185
186 build_executed_transaction(tx_args, tx_inputs, trace.stack_outputs().clone(), host)
187 }
188
189 #[maybe_async]
208 pub fn execute_tx_view_script(
209 &self,
210 account_id: AccountId,
211 block_ref: BlockNumber,
212 tx_script: TransactionScript,
213 advice_inputs: AdviceInputs,
214 foreign_account_inputs: Vec<AccountInputs>,
215 source_manager: Arc<dyn SourceManager>,
216 ) -> Result<[Felt; 16], TransactionExecutorError> {
217 let ref_blocks = [block_ref].into_iter().collect();
218 let (account, seed, ref_block, mmr) =
219 maybe_await!(self.data_store.get_transaction_inputs(account_id, ref_blocks))
220 .map_err(TransactionExecutorError::FetchTransactionInputsFailed)?;
221 let tx_args = TransactionArgs::new(Default::default(), foreign_account_inputs)
222 .with_tx_script(tx_script);
223
224 validate_account_inputs(&tx_args, &ref_block)?;
225
226 let tx_inputs = TransactionInputs::new(account, seed, ref_block, mmr, Default::default())
227 .map_err(TransactionExecutorError::InvalidTransactionInputs)?;
228
229 let (stack_inputs, advice_inputs) =
230 TransactionKernel::prepare_inputs(&tx_inputs, &tx_args, Some(advice_inputs));
231 let advice_recorder = RecAdviceProvider::from(advice_inputs.into_inner());
232
233 let scripts_mast_store =
234 ScriptMastForestStore::new(tx_args.tx_script(), core::iter::empty::<&NoteScript>());
235
236 let mut host = TransactionHost::new(
237 &tx_inputs.account().into(),
238 advice_recorder,
239 self.data_store,
240 scripts_mast_store,
241 self.authenticator,
242 tx_args.foreign_account_code_commitments(),
243 )
244 .map_err(TransactionExecutorError::TransactionHostCreationFailed)?;
245
246 let mut process = Process::new(
247 TransactionKernel::tx_script_main().kernel().clone(),
248 stack_inputs,
249 self.exec_options,
250 )
251 .with_source_manager(source_manager);
252 let stack_outputs = process
253 .execute(&TransactionKernel::tx_script_main(), &mut host)
254 .map_err(TransactionExecutorError::TransactionProgramExecutionFailed)?;
255
256 Ok(*stack_outputs)
257 }
258
259 #[maybe_async]
279 pub(crate) fn try_execute_notes(
280 &self,
281 account_id: AccountId,
282 block_ref: BlockNumber,
283 notes: InputNotes<InputNote>,
284 tx_args: TransactionArgs,
285 source_manager: Arc<dyn SourceManager>,
286 ) -> Result<NoteAccountExecution, TransactionExecutorError> {
287 let mut ref_blocks = validate_input_notes(¬es, block_ref)?;
288 ref_blocks.insert(block_ref);
289
290 let (account, seed, ref_block, mmr) =
291 maybe_await!(self.data_store.get_transaction_inputs(account_id, ref_blocks))
292 .map_err(TransactionExecutorError::FetchTransactionInputsFailed)?;
293
294 validate_account_inputs(&tx_args, &ref_block)?;
295
296 let tx_inputs = TransactionInputs::new(account, seed, ref_block, mmr, notes)
297 .map_err(TransactionExecutorError::InvalidTransactionInputs)?;
298
299 let (stack_inputs, advice_inputs) =
300 TransactionKernel::prepare_inputs(&tx_inputs, &tx_args, None);
301
302 let advice_provider = MemAdviceProvider::from(advice_inputs.into_inner());
303
304 let scripts_mast_store = ScriptMastForestStore::new(
305 tx_args.tx_script(),
306 tx_inputs.input_notes().iter().map(|n| n.note().script()),
307 );
308
309 let mut host = TransactionHost::new(
310 &tx_inputs.account().into(),
311 advice_provider,
312 self.data_store,
313 scripts_mast_store,
314 self.authenticator,
315 tx_args.foreign_account_code_commitments(),
316 )
317 .map_err(TransactionExecutorError::TransactionHostCreationFailed)?;
318
319 let result = vm_processor::execute(
321 &TransactionKernel::main(),
322 stack_inputs,
323 &mut host,
324 self.exec_options,
325 source_manager,
326 )
327 .map_err(TransactionExecutorError::TransactionProgramExecutionFailed);
328
329 match result {
330 Ok(_) => Ok(NoteAccountExecution::Success),
331 Err(tx_execution_error) => {
332 let notes = host.tx_progress().note_execution();
333
334 if notes.is_empty() {
337 return Err(tx_execution_error);
338 }
339
340 let ((last_note, last_note_interval), success_notes) = notes
341 .split_last()
342 .expect("notes vector should not be empty because we just checked");
343
344 if last_note_interval.end().is_some() {
347 return Err(tx_execution_error);
348 }
349
350 Ok(NoteAccountExecution::Failure {
351 failed_note_id: *last_note,
352 successful_notes: success_notes.iter().map(|(note, _)| *note).collect(),
353 error: Some(tx_execution_error),
354 })
355 },
356 }
357 }
358}
359
360fn build_executed_transaction(
365 tx_args: TransactionArgs,
366 tx_inputs: TransactionInputs,
367 stack_outputs: StackOutputs,
368 host: TransactionHost<RecAdviceProvider>,
369) -> Result<ExecutedTransaction, TransactionExecutorError> {
370 let (advice_recorder, account_delta, output_notes, generated_signatures, tx_progress) =
371 host.into_parts();
372
373 let (mut advice_witness, _, map, _store) = advice_recorder.finalize();
374
375 let advice_map = AdviceMap::from(map);
376 let tx_outputs =
377 TransactionKernel::from_transaction_parts(&stack_outputs, &advice_map, output_notes)
378 .map_err(TransactionExecutorError::TransactionOutputConstructionFailed)?;
379
380 let initial_account = tx_inputs.account();
381 let final_account = &tx_outputs.account;
382
383 let host_delta_commitment = account_delta.commitment();
384 if tx_outputs.account_delta_commitment != host_delta_commitment {
385 return Err(TransactionExecutorError::InconsistentAccountDeltaCommitment {
386 in_kernel_commitment: tx_outputs.account_delta_commitment,
387 host_commitment: host_delta_commitment,
388 });
389 }
390
391 if initial_account.id() != final_account.id() {
392 return Err(TransactionExecutorError::InconsistentAccountId {
393 input_id: initial_account.id(),
394 output_id: final_account.id(),
395 });
396 }
397
398 let nonce_delta = final_account.nonce() - initial_account.nonce();
400 if nonce_delta != account_delta.nonce_delta() {
401 return Err(TransactionExecutorError::InconsistentAccountNonceDelta {
402 expected: nonce_delta,
403 actual: account_delta.nonce_delta(),
404 });
405 }
406
407 advice_witness.extend_map(generated_signatures);
409
410 Ok(ExecutedTransaction::new(
411 tx_inputs,
412 tx_outputs,
413 account_delta,
414 tx_args,
415 advice_witness,
416 tx_progress.into(),
417 ))
418}
419
420fn validate_account_inputs(
422 tx_args: &TransactionArgs,
423 ref_block: &BlockHeader,
424) -> Result<(), TransactionExecutorError> {
425 for foreign_account in tx_args.foreign_account_inputs() {
427 let computed_account_root = foreign_account.compute_account_root().map_err(|err| {
428 TransactionExecutorError::InvalidAccountWitness(foreign_account.id(), err)
429 })?;
430 if computed_account_root != ref_block.account_root() {
431 return Err(TransactionExecutorError::ForeignAccountNotAnchoredInReference(
432 foreign_account.id(),
433 ));
434 }
435 }
436 Ok(())
437}
438
439fn validate_input_notes(
443 notes: &InputNotes<InputNote>,
444 block_ref: BlockNumber,
445) -> Result<BTreeSet<BlockNumber>, TransactionExecutorError> {
446 let mut ref_blocks: BTreeSet<BlockNumber> = BTreeSet::new();
449 for note in notes.iter() {
450 if let Some(location) = note.location() {
451 if location.block_num() > block_ref {
452 return Err(TransactionExecutorError::NoteBlockPastReferenceBlock(
453 note.id(),
454 block_ref,
455 ));
456 }
457 ref_blocks.insert(location.block_num());
458 }
459 }
460
461 Ok(ref_blocks)
462}
463
464fn validate_num_cycles(num_cycles: u32) -> Result<(), TransactionExecutorError> {
466 if !(MIN_TX_EXECUTION_CYCLES..=MAX_TX_EXECUTION_CYCLES).contains(&num_cycles) {
467 Err(TransactionExecutorError::InvalidExecutionOptionsCycles {
468 min_cycles: MIN_TX_EXECUTION_CYCLES,
469 max_cycles: MAX_TX_EXECUTION_CYCLES,
470 actual: num_cycles,
471 })
472 } else {
473 Ok(())
474 }
475}
476
477#[derive(Debug)]
486pub enum NoteAccountExecution {
487 Success,
488 Failure {
489 failed_note_id: NoteId,
490 successful_notes: Vec<NoteId>,
491 error: Option<TransactionExecutorError>,
492 },
493}