1use alloc::collections::BTreeSet;
2use alloc::sync::Arc;
3use core::marker::PhantomData;
4
5use miden_processor::advice::AdviceInputs;
6use miden_processor::{ExecutionError, FastProcessor, StackInputs};
7pub use miden_processor::{ExecutionOptions, MastForestStore};
8use miden_protocol::account::AccountId;
9use miden_protocol::assembly::DefaultSourceManager;
10use miden_protocol::assembly::debuginfo::SourceManagerSync;
11use miden_protocol::asset::{Asset, AssetId};
12use miden_protocol::block::BlockNumber;
13use miden_protocol::transaction::{
14 ExecutedTransaction,
15 InputNote,
16 InputNotes,
17 TransactionArgs,
18 TransactionInputs,
19 TransactionKernel,
20 TransactionScript,
21};
22use miden_protocol::vm::{PackageDebugInfo, StackOutputs};
23use miden_protocol::{Felt, MAX_TX_EXECUTION_CYCLES, MIN_TX_EXECUTION_CYCLES};
24
25use super::TransactionExecutorError;
26use crate::auth::TransactionAuthenticator;
27use crate::errors::TransactionKernelError;
28use crate::host::{AccountProcedureIndexMap, ScriptMastForestStore};
29
30mod exec_host;
31pub use exec_host::TransactionExecutorHost;
32
33mod data_store;
34pub use data_store::DataStore;
35
36mod notes_checker;
37pub use notes_checker::{
38 FailedNote,
39 MAX_NUM_CHECKER_NOTES,
40 NoteConsumptionChecker,
41 NoteConsumptionInfo,
42 SuccessfulNote,
43};
44
45mod program_executor;
46pub use program_executor::ProgramExecutor;
47
48pub struct TransactionExecutor<
61 'store,
62 'auth,
63 STORE: 'store,
64 AUTH: 'auth,
65 EXEC: ProgramExecutor = FastProcessor,
66> {
67 data_store: &'store STORE,
68 authenticator: Option<&'auth AUTH>,
69 source_manager: Arc<dyn SourceManagerSync>,
70 exec_options: ExecutionOptions,
71 _executor: PhantomData<EXEC>,
72}
73
74impl<'store, 'auth, STORE, AUTH> TransactionExecutor<'store, 'auth, STORE, AUTH>
75where
76 STORE: DataStore + 'store + Sync,
77 AUTH: TransactionAuthenticator + 'auth + Sync,
78{
79 pub fn new(data_store: &'store STORE) -> Self {
91 const _: () = assert!(MIN_TX_EXECUTION_CYCLES <= MAX_TX_EXECUTION_CYCLES);
92 Self {
93 data_store,
94 authenticator: None,
95 source_manager: Arc::new(DefaultSourceManager::default()),
96 exec_options: ExecutionOptions::new(
97 Some(MAX_TX_EXECUTION_CYCLES),
98 MIN_TX_EXECUTION_CYCLES,
99 ExecutionOptions::DEFAULT_CORE_TRACE_FRAGMENT_SIZE,
100 )
101 .expect("Must not fail while max cycles is more than min trace length"),
102 _executor: PhantomData,
103 }
104 }
105}
106
107impl<'store, 'auth, STORE, AUTH, EXEC> TransactionExecutor<'store, 'auth, STORE, AUTH, EXEC>
108where
109 STORE: DataStore + 'store + Sync,
110 AUTH: TransactionAuthenticator + 'auth + Sync,
111 EXEC: ProgramExecutor,
112{
113 pub fn with_program_executor<EXEC2: ProgramExecutor>(
118 self,
119 ) -> TransactionExecutor<'store, 'auth, STORE, AUTH, EXEC2> {
120 TransactionExecutor::<'store, 'auth, STORE, AUTH, EXEC2> {
121 data_store: self.data_store,
122 authenticator: self.authenticator,
123 source_manager: self.source_manager,
124 exec_options: self.exec_options,
125 _executor: PhantomData,
126 }
127 }
128
129 #[must_use]
134 pub fn with_authenticator(mut self, authenticator: &'auth AUTH) -> Self {
135 self.authenticator = Some(authenticator);
136 self
137 }
138
139 #[must_use]
148 pub fn with_source_manager(mut self, source_manager: Arc<dyn SourceManagerSync>) -> Self {
149 self.source_manager = source_manager;
150 self
151 }
152
153 pub fn with_options(
161 mut self,
162 exec_options: ExecutionOptions,
163 ) -> Result<Self, TransactionExecutorError> {
164 validate_num_cycles(exec_options.max_cycles())?;
165 validate_num_cycles(exec_options.expected_cycles())?;
166
167 self.exec_options = exec_options;
168 Ok(self)
169 }
170
171 pub async fn execute_transaction(
192 &self,
193 account_id: AccountId,
194 block_ref: BlockNumber,
195 notes: InputNotes<InputNote>,
196 tx_args: TransactionArgs,
197 ) -> Result<ExecutedTransaction, TransactionExecutorError> {
198 let tx_inputs = self.prepare_tx_inputs(account_id, block_ref, notes, tx_args).await?;
199
200 let (mut host, stack_inputs, advice_inputs) = self.prepare_transaction(&tx_inputs).await?;
201
202 let processor = EXEC::new(stack_inputs, advice_inputs, self.exec_options);
205
206 let program = TransactionKernel::main();
207 let kernel_debug_info = TransactionKernel::main_debug_info();
208 let fallback_debug_info = PackageDebugInfo::default();
209 let output = processor
210 .execute_with_package_debug_info(
211 &program,
212 kernel_debug_info.as_deref().unwrap_or(&fallback_debug_info),
213 TransactionKernel::main_entrypoint_source_node(),
214 &mut host,
215 )
216 .await
217 .map_err(map_execution_error)?;
218 let stack_outputs = output.stack;
219 let advice_provider = output.advice;
220
221 let (_stack, advice_map, merkle_store, _pc_requests) = advice_provider.into_parts();
223 let advice_inputs = AdviceInputs {
224 map: advice_map,
225 store: merkle_store,
226 ..Default::default()
227 };
228
229 build_executed_transaction(advice_inputs, tx_inputs, stack_outputs, host)
230 }
231
232 pub async fn execute_tx_view_script(
244 &self,
245 account_id: AccountId,
246 block_ref: BlockNumber,
247 tx_script: TransactionScript,
248 advice_inputs: AdviceInputs,
249 ) -> Result<[Felt; 16], TransactionExecutorError> {
250 let mut tx_args = TransactionArgs::default().with_tx_script(tx_script);
251 tx_args.extend_advice_inputs(advice_inputs);
252
253 let notes = InputNotes::default();
254 let tx_inputs = self.prepare_tx_inputs(account_id, block_ref, notes, tx_args).await?;
255
256 let (mut host, stack_inputs, advice_inputs) = self.prepare_transaction(&tx_inputs).await?;
257
258 let processor = EXEC::new(stack_inputs, advice_inputs, self.exec_options);
259 let program = TransactionKernel::tx_script_main();
260 let kernel_debug_info = TransactionKernel::tx_script_main_debug_info();
261 let fallback_debug_info = PackageDebugInfo::default();
262 let output = processor
263 .execute_with_package_debug_info(
264 &program,
265 kernel_debug_info.as_deref().unwrap_or(&fallback_debug_info),
266 TransactionKernel::tx_script_main_entrypoint_source_node(),
267 &mut host,
268 )
269 .await
270 .map_err(TransactionExecutorError::TransactionProgramExecutionFailed)?;
271 let stack_outputs = output.stack;
272
273 Ok(*stack_outputs)
274 }
275
276 async fn prepare_tx_inputs(
285 &self,
286 account_id: AccountId,
287 block_ref: BlockNumber,
288 input_notes: InputNotes<InputNote>,
289 tx_args: TransactionArgs,
290 ) -> Result<TransactionInputs, TransactionExecutorError> {
291 let (mut asset_ids, mut ref_blocks) = validate_input_notes(&input_notes, block_ref)?;
292 ref_blocks.insert(block_ref);
293
294 let (account, block_header, blockchain) = self
295 .data_store
296 .get_transaction_inputs(account_id, ref_blocks)
297 .await
298 .map_err(TransactionExecutorError::FetchTransactionInputsFailed)?;
299
300 let native_account_vault_root = account.vault().root();
301
302 let mut tx_inputs = TransactionInputs::new(account, block_header, blockchain, input_notes)
303 .map_err(TransactionExecutorError::InvalidTransactionInputs)?
304 .with_tx_args(tx_args);
305
306 asset_ids.retain(|asset_id| {
308 !tx_inputs.has_vault_asset_witness(native_account_vault_root, asset_id)
309 });
310
311 if !asset_ids.is_empty() {
313 let asset_witnesses = self
314 .data_store
315 .get_vault_asset_witnesses(account_id, native_account_vault_root, asset_ids)
316 .await
317 .map_err(TransactionExecutorError::FetchAssetWitnessFailed)?;
318
319 tx_inputs = tx_inputs.with_asset_witnesses(asset_witnesses);
320 }
321
322 Ok(tx_inputs)
323 }
324
325 async fn prepare_transaction(
330 &self,
331 tx_inputs: &TransactionInputs,
332 ) -> Result<
333 (TransactionExecutorHost<'store, 'auth, STORE, AUTH>, StackInputs, AdviceInputs),
334 TransactionExecutorError,
335 > {
336 let (stack_inputs, tx_advice_inputs) = TransactionKernel::prepare_inputs(tx_inputs);
337 let input_notes = tx_inputs.input_notes();
338
339 let script_mast_store = ScriptMastForestStore::new(
340 tx_inputs.tx_script(),
341 input_notes.iter().map(|n| n.note().script()),
342 );
343
344 let account_procedure_index_map =
347 AccountProcedureIndexMap::new([tx_inputs.account().code()]);
348
349 let host = TransactionExecutorHost::new(
350 tx_inputs.account(),
351 input_notes.clone(),
352 self.data_store,
353 script_mast_store,
354 account_procedure_index_map,
355 self.authenticator,
356 tx_inputs.block_header().block_num(),
357 tx_inputs.block_header().commitment(),
358 self.source_manager.clone(),
359 );
360
361 let advice_inputs = tx_advice_inputs.into_advice_inputs();
362
363 Ok((host, stack_inputs, advice_inputs))
364 }
365}
366
367fn build_executed_transaction<STORE: DataStore + Sync, AUTH: TransactionAuthenticator + Sync>(
372 mut advice_inputs: AdviceInputs,
373 tx_inputs: TransactionInputs,
374 stack_outputs: StackOutputs,
375 host: TransactionExecutorHost<STORE, AUTH>,
376) -> Result<ExecutedTransaction, TransactionExecutorError> {
377 let (
378 account_patch,
379 _input_notes,
380 output_notes,
381 accessed_foreign_account_code,
382 generated_signatures,
383 tx_progress,
384 foreign_account_slot_names,
385 ) = host.into_parts();
386
387 let tx_outputs =
388 TransactionKernel::from_transaction_parts(&stack_outputs, &advice_inputs, output_notes)
389 .map_err(TransactionExecutorError::TransactionOutputConstructionFailed)?;
390
391 let patch_commitment = account_patch.to_commitment();
392 if tx_outputs.account_patch_commitment() != patch_commitment {
393 return Err(TransactionExecutorError::InconsistentAccountPatchCommitment {
394 in_kernel_commitment: tx_outputs.account_patch_commitment(),
395 host_commitment: patch_commitment,
396 });
397 }
398
399 let initial_account = tx_inputs.account();
400 let final_account = tx_outputs.account();
401
402 if initial_account.id() != final_account.id() {
403 return Err(TransactionExecutorError::InconsistentAccountId {
404 input_id: initial_account.id(),
405 output_id: final_account.id(),
406 });
407 }
408
409 advice_inputs.map.extend(generated_signatures);
411
412 let tx_inputs = tx_inputs
415 .with_foreign_account_code(accessed_foreign_account_code)
416 .with_foreign_account_slot_names(foreign_account_slot_names)
417 .with_advice_inputs(advice_inputs);
418
419 Ok(ExecutedTransaction::new(
420 tx_inputs,
421 tx_outputs,
422 account_patch,
423 tx_progress.into(),
424 ))
425}
426
427fn validate_input_notes(
436 notes: &InputNotes<InputNote>,
437 block_ref: BlockNumber,
438) -> Result<(BTreeSet<AssetId>, BTreeSet<BlockNumber>), TransactionExecutorError> {
439 let mut ref_blocks: BTreeSet<BlockNumber> = BTreeSet::new();
440 let mut asset_ids: BTreeSet<AssetId> = BTreeSet::new();
441
442 for input_note in notes.iter() {
443 if let Some(location) = input_note.location() {
446 if location.block_num() > block_ref {
447 return Err(TransactionExecutorError::NoteBlockPastReferenceBlock(
448 input_note.id(),
449 block_ref,
450 ));
451 }
452 ref_blocks.insert(location.block_num());
453 }
454
455 asset_ids.extend(input_note.note().assets().iter().map(Asset::id));
456 }
457
458 Ok((asset_ids, ref_blocks))
459}
460
461fn validate_num_cycles(num_cycles: u32) -> Result<(), TransactionExecutorError> {
463 if !(MIN_TX_EXECUTION_CYCLES..=MAX_TX_EXECUTION_CYCLES).contains(&num_cycles) {
464 Err(TransactionExecutorError::InvalidExecutionOptionsCycles {
465 min_cycles: MIN_TX_EXECUTION_CYCLES,
466 max_cycles: MAX_TX_EXECUTION_CYCLES,
467 actual: num_cycles,
468 })
469 } else {
470 Ok(())
471 }
472}
473
474fn map_execution_error(exec_err: ExecutionError) -> TransactionExecutorError {
486 match exec_err {
487 ExecutionError::EventError { ref error, .. } => {
488 match error.downcast_ref::<TransactionKernelError>() {
489 Some(TransactionKernelError::Unauthorized(summary)) => {
490 TransactionExecutorError::Unauthorized(summary.clone())
491 },
492 Some(TransactionKernelError::MissingAuthenticator) => {
493 TransactionExecutorError::MissingAuthenticator
494 },
495 Some(TransactionKernelError::AuthRequestOutsideAuthProcedure) => {
496 TransactionExecutorError::AuthRequestOutsideAuthProcedure
497 },
498 Some(
499 TransactionKernelError::PrivilegedEventFromOutsideTransactionKernelContext(
500 event_id,
501 ),
502 ) => TransactionExecutorError::PrivilegedEventFromOutsideTransactionKernelContext(
503 event_id.clone(),
504 ),
505 _ => TransactionExecutorError::TransactionProgramExecutionFailed(exec_err),
506 }
507 },
508 _ => TransactionExecutorError::TransactionProgramExecutionFailed(exec_err),
509 }
510}