1use alloc::collections::BTreeSet;
2use alloc::sync::Arc;
3use alloc::vec::Vec;
4
5use miden_lib::errors::TransactionKernelError;
6use miden_lib::transaction::TransactionKernel;
7use miden_objects::account::AccountId;
8use miden_objects::assembly::DefaultSourceManager;
9use miden_objects::assembly::debuginfo::SourceManagerSync;
10use miden_objects::asset::Asset;
11use miden_objects::block::{BlockHeader, BlockNumber};
12use miden_objects::transaction::{
13 AccountInputs,
14 ExecutedTransaction,
15 InputNote,
16 InputNotes,
17 TransactionArgs,
18 TransactionInputs,
19 TransactionScript,
20};
21use miden_objects::vm::StackOutputs;
22use miden_objects::{Felt, MAX_TX_EXECUTION_CYCLES, MIN_TX_EXECUTION_CYCLES};
23use miden_processor::fast::FastProcessor;
24use miden_processor::{AdviceInputs, ExecutionError, StackInputs};
25pub use miden_processor::{ExecutionOptions, MastForestStore};
26
27use super::TransactionExecutorError;
28use crate::auth::TransactionAuthenticator;
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 NoteConsumptionChecker,
41 NoteConsumptionInfo,
42 NoteConsumptionStatus,
43};
44
45pub struct TransactionExecutor<'store, 'auth, STORE: 'store, AUTH: 'auth> {
58 data_store: &'store STORE,
59 authenticator: Option<&'auth AUTH>,
60 source_manager: Arc<dyn SourceManagerSync>,
61 exec_options: ExecutionOptions,
62}
63
64impl<'store, 'auth, STORE, AUTH> TransactionExecutor<'store, 'auth, STORE, AUTH>
65where
66 STORE: DataStore + 'store + Sync,
67 AUTH: TransactionAuthenticator + 'auth + Sync,
68{
69 pub fn new(data_store: &'store STORE) -> Self {
77 const _: () = assert!(MIN_TX_EXECUTION_CYCLES <= MAX_TX_EXECUTION_CYCLES);
78 TransactionExecutor {
79 data_store,
80 authenticator: None,
81 source_manager: Arc::new(DefaultSourceManager::default()),
82 exec_options: ExecutionOptions::new(
83 Some(MAX_TX_EXECUTION_CYCLES),
84 MIN_TX_EXECUTION_CYCLES,
85 false,
86 false,
87 )
88 .expect("Must not fail while max cycles is more than min trace length"),
89 }
90 }
91
92 #[must_use]
97 pub fn with_authenticator(mut self, authenticator: &'auth AUTH) -> Self {
98 self.authenticator = Some(authenticator);
99 self
100 }
101
102 #[must_use]
111 pub fn with_source_manager(mut self, source_manager: Arc<dyn SourceManagerSync>) -> Self {
112 self.source_manager = source_manager;
113 self
114 }
115
116 pub fn with_options(
124 mut self,
125 exec_options: ExecutionOptions,
126 ) -> Result<Self, TransactionExecutorError> {
127 validate_num_cycles(exec_options.max_cycles())?;
128 validate_num_cycles(exec_options.expected_cycles())?;
129
130 self.exec_options = exec_options;
131 Ok(self)
132 }
133
134 #[must_use]
140 pub fn with_debug_mode(mut self) -> Self {
141 self.exec_options = self.exec_options.with_debugging(true);
142 self
143 }
144
145 #[must_use]
152 pub fn with_tracing(mut self) -> Self {
153 self.exec_options = self.exec_options.with_tracing();
154 self
155 }
156
157 pub async fn execute_transaction(
178 &self,
179 account_id: AccountId,
180 block_ref: BlockNumber,
181 notes: InputNotes<InputNote>,
182 tx_args: TransactionArgs,
183 ) -> Result<ExecutedTransaction, TransactionExecutorError> {
184 let (mut host, tx_inputs, stack_inputs, advice_inputs) =
185 self.prepare_transaction(account_id, block_ref, notes, &tx_args, None).await?;
186
187 let processor = FastProcessor::new_debug(stack_inputs.as_slice(), advice_inputs);
188 let (stack_outputs, advice_provider) = processor
189 .execute(&TransactionKernel::main(), &mut host)
190 .await
191 .map_err(map_execution_error)?;
192
193 let (_stack, advice_map, merkle_store) = advice_provider.into_parts();
195 let advice_inputs = AdviceInputs {
196 map: advice_map,
197 store: merkle_store,
198 ..Default::default()
199 };
200
201 build_executed_transaction(advice_inputs, tx_args, tx_inputs, stack_outputs, host)
202 }
203
204 pub async fn execute_tx_view_script(
216 &self,
217 account_id: AccountId,
218 block_ref: BlockNumber,
219 tx_script: TransactionScript,
220 advice_inputs: AdviceInputs,
221 foreign_account_inputs: Vec<AccountInputs>,
222 ) -> Result<[Felt; 16], TransactionExecutorError> {
223 let tx_args = TransactionArgs::new(Default::default(), foreign_account_inputs)
224 .with_tx_script(tx_script);
225
226 let (mut host, _, stack_inputs, advice_inputs) = self
227 .prepare_transaction(
228 account_id,
229 block_ref,
230 InputNotes::default(),
231 &tx_args,
232 Some(advice_inputs),
233 )
234 .await?;
235
236 let processor =
237 FastProcessor::new_with_advice_inputs(stack_inputs.as_slice(), advice_inputs);
238 let (stack_outputs, _advice_provider) = processor
239 .execute(&TransactionKernel::tx_script_main(), &mut host)
240 .await
241 .map_err(TransactionExecutorError::TransactionProgramExecutionFailed)?;
242
243 Ok(*stack_outputs)
244 }
245
246 async fn prepare_transaction(
254 &self,
255 account_id: AccountId,
256 block_ref: BlockNumber,
257 notes: InputNotes<InputNote>,
258 tx_args: &TransactionArgs,
259 init_advice_inputs: Option<AdviceInputs>,
260 ) -> Result<
261 (
262 TransactionExecutorHost<'store, 'auth, STORE, AUTH>,
263 TransactionInputs,
264 StackInputs,
265 AdviceInputs,
266 ),
267 TransactionExecutorError,
268 > {
269 let mut ref_blocks = validate_input_notes(¬es, block_ref)?;
270 ref_blocks.insert(block_ref);
271
272 let (account, seed, ref_block, mmr) = self
273 .data_store
274 .get_transaction_inputs(account_id, ref_blocks)
275 .await
276 .map_err(TransactionExecutorError::FetchTransactionInputsFailed)?;
277
278 validate_account_inputs(tx_args, &ref_block)?;
279
280 let tx_inputs = TransactionInputs::new(account, seed, ref_block, mmr, notes)
281 .map_err(TransactionExecutorError::InvalidTransactionInputs)?;
282
283 let (stack_inputs, advice_inputs) =
284 TransactionKernel::prepare_inputs(&tx_inputs, tx_args, init_advice_inputs)
285 .map_err(TransactionExecutorError::ConflictingAdviceMapEntry)?;
286
287 let stack_inputs = StackInputs::new(stack_inputs.iter().copied().collect()).unwrap();
293
294 let input_notes = tx_inputs.input_notes();
295
296 let script_mast_store = ScriptMastForestStore::new(
297 tx_args.tx_script(),
298 input_notes.iter().map(|n| n.note().script()),
299 );
300
301 let acct_procedure_index_map =
302 AccountProcedureIndexMap::from_transaction_params(&tx_inputs, tx_args, &advice_inputs)
303 .map_err(TransactionExecutorError::TransactionHostCreationFailed)?;
304
305 let host = TransactionExecutorHost::new(
306 &tx_inputs.account().into(),
307 input_notes.clone(),
308 self.data_store,
309 script_mast_store,
310 acct_procedure_index_map,
311 self.authenticator,
312 tx_inputs.block_header().fee_parameters(),
313 self.source_manager.clone(),
314 );
315
316 let advice_inputs = advice_inputs.into_advice_inputs();
317
318 Ok((host, tx_inputs, stack_inputs, advice_inputs))
319 }
320}
321
322fn build_executed_transaction<STORE: DataStore + Sync, AUTH: TransactionAuthenticator + Sync>(
327 mut advice_inputs: AdviceInputs,
328 tx_args: TransactionArgs,
329 tx_inputs: TransactionInputs,
330 stack_outputs: StackOutputs,
331 host: TransactionExecutorHost<STORE, AUTH>,
332) -> Result<ExecutedTransaction, TransactionExecutorError> {
333 let (pre_fee_account_delta, output_notes, generated_signatures, tx_progress) =
336 host.into_parts();
337
338 let tx_outputs =
339 TransactionKernel::from_transaction_parts(&stack_outputs, &advice_inputs, output_notes)
340 .map_err(TransactionExecutorError::TransactionOutputConstructionFailed)?;
341
342 let pre_fee_delta_commitment = pre_fee_account_delta.to_commitment();
343 if tx_outputs.account_delta_commitment != pre_fee_delta_commitment {
344 return Err(TransactionExecutorError::InconsistentAccountDeltaCommitment {
345 in_kernel_commitment: tx_outputs.account_delta_commitment,
346 host_commitment: pre_fee_delta_commitment,
347 });
348 }
349
350 let mut post_fee_account_delta = pre_fee_account_delta;
352 post_fee_account_delta
353 .vault_mut()
354 .remove_asset(Asset::from(tx_outputs.fee))
355 .map_err(TransactionExecutorError::RemoveFeeAssetFromDelta)?;
356
357 let initial_account = tx_inputs.account();
358 let final_account = &tx_outputs.account;
359
360 if initial_account.id() != final_account.id() {
361 return Err(TransactionExecutorError::InconsistentAccountId {
362 input_id: initial_account.id(),
363 output_id: final_account.id(),
364 });
365 }
366
367 let nonce_delta = final_account.nonce() - initial_account.nonce();
369 if nonce_delta != post_fee_account_delta.nonce_delta() {
370 return Err(TransactionExecutorError::InconsistentAccountNonceDelta {
371 expected: nonce_delta,
372 actual: post_fee_account_delta.nonce_delta(),
373 });
374 }
375
376 advice_inputs.map.extend(generated_signatures);
378
379 Ok(ExecutedTransaction::new(
380 tx_inputs,
381 tx_outputs,
382 post_fee_account_delta,
383 tx_args,
384 advice_inputs,
385 tx_progress.into(),
386 ))
387}
388
389fn validate_account_inputs(
391 tx_args: &TransactionArgs,
392 ref_block: &BlockHeader,
393) -> Result<(), TransactionExecutorError> {
394 for foreign_account in tx_args.foreign_account_inputs() {
396 let computed_account_root = foreign_account.compute_account_root().map_err(|err| {
397 TransactionExecutorError::InvalidAccountWitness(foreign_account.id(), err)
398 })?;
399 if computed_account_root != ref_block.account_root() {
400 return Err(TransactionExecutorError::ForeignAccountNotAnchoredInReference(
401 foreign_account.id(),
402 ));
403 }
404 }
405 Ok(())
406}
407
408fn validate_input_notes(
412 notes: &InputNotes<InputNote>,
413 block_ref: BlockNumber,
414) -> Result<BTreeSet<BlockNumber>, TransactionExecutorError> {
415 let mut ref_blocks: BTreeSet<BlockNumber> = BTreeSet::new();
418 for note in notes.iter() {
419 if let Some(location) = note.location() {
420 if location.block_num() > block_ref {
421 return Err(TransactionExecutorError::NoteBlockPastReferenceBlock(
422 note.id(),
423 block_ref,
424 ));
425 }
426 ref_blocks.insert(location.block_num());
427 }
428 }
429
430 Ok(ref_blocks)
431}
432
433fn validate_num_cycles(num_cycles: u32) -> Result<(), TransactionExecutorError> {
435 if !(MIN_TX_EXECUTION_CYCLES..=MAX_TX_EXECUTION_CYCLES).contains(&num_cycles) {
436 Err(TransactionExecutorError::InvalidExecutionOptionsCycles {
437 min_cycles: MIN_TX_EXECUTION_CYCLES,
438 max_cycles: MAX_TX_EXECUTION_CYCLES,
439 actual: num_cycles,
440 })
441 } else {
442 Ok(())
443 }
444}
445
446fn map_execution_error(exec_err: ExecutionError) -> TransactionExecutorError {
453 match exec_err {
454 ExecutionError::EventError { ref error, .. } => {
455 match error.downcast_ref::<TransactionKernelError>() {
456 Some(TransactionKernelError::Unauthorized(summary)) => {
457 TransactionExecutorError::Unauthorized(summary.clone())
458 },
459 Some(TransactionKernelError::InsufficientFee { account_balance, tx_fee }) => {
460 TransactionExecutorError::InsufficientFee {
461 account_balance: *account_balance,
462 tx_fee: *tx_fee,
463 }
464 },
465 Some(TransactionKernelError::MissingAuthenticator) => {
466 TransactionExecutorError::MissingAuthenticator
467 },
468 _ => TransactionExecutorError::TransactionProgramExecutionFailed(exec_err),
469 }
470 },
471 _ => TransactionExecutorError::TransactionProgramExecutionFailed(exec_err),
472 }
473}