1use alloc::collections::BTreeSet;
2use alloc::sync::Arc;
3
4use miden_processor::fast::FastProcessor;
5use miden_processor::{AdviceInputs, ExecutionError, StackInputs};
6pub use miden_processor::{ExecutionOptions, MastForestStore};
7use miden_protocol::account::AccountId;
8use miden_protocol::assembly::DefaultSourceManager;
9use miden_protocol::assembly::debuginfo::SourceManagerSync;
10use miden_protocol::asset::{Asset, AssetVaultKey};
11use miden_protocol::block::BlockNumber;
12use miden_protocol::transaction::{
13 ExecutedTransaction,
14 InputNote,
15 InputNotes,
16 TransactionArgs,
17 TransactionInputs,
18 TransactionKernel,
19 TransactionScript,
20};
21use miden_protocol::vm::StackOutputs;
22use miden_protocol::{Felt, MAX_TX_EXECUTION_CYCLES, MIN_TX_EXECUTION_CYCLES};
23
24use super::TransactionExecutorError;
25use crate::auth::TransactionAuthenticator;
26use crate::errors::TransactionKernelError;
27use crate::host::{AccountProcedureIndexMap, ScriptMastForestStore};
28
29mod exec_host;
30pub use exec_host::TransactionExecutorHost;
31
32mod data_store;
33pub use data_store::DataStore;
34
35mod notes_checker;
36pub use notes_checker::{
37 FailedNote,
38 MAX_NUM_CHECKER_NOTES,
39 NoteConsumptionChecker,
40 NoteConsumptionInfo,
41};
42
43pub struct TransactionExecutor<'store, 'auth, STORE: 'store, AUTH: 'auth> {
56 data_store: &'store STORE,
57 authenticator: Option<&'auth AUTH>,
58 source_manager: Arc<dyn SourceManagerSync>,
59 exec_options: ExecutionOptions,
60}
61
62impl<'store, 'auth, STORE, AUTH> TransactionExecutor<'store, 'auth, STORE, AUTH>
63where
64 STORE: DataStore + 'store + Sync,
65 AUTH: TransactionAuthenticator + 'auth + Sync,
66{
67 pub fn new(data_store: &'store STORE) -> Self {
75 const _: () = assert!(MIN_TX_EXECUTION_CYCLES <= MAX_TX_EXECUTION_CYCLES);
76 TransactionExecutor {
77 data_store,
78 authenticator: None,
79 source_manager: Arc::new(DefaultSourceManager::default()),
80 exec_options: ExecutionOptions::new(
81 Some(MAX_TX_EXECUTION_CYCLES),
82 MIN_TX_EXECUTION_CYCLES,
83 false,
84 false,
85 )
86 .expect("Must not fail while max cycles is more than min trace length"),
87 }
88 }
89
90 #[must_use]
95 pub fn with_authenticator(mut self, authenticator: &'auth AUTH) -> Self {
96 self.authenticator = Some(authenticator);
97 self
98 }
99
100 #[must_use]
109 pub fn with_source_manager(mut self, source_manager: Arc<dyn SourceManagerSync>) -> Self {
110 self.source_manager = source_manager;
111 self
112 }
113
114 pub fn with_options(
122 mut self,
123 exec_options: ExecutionOptions,
124 ) -> Result<Self, TransactionExecutorError> {
125 validate_num_cycles(exec_options.max_cycles())?;
126 validate_num_cycles(exec_options.expected_cycles())?;
127
128 self.exec_options = exec_options;
129 Ok(self)
130 }
131
132 #[must_use]
138 pub fn with_debug_mode(mut self) -> Self {
139 self.exec_options = self.exec_options.with_debugging(true);
140 self
141 }
142
143 #[must_use]
150 pub fn with_tracing(mut self) -> Self {
151 self.exec_options = self.exec_options.with_tracing();
152 self
153 }
154
155 pub async fn execute_transaction(
176 &self,
177 account_id: AccountId,
178 block_ref: BlockNumber,
179 notes: InputNotes<InputNote>,
180 tx_args: TransactionArgs,
181 ) -> Result<ExecutedTransaction, TransactionExecutorError> {
182 let tx_inputs = self.prepare_tx_inputs(account_id, block_ref, notes, tx_args).await?;
183
184 let (mut host, stack_inputs, advice_inputs) = self.prepare_transaction(&tx_inputs).await?;
185
186 let processor = if self.exec_options.enable_debugging() {
191 FastProcessor::new_debug(stack_inputs.as_slice(), advice_inputs)
192 } else {
193 FastProcessor::new_with_advice_inputs(stack_inputs.as_slice(), advice_inputs)
194 };
195
196 let output = processor
197 .execute(&TransactionKernel::main(), &mut host)
198 .await
199 .map_err(map_execution_error)?;
200 let stack_outputs = output.stack;
201 let advice_provider = output.advice;
202
203 let (_stack, advice_map, merkle_store, _pc_requests) = advice_provider.into_parts();
205 let advice_inputs = AdviceInputs {
206 map: advice_map,
207 store: merkle_store,
208 ..Default::default()
209 };
210
211 build_executed_transaction(advice_inputs, tx_inputs, stack_outputs, host)
212 }
213
214 pub async fn execute_tx_view_script(
226 &self,
227 account_id: AccountId,
228 block_ref: BlockNumber,
229 tx_script: TransactionScript,
230 advice_inputs: AdviceInputs,
231 ) -> Result<[Felt; 16], TransactionExecutorError> {
232 let mut tx_args = TransactionArgs::default().with_tx_script(tx_script);
233 tx_args.extend_advice_inputs(advice_inputs);
234
235 let notes = InputNotes::default();
236 let tx_inputs = self.prepare_tx_inputs(account_id, block_ref, notes, tx_args).await?;
237
238 let (mut host, stack_inputs, advice_inputs) = self.prepare_transaction(&tx_inputs).await?;
239
240 let processor =
241 FastProcessor::new_with_advice_inputs(stack_inputs.as_slice(), advice_inputs);
242 let output = processor
243 .execute(&TransactionKernel::tx_script_main(), &mut host)
244 .await
245 .map_err(TransactionExecutorError::TransactionProgramExecutionFailed)?;
246 let stack_outputs = output.stack;
247
248 Ok(*stack_outputs)
249 }
250
251 async fn prepare_tx_inputs(
260 &self,
261 account_id: AccountId,
262 block_ref: BlockNumber,
263 input_notes: InputNotes<InputNote>,
264 tx_args: TransactionArgs,
265 ) -> Result<TransactionInputs, TransactionExecutorError> {
266 let (mut asset_vault_keys, mut ref_blocks) = validate_input_notes(&input_notes, block_ref)?;
267 ref_blocks.insert(block_ref);
268
269 let (account, block_header, blockchain) = self
270 .data_store
271 .get_transaction_inputs(account_id, ref_blocks)
272 .await
273 .map_err(TransactionExecutorError::FetchTransactionInputsFailed)?;
274
275 let fee_asset_vault_key =
278 AssetVaultKey::from_account_id(block_header.fee_parameters().native_asset_id())
279 .expect("fee asset should be a fungible asset");
280 asset_vault_keys.insert(fee_asset_vault_key);
281
282 let asset_witnesses = self
284 .data_store
285 .get_vault_asset_witnesses(account_id, account.vault().root(), asset_vault_keys)
286 .await
287 .map_err(TransactionExecutorError::FetchAssetWitnessFailed)?;
288
289 let tx_inputs = TransactionInputs::new(account, block_header, blockchain, input_notes)
290 .map_err(TransactionExecutorError::InvalidTransactionInputs)?
291 .with_tx_args(tx_args)
292 .with_asset_witnesses(asset_witnesses);
293
294 Ok(tx_inputs)
295 }
296
297 async fn prepare_transaction(
302 &self,
303 tx_inputs: &TransactionInputs,
304 ) -> Result<
305 (TransactionExecutorHost<'store, 'auth, STORE, AUTH>, StackInputs, AdviceInputs),
306 TransactionExecutorError,
307 > {
308 let (stack_inputs, tx_advice_inputs) = TransactionKernel::prepare_inputs(tx_inputs);
309
310 let stack_inputs = StackInputs::new(stack_inputs.iter().copied().collect()).unwrap();
316
317 let input_notes = tx_inputs.input_notes();
318
319 let script_mast_store = ScriptMastForestStore::new(
320 tx_inputs.tx_script(),
321 input_notes.iter().map(|n| n.note().script()),
322 );
323
324 let account_procedure_index_map =
327 AccountProcedureIndexMap::new([tx_inputs.account().code()]);
328
329 let initial_fee_asset_balance = {
330 let native_asset_id = tx_inputs.block_header().fee_parameters().native_asset_id();
331 let fee_asset_vault_key = AssetVaultKey::from_account_id(native_asset_id)
332 .expect("fee asset should be a fungible asset");
333
334 let fee_asset_witness = tx_inputs
335 .asset_witnesses()
336 .iter()
337 .find_map(|witness| witness.find(fee_asset_vault_key));
338
339 match fee_asset_witness {
340 Some(Asset::Fungible(fee_asset)) => fee_asset.amount(),
341 Some(Asset::NonFungible(_)) => {
342 return Err(TransactionExecutorError::FeeAssetMustBeFungible);
343 },
344 None => 0,
346 }
347 };
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 initial_fee_asset_balance,
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 (
381 pre_fee_account_delta,
382 _input_notes,
383 output_notes,
384 accessed_foreign_account_code,
385 generated_signatures,
386 tx_progress,
387 foreign_account_slot_names,
388 ) = host.into_parts();
389
390 let tx_outputs =
391 TransactionKernel::from_transaction_parts(&stack_outputs, &advice_inputs, output_notes)
392 .map_err(TransactionExecutorError::TransactionOutputConstructionFailed)?;
393
394 let pre_fee_delta_commitment = pre_fee_account_delta.to_commitment();
395 if tx_outputs.account_delta_commitment != pre_fee_delta_commitment {
396 return Err(TransactionExecutorError::InconsistentAccountDeltaCommitment {
397 in_kernel_commitment: tx_outputs.account_delta_commitment,
398 host_commitment: pre_fee_delta_commitment,
399 });
400 }
401
402 let mut post_fee_account_delta = pre_fee_account_delta;
404 post_fee_account_delta
405 .vault_mut()
406 .remove_asset(Asset::from(tx_outputs.fee))
407 .map_err(TransactionExecutorError::RemoveFeeAssetFromDelta)?;
408
409 let initial_account = tx_inputs.account();
410 let final_account = &tx_outputs.account;
411
412 if initial_account.id() != final_account.id() {
413 return Err(TransactionExecutorError::InconsistentAccountId {
414 input_id: initial_account.id(),
415 output_id: final_account.id(),
416 });
417 }
418
419 let nonce_delta = final_account.nonce() - initial_account.nonce();
421 if nonce_delta != post_fee_account_delta.nonce_delta() {
422 return Err(TransactionExecutorError::InconsistentAccountNonceDelta {
423 expected: nonce_delta,
424 actual: post_fee_account_delta.nonce_delta(),
425 });
426 }
427
428 advice_inputs.map.extend(generated_signatures);
430
431 let tx_inputs = tx_inputs
434 .with_foreign_account_code(accessed_foreign_account_code)
435 .with_foreign_account_slot_names(foreign_account_slot_names)
436 .with_advice_inputs(advice_inputs);
437
438 Ok(ExecutedTransaction::new(
439 tx_inputs,
440 tx_outputs,
441 post_fee_account_delta,
442 tx_progress.into(),
443 ))
444}
445
446fn validate_input_notes(
455 notes: &InputNotes<InputNote>,
456 block_ref: BlockNumber,
457) -> Result<(BTreeSet<AssetVaultKey>, BTreeSet<BlockNumber>), TransactionExecutorError> {
458 let mut ref_blocks: BTreeSet<BlockNumber> = BTreeSet::new();
459 let mut asset_vault_keys: BTreeSet<AssetVaultKey> = BTreeSet::new();
460
461 for input_note in notes.iter() {
462 if let Some(location) = input_note.location() {
465 if location.block_num() > block_ref {
466 return Err(TransactionExecutorError::NoteBlockPastReferenceBlock(
467 input_note.id(),
468 block_ref,
469 ));
470 }
471 ref_blocks.insert(location.block_num());
472 }
473
474 asset_vault_keys.extend(input_note.note().assets().iter().map(Asset::vault_key));
475 }
476
477 Ok((asset_vault_keys, ref_blocks))
478}
479
480fn validate_num_cycles(num_cycles: u32) -> Result<(), TransactionExecutorError> {
482 if !(MIN_TX_EXECUTION_CYCLES..=MAX_TX_EXECUTION_CYCLES).contains(&num_cycles) {
483 Err(TransactionExecutorError::InvalidExecutionOptionsCycles {
484 min_cycles: MIN_TX_EXECUTION_CYCLES,
485 max_cycles: MAX_TX_EXECUTION_CYCLES,
486 actual: num_cycles,
487 })
488 } else {
489 Ok(())
490 }
491}
492
493fn map_execution_error(exec_err: ExecutionError) -> TransactionExecutorError {
500 match exec_err {
501 ExecutionError::EventError { ref error, .. } => {
502 match error.downcast_ref::<TransactionKernelError>() {
503 Some(TransactionKernelError::Unauthorized(summary)) => {
504 TransactionExecutorError::Unauthorized(summary.clone())
505 },
506 Some(TransactionKernelError::InsufficientFee { account_balance, tx_fee }) => {
507 TransactionExecutorError::InsufficientFee {
508 account_balance: *account_balance,
509 tx_fee: *tx_fee,
510 }
511 },
512 Some(TransactionKernelError::MissingAuthenticator) => {
513 TransactionExecutorError::MissingAuthenticator
514 },
515 _ => TransactionExecutorError::TransactionProgramExecutionFailed(exec_err),
516 }
517 },
518 _ => TransactionExecutorError::TransactionProgramExecutionFailed(exec_err),
519 }
520}