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, AssetCallbackFlag, AssetVaultKey};
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::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
48const FEE_ASSET_CALLBACK_FLAG: AssetCallbackFlag = AssetCallbackFlag::Disabled;
54
55pub struct TransactionExecutor<
68 'store,
69 'auth,
70 STORE: 'store,
71 AUTH: 'auth,
72 EXEC: ProgramExecutor = FastProcessor,
73> {
74 data_store: &'store STORE,
75 authenticator: Option<&'auth AUTH>,
76 source_manager: Arc<dyn SourceManagerSync>,
77 exec_options: ExecutionOptions,
78 _executor: PhantomData<EXEC>,
79}
80
81impl<'store, 'auth, STORE, AUTH> TransactionExecutor<'store, 'auth, STORE, AUTH>
82where
83 STORE: DataStore + 'store + Sync,
84 AUTH: TransactionAuthenticator + 'auth + Sync,
85{
86 pub fn new(data_store: &'store STORE) -> Self {
98 const _: () = assert!(MIN_TX_EXECUTION_CYCLES <= MAX_TX_EXECUTION_CYCLES);
99 Self {
100 data_store,
101 authenticator: None,
102 source_manager: Arc::new(DefaultSourceManager::default()),
103 exec_options: ExecutionOptions::new(
104 Some(MAX_TX_EXECUTION_CYCLES),
105 MIN_TX_EXECUTION_CYCLES,
106 ExecutionOptions::DEFAULT_CORE_TRACE_FRAGMENT_SIZE,
107 false,
108 false,
109 )
110 .expect("Must not fail while max cycles is more than min trace length"),
111 _executor: PhantomData,
112 }
113 }
114}
115
116impl<'store, 'auth, STORE, AUTH, EXEC> TransactionExecutor<'store, 'auth, STORE, AUTH, EXEC>
117where
118 STORE: DataStore + 'store + Sync,
119 AUTH: TransactionAuthenticator + 'auth + Sync,
120 EXEC: ProgramExecutor,
121{
122 pub fn with_program_executor<EXEC2: ProgramExecutor>(
127 self,
128 ) -> TransactionExecutor<'store, 'auth, STORE, AUTH, EXEC2> {
129 TransactionExecutor::<'store, 'auth, STORE, AUTH, EXEC2> {
130 data_store: self.data_store,
131 authenticator: self.authenticator,
132 source_manager: self.source_manager,
133 exec_options: self.exec_options,
134 _executor: PhantomData,
135 }
136 }
137
138 #[must_use]
143 pub fn with_authenticator(mut self, authenticator: &'auth AUTH) -> Self {
144 self.authenticator = Some(authenticator);
145 self
146 }
147
148 #[must_use]
157 pub fn with_source_manager(mut self, source_manager: Arc<dyn SourceManagerSync>) -> Self {
158 self.source_manager = source_manager;
159 self
160 }
161
162 pub fn with_options(
170 mut self,
171 exec_options: ExecutionOptions,
172 ) -> Result<Self, TransactionExecutorError> {
173 validate_num_cycles(exec_options.max_cycles())?;
174 validate_num_cycles(exec_options.expected_cycles())?;
175
176 self.exec_options = exec_options;
177 Ok(self)
178 }
179
180 #[must_use]
186 pub fn with_debug_mode(mut self) -> Self {
187 self.exec_options = self.exec_options.with_debugging(true);
188 self
189 }
190
191 #[must_use]
198 pub fn with_tracing(mut self) -> Self {
199 self.exec_options = self.exec_options.with_tracing(true);
200 self
201 }
202
203 pub async fn execute_transaction(
224 &self,
225 account_id: AccountId,
226 block_ref: BlockNumber,
227 notes: InputNotes<InputNote>,
228 tx_args: TransactionArgs,
229 ) -> Result<ExecutedTransaction, TransactionExecutorError> {
230 let tx_inputs = self.prepare_tx_inputs(account_id, block_ref, notes, tx_args).await?;
231
232 let (mut host, stack_inputs, advice_inputs) = self.prepare_transaction(&tx_inputs).await?;
233
234 let processor = EXEC::new(stack_inputs, advice_inputs, self.exec_options);
237
238 let output = processor
239 .execute(&TransactionKernel::main(), &mut host)
240 .await
241 .map_err(map_execution_error)?;
242 let stack_outputs = output.stack;
243 let advice_provider = output.advice;
244
245 let (_stack, advice_map, merkle_store, _pc_requests) = advice_provider.into_parts();
247 let advice_inputs = AdviceInputs {
248 map: advice_map,
249 store: merkle_store,
250 ..Default::default()
251 };
252
253 build_executed_transaction(advice_inputs, tx_inputs, stack_outputs, host)
254 }
255
256 pub async fn execute_tx_view_script(
268 &self,
269 account_id: AccountId,
270 block_ref: BlockNumber,
271 tx_script: TransactionScript,
272 advice_inputs: AdviceInputs,
273 ) -> Result<[Felt; 16], TransactionExecutorError> {
274 let mut tx_args = TransactionArgs::default().with_tx_script(tx_script);
275 tx_args.extend_advice_inputs(advice_inputs);
276
277 let notes = InputNotes::default();
278 let tx_inputs = self.prepare_tx_inputs(account_id, block_ref, notes, tx_args).await?;
279
280 let (mut host, stack_inputs, advice_inputs) = self.prepare_transaction(&tx_inputs).await?;
281
282 let processor = EXEC::new(stack_inputs, advice_inputs, self.exec_options);
283 let output = processor
284 .execute(&TransactionKernel::tx_script_main(), &mut host)
285 .await
286 .map_err(TransactionExecutorError::TransactionProgramExecutionFailed)?;
287 let stack_outputs = output.stack;
288
289 Ok(*stack_outputs)
290 }
291
292 async fn prepare_tx_inputs(
301 &self,
302 account_id: AccountId,
303 block_ref: BlockNumber,
304 input_notes: InputNotes<InputNote>,
305 tx_args: TransactionArgs,
306 ) -> Result<TransactionInputs, TransactionExecutorError> {
307 let (mut asset_vault_keys, mut ref_blocks) = validate_input_notes(&input_notes, block_ref)?;
308 ref_blocks.insert(block_ref);
309
310 let (account, block_header, blockchain) = self
311 .data_store
312 .get_transaction_inputs(account_id, ref_blocks)
313 .await
314 .map_err(TransactionExecutorError::FetchTransactionInputsFailed)?;
315
316 let native_account_vault_root = account.vault().root();
317 let fee_asset_vault_key = AssetVaultKey::new_fungible(
318 block_header.fee_parameters().fee_faucet_id(),
319 FEE_ASSET_CALLBACK_FLAG,
320 );
321
322 let mut tx_inputs = TransactionInputs::new(account, block_header, blockchain, input_notes)
323 .map_err(TransactionExecutorError::InvalidTransactionInputs)?
324 .with_tx_args(tx_args);
325
326 asset_vault_keys.insert(fee_asset_vault_key);
329
330 asset_vault_keys.retain(|asset_key| {
332 !tx_inputs.has_vault_asset_witness(native_account_vault_root, asset_key)
333 });
334
335 if !asset_vault_keys.is_empty() {
337 let asset_witnesses = self
338 .data_store
339 .get_vault_asset_witnesses(account_id, native_account_vault_root, asset_vault_keys)
340 .await
341 .map_err(TransactionExecutorError::FetchAssetWitnessFailed)?;
342
343 tx_inputs = tx_inputs.with_asset_witnesses(asset_witnesses);
344 }
345
346 Ok(tx_inputs)
347 }
348
349 async fn prepare_transaction(
354 &self,
355 tx_inputs: &TransactionInputs,
356 ) -> Result<
357 (TransactionExecutorHost<'store, 'auth, STORE, AUTH>, StackInputs, AdviceInputs),
358 TransactionExecutorError,
359 > {
360 let (stack_inputs, tx_advice_inputs) = TransactionKernel::prepare_inputs(tx_inputs);
361 let input_notes = tx_inputs.input_notes();
362
363 let script_mast_store = ScriptMastForestStore::new(
364 tx_inputs.tx_script(),
365 input_notes.iter().map(|n| n.note().script()),
366 );
367
368 let account_procedure_index_map =
371 AccountProcedureIndexMap::new([tx_inputs.account().code()]);
372
373 let initial_fee_asset_balance = {
374 let vault_root = tx_inputs.account().vault().root();
375 let fee_parameters = tx_inputs.block_header().fee_parameters();
376 let fee_asset_vault_key = AssetVaultKey::new_fungible(
377 fee_parameters.fee_faucet_id(),
378 FEE_ASSET_CALLBACK_FLAG,
379 );
380
381 let fee_asset = tx_inputs
382 .read_vault_asset(vault_root, fee_asset_vault_key)
383 .map_err(TransactionExecutorError::FeeAssetRetrievalFailed)?;
384 match fee_asset {
385 Some(Asset::Fungible(fee_asset)) => fee_asset.amount().as_u64(),
386 Some(Asset::NonFungible(_)) => {
387 return Err(TransactionExecutorError::FeeAssetMustBeFungible);
388 },
389 None => 0,
391 }
392 };
393 let host = TransactionExecutorHost::new(
394 tx_inputs.account(),
395 input_notes.clone(),
396 self.data_store,
397 script_mast_store,
398 account_procedure_index_map,
399 self.authenticator,
400 tx_inputs.block_header().block_num(),
401 initial_fee_asset_balance,
402 self.source_manager.clone(),
403 );
404
405 let advice_inputs = tx_advice_inputs.into_advice_inputs();
406
407 Ok((host, stack_inputs, advice_inputs))
408 }
409}
410
411fn build_executed_transaction<STORE: DataStore + Sync, AUTH: TransactionAuthenticator + Sync>(
416 mut advice_inputs: AdviceInputs,
417 tx_inputs: TransactionInputs,
418 stack_outputs: StackOutputs,
419 host: TransactionExecutorHost<STORE, AUTH>,
420) -> Result<ExecutedTransaction, TransactionExecutorError> {
421 let (
425 pre_fee_account_delta,
426 _input_notes,
427 output_notes,
428 accessed_foreign_account_code,
429 generated_signatures,
430 tx_progress,
431 foreign_account_slot_names,
432 ) = host.into_parts();
433
434 let tx_outputs =
435 TransactionKernel::from_transaction_parts(&stack_outputs, &advice_inputs, output_notes)
436 .map_err(TransactionExecutorError::TransactionOutputConstructionFailed)?;
437
438 let pre_fee_delta_commitment = pre_fee_account_delta.to_commitment();
439 if tx_outputs.account_delta_commitment() != pre_fee_delta_commitment {
440 return Err(TransactionExecutorError::InconsistentAccountDeltaCommitment {
441 in_kernel_commitment: tx_outputs.account_delta_commitment(),
442 host_commitment: pre_fee_delta_commitment,
443 });
444 }
445
446 let mut post_fee_account_delta = pre_fee_account_delta;
448 post_fee_account_delta
449 .vault_mut()
450 .remove_asset(Asset::from(tx_outputs.fee()))
451 .map_err(TransactionExecutorError::RemoveFeeAssetFromDelta)?;
452
453 let initial_account = tx_inputs.account();
454 let final_account = tx_outputs.account();
455
456 if initial_account.id() != final_account.id() {
457 return Err(TransactionExecutorError::InconsistentAccountId {
458 input_id: initial_account.id(),
459 output_id: final_account.id(),
460 });
461 }
462
463 let nonce_delta = final_account.nonce() - initial_account.nonce();
465 if nonce_delta != post_fee_account_delta.nonce_delta() {
466 return Err(TransactionExecutorError::InconsistentAccountNonceDelta {
467 expected: nonce_delta,
468 actual: post_fee_account_delta.nonce_delta(),
469 });
470 }
471
472 advice_inputs.map.extend(generated_signatures);
474
475 let tx_inputs = tx_inputs
478 .with_foreign_account_code(accessed_foreign_account_code)
479 .with_foreign_account_slot_names(foreign_account_slot_names)
480 .with_advice_inputs(advice_inputs);
481
482 Ok(ExecutedTransaction::new(
483 tx_inputs,
484 tx_outputs,
485 post_fee_account_delta,
486 tx_progress.into(),
487 ))
488}
489
490fn validate_input_notes(
499 notes: &InputNotes<InputNote>,
500 block_ref: BlockNumber,
501) -> Result<(BTreeSet<AssetVaultKey>, BTreeSet<BlockNumber>), TransactionExecutorError> {
502 let mut ref_blocks: BTreeSet<BlockNumber> = BTreeSet::new();
503 let mut asset_vault_keys: BTreeSet<AssetVaultKey> = BTreeSet::new();
504
505 for input_note in notes.iter() {
506 if let Some(location) = input_note.location() {
509 if location.block_num() > block_ref {
510 return Err(TransactionExecutorError::NoteBlockPastReferenceBlock(
511 input_note.id(),
512 block_ref,
513 ));
514 }
515 ref_blocks.insert(location.block_num());
516 }
517
518 asset_vault_keys.extend(input_note.note().assets().iter().map(Asset::vault_key));
519 }
520
521 Ok((asset_vault_keys, ref_blocks))
522}
523
524fn validate_num_cycles(num_cycles: u32) -> Result<(), TransactionExecutorError> {
526 if !(MIN_TX_EXECUTION_CYCLES..=MAX_TX_EXECUTION_CYCLES).contains(&num_cycles) {
527 Err(TransactionExecutorError::InvalidExecutionOptionsCycles {
528 min_cycles: MIN_TX_EXECUTION_CYCLES,
529 max_cycles: MAX_TX_EXECUTION_CYCLES,
530 actual: num_cycles,
531 })
532 } else {
533 Ok(())
534 }
535}
536
537fn map_execution_error(exec_err: ExecutionError) -> TransactionExecutorError {
544 match exec_err {
545 ExecutionError::EventError { ref error, .. } => {
546 match error.downcast_ref::<TransactionKernelError>() {
547 Some(TransactionKernelError::Unauthorized(summary)) => {
548 TransactionExecutorError::Unauthorized(summary.clone())
549 },
550 Some(TransactionKernelError::InsufficientFee { account_balance, tx_fee }) => {
551 TransactionExecutorError::InsufficientFee {
552 account_balance: *account_balance,
553 tx_fee: *tx_fee,
554 }
555 },
556 Some(TransactionKernelError::MissingAuthenticator) => {
557 TransactionExecutorError::MissingAuthenticator
558 },
559 _ => TransactionExecutorError::TransactionProgramExecutionFailed(exec_err),
560 }
561 },
562 _ => TransactionExecutorError::TransactionProgramExecutionFailed(exec_err),
563 }
564}