1use alloc::boxed::Box;
2use alloc::collections::BTreeSet;
3use alloc::sync::Arc;
4use core::marker::PhantomData;
5
6use miden_processor::advice::AdviceInputs;
7use miden_processor::{ExecutionError, FastProcessor, StackInputs};
8pub use miden_processor::{ExecutionOptions, MastForestStore, ProgramExecutor};
9use miden_protocol::account::AccountId;
10use miden_protocol::assembly::DefaultSourceManager;
11use miden_protocol::assembly::debuginfo::SourceManagerSync;
12use miden_protocol::asset::{Asset, AssetId};
13use miden_protocol::block::BlockNumber;
14use miden_protocol::transaction::{
15 ExecutedTransaction,
16 InputNote,
17 InputNotes,
18 TransactionArgs,
19 TransactionInputs,
20 TransactionKernel,
21 TransactionScript,
22};
23use miden_protocol::vm::StackOutputs;
24use miden_protocol::{Felt, MAX_TX_EXECUTION_CYCLES, MIN_TX_EXECUTION_CYCLES};
25
26use super::TransactionExecutorError;
27use crate::auth::TransactionAuthenticator;
28use crate::errors::TransactionKernelError;
29use crate::host::{AccountProcedureIndexMap, ScriptMastForestStore};
30
31mod exec_host;
32pub use exec_host::TransactionExecutorHost;
33
34mod data_store;
35pub use data_store::DataStore;
36
37mod notes_checker;
38pub use notes_checker::{
39 FailedNote,
40 MAX_NUM_CHECKER_NOTES,
41 NoteConsumptionChecker,
42 NoteConsumptionInfo,
43 NoteFailure,
44 SuccessfulNote,
45};
46
47pub struct TransactionExecutor<
60 'store,
61 'auth,
62 STORE: 'store,
63 AUTH: 'auth,
64 EXEC: ProgramExecutor = FastProcessor,
65> {
66 data_store: &'store STORE,
67 authenticator: Option<&'auth AUTH>,
68 source_manager: Arc<dyn SourceManagerSync>,
69 exec_options: ExecutionOptions,
70 _executor: PhantomData<EXEC>,
71}
72
73impl<'store, 'auth, STORE, AUTH> TransactionExecutor<'store, 'auth, STORE, AUTH>
74where
75 STORE: DataStore + 'store + Sync,
76 AUTH: TransactionAuthenticator + 'auth + Sync,
77{
78 pub fn new(data_store: &'store STORE) -> Self {
90 const _: () = assert!(MIN_TX_EXECUTION_CYCLES <= MAX_TX_EXECUTION_CYCLES);
91 Self {
92 data_store,
93 authenticator: None,
94 source_manager: Arc::new(DefaultSourceManager::default()),
95 exec_options: ExecutionOptions::new(
96 Some(MAX_TX_EXECUTION_CYCLES),
97 MIN_TX_EXECUTION_CYCLES,
98 ExecutionOptions::DEFAULT_CORE_TRACE_FRAGMENT_SIZE,
99 )
100 .expect("Must not fail while max cycles is more than min trace length"),
101 _executor: PhantomData,
102 }
103 }
104}
105
106impl<'store, 'auth, STORE, AUTH, EXEC> TransactionExecutor<'store, 'auth, STORE, AUTH, EXEC>
107where
108 STORE: DataStore + 'store + Sync,
109 AUTH: TransactionAuthenticator + 'auth + Sync,
110 EXEC: ProgramExecutor,
111{
112 pub fn with_program_executor<EXEC2: ProgramExecutor>(
117 self,
118 ) -> TransactionExecutor<'store, 'auth, STORE, AUTH, EXEC2> {
119 TransactionExecutor::<'store, 'auth, STORE, AUTH, EXEC2> {
120 data_store: self.data_store,
121 authenticator: self.authenticator,
122 source_manager: self.source_manager,
123 exec_options: self.exec_options,
124 _executor: PhantomData,
125 }
126 }
127
128 #[must_use]
133 pub fn with_authenticator(mut self, authenticator: &'auth AUTH) -> Self {
134 self.authenticator = Some(authenticator);
135 self
136 }
137
138 #[must_use]
147 pub fn with_source_manager(mut self, source_manager: Arc<dyn SourceManagerSync>) -> Self {
148 self.source_manager = source_manager;
149 self
150 }
151
152 pub fn with_options(
160 mut self,
161 exec_options: ExecutionOptions,
162 ) -> Result<Self, TransactionExecutorError> {
163 validate_num_cycles(exec_options.max_cycles())?;
164 validate_num_cycles(exec_options.expected_cycles())?;
165
166 self.exec_options = exec_options;
167 Ok(self)
168 }
169
170 pub async fn execute_transaction(
191 &self,
192 account_id: AccountId,
193 block_ref: BlockNumber,
194 notes: InputNotes<InputNote>,
195 tx_args: TransactionArgs,
196 ) -> Result<ExecutedTransaction, TransactionExecutorError> {
197 let tx_inputs = self.prepare_tx_inputs(account_id, block_ref, notes, tx_args).await?;
198
199 let (mut host, stack_inputs, advice_inputs) = self.prepare_transaction(&tx_inputs).await?;
200
201 let program = TransactionKernel::main();
204 let kernel_debug_info = TransactionKernel::main_debug_info();
205 let processor = EXEC::new(stack_inputs, advice_inputs, self.exec_options)
206 .map_err(ExecutionError::advice_error_no_context)
207 .map_err(map_execution_error)?
208 .with_debug_info(kernel_debug_info.as_deref().cloned().unwrap_or_default())
209 .with_entrypoint_source_node(TransactionKernel::main_entrypoint_source_node());
210 let output = processor.execute(&program, &mut host).await.map_err(map_execution_error)?;
211 let stack_outputs = output.stack;
212 let advice_provider = output.advice;
213
214 let (_stack, advice_map, merkle_store) = advice_provider.into_parts();
216 let advice_inputs = AdviceInputs::from(advice_map).with_merkle_store(merkle_store);
217
218 build_executed_transaction(advice_inputs, tx_inputs, stack_outputs, host)
219 }
220
221 pub async fn execute_tx_view_script(
233 &self,
234 account_id: AccountId,
235 block_ref: BlockNumber,
236 tx_script: TransactionScript,
237 advice_inputs: AdviceInputs,
238 ) -> Result<[Felt; 16], TransactionExecutorError> {
239 let mut tx_args = TransactionArgs::default().with_tx_script(tx_script);
240 tx_args.extend_advice_inputs(advice_inputs);
241
242 let notes = InputNotes::default();
243 let tx_inputs = self.prepare_tx_inputs(account_id, block_ref, notes, tx_args).await?;
244
245 let (mut host, stack_inputs, advice_inputs) = self.prepare_transaction(&tx_inputs).await?;
246
247 let program = TransactionKernel::tx_script_main();
248 let kernel_debug_info = TransactionKernel::tx_script_main_debug_info();
249 let processor =
250 EXEC::new(stack_inputs, advice_inputs, self.exec_options)
251 .map_err(ExecutionError::advice_error_no_context)
252 .map_err(TransactionExecutorError::TransactionProgramExecutionFailed)?
253 .with_debug_info(kernel_debug_info.as_deref().cloned().unwrap_or_default())
254 .with_entrypoint_source_node(
255 TransactionKernel::tx_script_main_entrypoint_source_node(),
256 );
257 let output = processor
258 .execute(&program, &mut host)
259 .await
260 .map_err(TransactionExecutorError::TransactionProgramExecutionFailed)?;
261 let stack_outputs = output.stack;
262
263 Ok(*stack_outputs)
264 }
265
266 async fn prepare_tx_inputs(
275 &self,
276 account_id: AccountId,
277 block_ref: BlockNumber,
278 input_notes: InputNotes<InputNote>,
279 tx_args: TransactionArgs,
280 ) -> Result<TransactionInputs, TransactionExecutorError> {
281 let (mut asset_ids, mut ref_blocks) = validate_input_notes(&input_notes, block_ref)?;
282 ref_blocks.insert(block_ref);
283
284 let (account, block_header, protocol_config, blockchain) = self
285 .data_store
286 .get_transaction_inputs(account_id, ref_blocks)
287 .await
288 .map_err(TransactionExecutorError::FetchTransactionInputsFailed)?;
289
290 let native_account_vault_root = account.vault().root();
291
292 let mut tx_inputs =
293 TransactionInputs::new(account, block_header, protocol_config, blockchain, input_notes)
294 .map_err(TransactionExecutorError::InvalidTransactionInputs)?
295 .with_tx_args(tx_args);
296
297 asset_ids.retain(|asset_id| {
299 !tx_inputs.has_vault_asset_witness(native_account_vault_root, asset_id)
300 });
301
302 if !asset_ids.is_empty() {
304 let asset_witnesses = self
305 .data_store
306 .get_vault_asset_witnesses(account_id, native_account_vault_root, asset_ids)
307 .await
308 .map_err(TransactionExecutorError::FetchAssetWitnessFailed)?;
309
310 tx_inputs = tx_inputs.with_asset_witnesses(asset_witnesses);
311 }
312
313 Ok(tx_inputs)
314 }
315
316 async fn prepare_transaction(
321 &self,
322 tx_inputs: &TransactionInputs,
323 ) -> Result<
324 (TransactionExecutorHost<'store, 'auth, STORE, AUTH>, StackInputs, AdviceInputs),
325 TransactionExecutorError,
326 > {
327 let (stack_inputs, tx_advice_inputs) = TransactionKernel::prepare_inputs(tx_inputs);
328 let input_notes = tx_inputs.input_notes();
329
330 let script_mast_store = ScriptMastForestStore::new(
331 tx_inputs.tx_script(),
332 input_notes.iter().map(|n| n.note().script()),
333 );
334
335 let account_procedure_index_map =
338 AccountProcedureIndexMap::new([tx_inputs.account().code()]);
339
340 let host = TransactionExecutorHost::new(
341 tx_inputs.account(),
342 input_notes.clone(),
343 self.data_store,
344 script_mast_store,
345 account_procedure_index_map,
346 self.authenticator,
347 tx_inputs.block_header().block_num(),
348 tx_inputs.collect_block_commitments(),
349 self.source_manager.clone(),
350 )
351 .map_err(|err| TransactionExecutorError::TransactionHostCreationFailed(Box::new(err)))?;
352
353 let advice_inputs = tx_advice_inputs.into_advice_inputs();
354
355 Ok((host, stack_inputs, advice_inputs))
356 }
357}
358
359fn build_executed_transaction<STORE: DataStore + Sync, AUTH: TransactionAuthenticator + Sync>(
364 mut advice_inputs: AdviceInputs,
365 tx_inputs: TransactionInputs,
366 stack_outputs: StackOutputs,
367 host: TransactionExecutorHost<STORE, AUTH>,
368) -> Result<ExecutedTransaction, TransactionExecutorError> {
369 let (
370 account_patch,
371 _input_notes,
372 output_notes,
373 accessed_foreign_account_code,
374 generated_signatures,
375 tx_progress,
376 foreign_account_slot_names,
377 ) = host.into_parts();
378
379 let tx_outputs =
380 TransactionKernel::from_transaction_parts(&stack_outputs, &advice_inputs, output_notes)
381 .map_err(TransactionExecutorError::TransactionOutputConstructionFailed)?;
382
383 let patch_commitment = account_patch.to_commitment();
384 if tx_outputs.account_patch_commitment() != patch_commitment {
385 return Err(TransactionExecutorError::InconsistentAccountPatchCommitment {
386 in_kernel_commitment: tx_outputs.account_patch_commitment(),
387 host_commitment: patch_commitment,
388 });
389 }
390
391 let initial_account = tx_inputs.account();
392 let final_account = tx_outputs.account();
393
394 if initial_account.id() != final_account.id() {
395 return Err(TransactionExecutorError::InconsistentAccountId {
396 input_id: initial_account.id(),
397 output_id: final_account.id(),
398 });
399 }
400
401 advice_inputs.extend(AdviceInputs::default().with_map(generated_signatures));
403
404 let tx_inputs = tx_inputs
407 .with_foreign_account_code(accessed_foreign_account_code)
408 .with_foreign_account_slot_names(foreign_account_slot_names)
409 .with_advice_inputs(advice_inputs);
410
411 Ok(ExecutedTransaction::new(
412 tx_inputs,
413 tx_outputs,
414 account_patch,
415 tx_progress.into(),
416 ))
417}
418
419fn validate_input_notes(
428 notes: &InputNotes<InputNote>,
429 block_ref: BlockNumber,
430) -> Result<(BTreeSet<AssetId>, BTreeSet<BlockNumber>), TransactionExecutorError> {
431 let mut ref_blocks: BTreeSet<BlockNumber> = BTreeSet::new();
432 let mut asset_ids: BTreeSet<AssetId> = BTreeSet::new();
433
434 for input_note in notes.iter() {
435 if let Some(location) = input_note.location() {
438 if location.block_num() > block_ref {
439 return Err(TransactionExecutorError::NoteBlockPastReferenceBlock(
440 input_note.id(),
441 block_ref,
442 ));
443 }
444 ref_blocks.insert(location.block_num());
445 }
446
447 asset_ids.extend(input_note.note().assets().iter().map(Asset::id));
448 }
449
450 Ok((asset_ids, ref_blocks))
451}
452
453fn validate_num_cycles(num_cycles: u32) -> Result<(), TransactionExecutorError> {
455 if !(MIN_TX_EXECUTION_CYCLES..=MAX_TX_EXECUTION_CYCLES).contains(&num_cycles) {
456 Err(TransactionExecutorError::InvalidExecutionOptionsCycles {
457 min_cycles: MIN_TX_EXECUTION_CYCLES,
458 max_cycles: MAX_TX_EXECUTION_CYCLES,
459 actual: num_cycles,
460 })
461 } else {
462 Ok(())
463 }
464}
465
466fn map_execution_error(exec_err: ExecutionError) -> TransactionExecutorError {
478 match exec_err {
479 ExecutionError::EventError { ref error, .. } => {
480 match error.downcast_ref::<TransactionKernelError>() {
481 Some(TransactionKernelError::Unauthorized(summary)) => {
482 TransactionExecutorError::Unauthorized(summary.clone())
483 },
484 Some(TransactionKernelError::MissingAuthenticator) => {
485 TransactionExecutorError::MissingAuthenticator
486 },
487 Some(TransactionKernelError::AuthRequestOutsideAuthProcedure) => {
488 TransactionExecutorError::AuthRequestOutsideAuthProcedure
489 },
490 Some(
491 TransactionKernelError::PrivilegedEventFromOutsideTransactionKernelContext(
492 event_id,
493 ),
494 ) => TransactionExecutorError::PrivilegedEventFromOutsideTransactionKernelContext(
495 event_id.clone(),
496 ),
497 _ => TransactionExecutorError::TransactionProgramExecutionFailed(exec_err),
498 }
499 },
500 _ => TransactionExecutorError::TransactionProgramExecutionFailed(exec_err),
501 }
502}