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 = FastProcessor::new_debug(stack_inputs.as_slice(), advice_inputs);
187 let output = processor
188 .execute(&TransactionKernel::main(), &mut host)
189 .await
190 .map_err(map_execution_error)?;
191 let stack_outputs = output.stack;
192 let advice_provider = output.advice;
193
194 let (_stack, advice_map, merkle_store, _pc_requests) = advice_provider.into_parts();
196 let advice_inputs = AdviceInputs {
197 map: advice_map,
198 store: merkle_store,
199 ..Default::default()
200 };
201
202 build_executed_transaction(advice_inputs, tx_inputs, stack_outputs, host)
203 }
204
205 pub async fn execute_tx_view_script(
217 &self,
218 account_id: AccountId,
219 block_ref: BlockNumber,
220 tx_script: TransactionScript,
221 advice_inputs: AdviceInputs,
222 ) -> Result<[Felt; 16], TransactionExecutorError> {
223 let mut tx_args = TransactionArgs::default().with_tx_script(tx_script);
224 tx_args.extend_advice_inputs(advice_inputs);
225
226 let notes = InputNotes::default();
227 let tx_inputs = self.prepare_tx_inputs(account_id, block_ref, notes, tx_args).await?;
228
229 let (mut host, stack_inputs, advice_inputs) = self.prepare_transaction(&tx_inputs).await?;
230
231 let processor =
232 FastProcessor::new_with_advice_inputs(stack_inputs.as_slice(), advice_inputs);
233 let output = processor
234 .execute(&TransactionKernel::tx_script_main(), &mut host)
235 .await
236 .map_err(TransactionExecutorError::TransactionProgramExecutionFailed)?;
237 let stack_outputs = output.stack;
238
239 Ok(*stack_outputs)
240 }
241
242 async fn prepare_tx_inputs(
251 &self,
252 account_id: AccountId,
253 block_ref: BlockNumber,
254 input_notes: InputNotes<InputNote>,
255 tx_args: TransactionArgs,
256 ) -> Result<TransactionInputs, TransactionExecutorError> {
257 let (mut asset_vault_keys, mut ref_blocks) = validate_input_notes(&input_notes, block_ref)?;
258 ref_blocks.insert(block_ref);
259
260 let (account, block_header, blockchain) = self
261 .data_store
262 .get_transaction_inputs(account_id, ref_blocks)
263 .await
264 .map_err(TransactionExecutorError::FetchTransactionInputsFailed)?;
265
266 let fee_asset_vault_key =
269 AssetVaultKey::from_account_id(block_header.fee_parameters().native_asset_id())
270 .expect("fee asset should be a fungible asset");
271 asset_vault_keys.insert(fee_asset_vault_key);
272
273 let asset_witnesses = self
275 .data_store
276 .get_vault_asset_witnesses(account_id, account.vault().root(), asset_vault_keys)
277 .await
278 .map_err(TransactionExecutorError::FetchAssetWitnessFailed)?;
279
280 let tx_inputs = TransactionInputs::new(account, block_header, blockchain, input_notes)
281 .map_err(TransactionExecutorError::InvalidTransactionInputs)?
282 .with_tx_args(tx_args)
283 .with_asset_witnesses(asset_witnesses);
284
285 Ok(tx_inputs)
286 }
287
288 async fn prepare_transaction(
293 &self,
294 tx_inputs: &TransactionInputs,
295 ) -> Result<
296 (TransactionExecutorHost<'store, 'auth, STORE, AUTH>, StackInputs, AdviceInputs),
297 TransactionExecutorError,
298 > {
299 let (stack_inputs, tx_advice_inputs) = TransactionKernel::prepare_inputs(tx_inputs);
300
301 let stack_inputs = StackInputs::new(stack_inputs.iter().copied().collect()).unwrap();
307
308 let input_notes = tx_inputs.input_notes();
309
310 let script_mast_store = ScriptMastForestStore::new(
311 tx_inputs.tx_script(),
312 input_notes.iter().map(|n| n.note().script()),
313 );
314
315 let account_procedure_index_map =
318 AccountProcedureIndexMap::new([tx_inputs.account().code()]);
319
320 let initial_fee_asset_balance = {
321 let native_asset_id = tx_inputs.block_header().fee_parameters().native_asset_id();
322 let fee_asset_vault_key = AssetVaultKey::from_account_id(native_asset_id)
323 .expect("fee asset should be a fungible asset");
324
325 let fee_asset_witness = tx_inputs
326 .asset_witnesses()
327 .iter()
328 .find_map(|witness| witness.find(fee_asset_vault_key));
329
330 match fee_asset_witness {
331 Some(Asset::Fungible(fee_asset)) => fee_asset.amount(),
332 Some(Asset::NonFungible(_)) => {
333 return Err(TransactionExecutorError::FeeAssetMustBeFungible);
334 },
335 None => 0,
337 }
338 };
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 initial_fee_asset_balance,
349 self.source_manager.clone(),
350 );
351
352 let advice_inputs = tx_advice_inputs.into_advice_inputs();
353
354 Ok((host, stack_inputs, advice_inputs))
355 }
356}
357
358fn build_executed_transaction<STORE: DataStore + Sync, AUTH: TransactionAuthenticator + Sync>(
363 mut advice_inputs: AdviceInputs,
364 tx_inputs: TransactionInputs,
365 stack_outputs: StackOutputs,
366 host: TransactionExecutorHost<STORE, AUTH>,
367) -> Result<ExecutedTransaction, TransactionExecutorError> {
368 let (
372 pre_fee_account_delta,
373 _input_notes,
374 output_notes,
375 accessed_foreign_account_code,
376 generated_signatures,
377 tx_progress,
378 foreign_account_slot_names,
379 ) = host.into_parts();
380
381 let tx_outputs =
382 TransactionKernel::from_transaction_parts(&stack_outputs, &advice_inputs, output_notes)
383 .map_err(TransactionExecutorError::TransactionOutputConstructionFailed)?;
384
385 let pre_fee_delta_commitment = pre_fee_account_delta.to_commitment();
386 if tx_outputs.account_delta_commitment != pre_fee_delta_commitment {
387 return Err(TransactionExecutorError::InconsistentAccountDeltaCommitment {
388 in_kernel_commitment: tx_outputs.account_delta_commitment,
389 host_commitment: pre_fee_delta_commitment,
390 });
391 }
392
393 let mut post_fee_account_delta = pre_fee_account_delta;
395 post_fee_account_delta
396 .vault_mut()
397 .remove_asset(Asset::from(tx_outputs.fee))
398 .map_err(TransactionExecutorError::RemoveFeeAssetFromDelta)?;
399
400 let initial_account = tx_inputs.account();
401 let final_account = &tx_outputs.account;
402
403 if initial_account.id() != final_account.id() {
404 return Err(TransactionExecutorError::InconsistentAccountId {
405 input_id: initial_account.id(),
406 output_id: final_account.id(),
407 });
408 }
409
410 let nonce_delta = final_account.nonce() - initial_account.nonce();
412 if nonce_delta != post_fee_account_delta.nonce_delta() {
413 return Err(TransactionExecutorError::InconsistentAccountNonceDelta {
414 expected: nonce_delta,
415 actual: post_fee_account_delta.nonce_delta(),
416 });
417 }
418
419 advice_inputs.map.extend(generated_signatures);
421
422 let tx_inputs = tx_inputs
425 .with_foreign_account_code(accessed_foreign_account_code)
426 .with_foreign_account_slot_names(foreign_account_slot_names)
427 .with_advice_inputs(advice_inputs);
428
429 Ok(ExecutedTransaction::new(
430 tx_inputs,
431 tx_outputs,
432 post_fee_account_delta,
433 tx_progress.into(),
434 ))
435}
436
437fn validate_input_notes(
446 notes: &InputNotes<InputNote>,
447 block_ref: BlockNumber,
448) -> Result<(BTreeSet<AssetVaultKey>, BTreeSet<BlockNumber>), TransactionExecutorError> {
449 let mut ref_blocks: BTreeSet<BlockNumber> = BTreeSet::new();
450 let mut asset_vault_keys: BTreeSet<AssetVaultKey> = BTreeSet::new();
451
452 for input_note in notes.iter() {
453 if let Some(location) = input_note.location() {
456 if location.block_num() > block_ref {
457 return Err(TransactionExecutorError::NoteBlockPastReferenceBlock(
458 input_note.id(),
459 block_ref,
460 ));
461 }
462 ref_blocks.insert(location.block_num());
463 }
464
465 asset_vault_keys.extend(input_note.note().assets().iter().map(Asset::vault_key));
466 }
467
468 Ok((asset_vault_keys, ref_blocks))
469}
470
471fn validate_num_cycles(num_cycles: u32) -> Result<(), TransactionExecutorError> {
473 if !(MIN_TX_EXECUTION_CYCLES..=MAX_TX_EXECUTION_CYCLES).contains(&num_cycles) {
474 Err(TransactionExecutorError::InvalidExecutionOptionsCycles {
475 min_cycles: MIN_TX_EXECUTION_CYCLES,
476 max_cycles: MAX_TX_EXECUTION_CYCLES,
477 actual: num_cycles,
478 })
479 } else {
480 Ok(())
481 }
482}
483
484fn map_execution_error(exec_err: ExecutionError) -> TransactionExecutorError {
491 match exec_err {
492 ExecutionError::EventError { ref error, .. } => {
493 match error.downcast_ref::<TransactionKernelError>() {
494 Some(TransactionKernelError::Unauthorized(summary)) => {
495 TransactionExecutorError::Unauthorized(summary.clone())
496 },
497 Some(TransactionKernelError::InsufficientFee { account_balance, tx_fee }) => {
498 TransactionExecutorError::InsufficientFee {
499 account_balance: *account_balance,
500 tx_fee: *tx_fee,
501 }
502 },
503 Some(TransactionKernelError::MissingAuthenticator) => {
504 TransactionExecutorError::MissingAuthenticator
505 },
506 _ => TransactionExecutorError::TransactionProgramExecutionFailed(exec_err),
507 }
508 },
509 _ => TransactionExecutorError::TransactionProgramExecutionFailed(exec_err),
510 }
511}