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::{FailedNote, NoteConsumptionChecker, NoteConsumptionInfo};
39
40pub struct TransactionExecutor<'store, 'auth, STORE: 'store, AUTH: 'auth> {
53 data_store: &'store STORE,
54 authenticator: Option<&'auth AUTH>,
55 source_manager: Arc<dyn SourceManagerSync>,
56 exec_options: ExecutionOptions,
57}
58
59impl<'store, 'auth, STORE, AUTH> TransactionExecutor<'store, 'auth, STORE, AUTH>
60where
61 STORE: DataStore + 'store + Sync,
62 AUTH: TransactionAuthenticator + 'auth + Sync,
63{
64 pub fn new(data_store: &'store STORE) -> Self {
72 const _: () = assert!(MIN_TX_EXECUTION_CYCLES <= MAX_TX_EXECUTION_CYCLES);
73 TransactionExecutor {
74 data_store,
75 authenticator: None,
76 source_manager: Arc::new(DefaultSourceManager::default()),
77 exec_options: ExecutionOptions::new(
78 Some(MAX_TX_EXECUTION_CYCLES),
79 MIN_TX_EXECUTION_CYCLES,
80 false,
81 false,
82 )
83 .expect("Must not fail while max cycles is more than min trace length"),
84 }
85 }
86
87 #[must_use]
92 pub fn with_authenticator(mut self, authenticator: &'auth AUTH) -> Self {
93 self.authenticator = Some(authenticator);
94 self
95 }
96
97 #[must_use]
106 pub fn with_source_manager(mut self, source_manager: Arc<dyn SourceManagerSync>) -> Self {
107 self.source_manager = source_manager;
108 self
109 }
110
111 pub fn with_options(
119 mut self,
120 exec_options: ExecutionOptions,
121 ) -> Result<Self, TransactionExecutorError> {
122 validate_num_cycles(exec_options.max_cycles())?;
123 validate_num_cycles(exec_options.expected_cycles())?;
124
125 self.exec_options = exec_options;
126 Ok(self)
127 }
128
129 #[must_use]
135 pub fn with_debug_mode(mut self) -> Self {
136 self.exec_options = self.exec_options.with_debugging(true);
137 self
138 }
139
140 #[must_use]
147 pub fn with_tracing(mut self) -> Self {
148 self.exec_options = self.exec_options.with_tracing();
149 self
150 }
151
152 pub async fn execute_transaction(
173 &self,
174 account_id: AccountId,
175 block_ref: BlockNumber,
176 notes: InputNotes<InputNote>,
177 tx_args: TransactionArgs,
178 ) -> Result<ExecutedTransaction, TransactionExecutorError> {
179 let (mut host, tx_inputs, stack_inputs, advice_inputs) =
180 self.prepare_transaction(account_id, block_ref, notes, &tx_args, None).await?;
181
182 let processor = FastProcessor::new_debug(stack_inputs.as_slice(), advice_inputs);
183 let (stack_outputs, advice_provider) = processor
184 .execute(&TransactionKernel::main(), &mut host)
185 .await
186 .map_err(map_execution_error)?;
187
188 let (_stack, advice_map, merkle_store) = advice_provider.into_parts();
190 let advice_inputs = AdviceInputs {
191 map: advice_map,
192 store: merkle_store,
193 ..Default::default()
194 };
195
196 build_executed_transaction(advice_inputs, tx_args, tx_inputs, stack_outputs, host)
197 }
198
199 pub async fn execute_tx_view_script(
211 &self,
212 account_id: AccountId,
213 block_ref: BlockNumber,
214 tx_script: TransactionScript,
215 advice_inputs: AdviceInputs,
216 foreign_account_inputs: Vec<AccountInputs>,
217 ) -> Result<[Felt; 16], TransactionExecutorError> {
218 let tx_args = TransactionArgs::new(Default::default(), foreign_account_inputs)
219 .with_tx_script(tx_script);
220
221 let (mut host, _, stack_inputs, advice_inputs) = self
222 .prepare_transaction(
223 account_id,
224 block_ref,
225 InputNotes::default(),
226 &tx_args,
227 Some(advice_inputs),
228 )
229 .await?;
230
231 let processor =
232 FastProcessor::new_with_advice_inputs(stack_inputs.as_slice(), advice_inputs);
233 let (stack_outputs, _advice_provider) = processor
234 .execute(&TransactionKernel::tx_script_main(), &mut host)
235 .await
236 .map_err(TransactionExecutorError::TransactionProgramExecutionFailed)?;
237
238 Ok(*stack_outputs)
239 }
240
241 async fn prepare_transaction(
249 &self,
250 account_id: AccountId,
251 block_ref: BlockNumber,
252 notes: InputNotes<InputNote>,
253 tx_args: &TransactionArgs,
254 init_advice_inputs: Option<AdviceInputs>,
255 ) -> Result<
256 (
257 TransactionExecutorHost<'store, 'auth, STORE, AUTH>,
258 TransactionInputs,
259 StackInputs,
260 AdviceInputs,
261 ),
262 TransactionExecutorError,
263 > {
264 let mut ref_blocks = validate_input_notes(¬es, block_ref)?;
265 ref_blocks.insert(block_ref);
266
267 let (account, seed, ref_block, mmr) = self
268 .data_store
269 .get_transaction_inputs(account_id, ref_blocks)
270 .await
271 .map_err(TransactionExecutorError::FetchTransactionInputsFailed)?;
272
273 validate_account_inputs(tx_args, &ref_block)?;
274
275 let tx_inputs = TransactionInputs::new(account, seed, ref_block, mmr, notes)
276 .map_err(TransactionExecutorError::InvalidTransactionInputs)?;
277
278 let (stack_inputs, advice_inputs) =
279 TransactionKernel::prepare_inputs(&tx_inputs, tx_args, init_advice_inputs)
280 .map_err(TransactionExecutorError::ConflictingAdviceMapEntry)?;
281
282 let stack_inputs = StackInputs::new(stack_inputs.iter().copied().collect()).unwrap();
288
289 let input_notes = tx_inputs.input_notes();
290
291 let script_mast_store = ScriptMastForestStore::new(
292 tx_args.tx_script(),
293 input_notes.iter().map(|n| n.note().script()),
294 );
295
296 let acct_procedure_index_map =
297 AccountProcedureIndexMap::from_transaction_params(&tx_inputs, tx_args, &advice_inputs)
298 .map_err(TransactionExecutorError::TransactionHostCreationFailed)?;
299
300 let host = TransactionExecutorHost::new(
301 &tx_inputs.account().into(),
302 input_notes.clone(),
303 self.data_store,
304 script_mast_store,
305 acct_procedure_index_map,
306 self.authenticator,
307 tx_inputs.block_header().fee_parameters(),
308 self.source_manager.clone(),
309 );
310
311 let advice_inputs = advice_inputs.into_advice_inputs();
312
313 Ok((host, tx_inputs, stack_inputs, advice_inputs))
314 }
315}
316
317fn build_executed_transaction<STORE: DataStore + Sync, AUTH: TransactionAuthenticator + Sync>(
322 mut advice_inputs: AdviceInputs,
323 tx_args: TransactionArgs,
324 tx_inputs: TransactionInputs,
325 stack_outputs: StackOutputs,
326 host: TransactionExecutorHost<STORE, AUTH>,
327) -> Result<ExecutedTransaction, TransactionExecutorError> {
328 let (pre_fee_account_delta, output_notes, generated_signatures, tx_progress) =
331 host.into_parts();
332
333 let tx_outputs =
334 TransactionKernel::from_transaction_parts(&stack_outputs, &advice_inputs, output_notes)
335 .map_err(TransactionExecutorError::TransactionOutputConstructionFailed)?;
336
337 let pre_fee_delta_commitment = pre_fee_account_delta.to_commitment();
338 if tx_outputs.account_delta_commitment != pre_fee_delta_commitment {
339 return Err(TransactionExecutorError::InconsistentAccountDeltaCommitment {
340 in_kernel_commitment: tx_outputs.account_delta_commitment,
341 host_commitment: pre_fee_delta_commitment,
342 });
343 }
344
345 let mut post_fee_account_delta = pre_fee_account_delta;
347 post_fee_account_delta
348 .vault_mut()
349 .remove_asset(Asset::from(tx_outputs.fee))
350 .map_err(TransactionExecutorError::RemoveFeeAssetFromDelta)?;
351
352 let initial_account = tx_inputs.account();
353 let final_account = &tx_outputs.account;
354
355 if initial_account.id() != final_account.id() {
356 return Err(TransactionExecutorError::InconsistentAccountId {
357 input_id: initial_account.id(),
358 output_id: final_account.id(),
359 });
360 }
361
362 let nonce_delta = final_account.nonce() - initial_account.nonce();
364 if nonce_delta != post_fee_account_delta.nonce_delta() {
365 return Err(TransactionExecutorError::InconsistentAccountNonceDelta {
366 expected: nonce_delta,
367 actual: post_fee_account_delta.nonce_delta(),
368 });
369 }
370
371 advice_inputs.map.extend(generated_signatures);
373
374 Ok(ExecutedTransaction::new(
375 tx_inputs,
376 tx_outputs,
377 post_fee_account_delta,
378 tx_args,
379 advice_inputs,
380 tx_progress.into(),
381 ))
382}
383
384fn validate_account_inputs(
386 tx_args: &TransactionArgs,
387 ref_block: &BlockHeader,
388) -> Result<(), TransactionExecutorError> {
389 for foreign_account in tx_args.foreign_account_inputs() {
391 let computed_account_root = foreign_account.compute_account_root().map_err(|err| {
392 TransactionExecutorError::InvalidAccountWitness(foreign_account.id(), err)
393 })?;
394 if computed_account_root != ref_block.account_root() {
395 return Err(TransactionExecutorError::ForeignAccountNotAnchoredInReference(
396 foreign_account.id(),
397 ));
398 }
399 }
400 Ok(())
401}
402
403fn validate_input_notes(
407 notes: &InputNotes<InputNote>,
408 block_ref: BlockNumber,
409) -> Result<BTreeSet<BlockNumber>, TransactionExecutorError> {
410 let mut ref_blocks: BTreeSet<BlockNumber> = BTreeSet::new();
413 for note in notes.iter() {
414 if let Some(location) = note.location() {
415 if location.block_num() > block_ref {
416 return Err(TransactionExecutorError::NoteBlockPastReferenceBlock(
417 note.id(),
418 block_ref,
419 ));
420 }
421 ref_blocks.insert(location.block_num());
422 }
423 }
424
425 Ok(ref_blocks)
426}
427
428fn validate_num_cycles(num_cycles: u32) -> Result<(), TransactionExecutorError> {
430 if !(MIN_TX_EXECUTION_CYCLES..=MAX_TX_EXECUTION_CYCLES).contains(&num_cycles) {
431 Err(TransactionExecutorError::InvalidExecutionOptionsCycles {
432 min_cycles: MIN_TX_EXECUTION_CYCLES,
433 max_cycles: MAX_TX_EXECUTION_CYCLES,
434 actual: num_cycles,
435 })
436 } else {
437 Ok(())
438 }
439}
440
441fn map_execution_error(exec_err: ExecutionError) -> TransactionExecutorError {
448 match exec_err {
449 ExecutionError::EventError { ref error, .. } => {
450 match error.downcast_ref::<TransactionKernelError>() {
451 Some(TransactionKernelError::Unauthorized(summary)) => {
452 TransactionExecutorError::Unauthorized(summary.clone())
453 },
454 Some(TransactionKernelError::InsufficientFee { account_balance, tx_fee }) => {
455 TransactionExecutorError::InsufficientFee {
456 account_balance: *account_balance,
457 tx_fee: *tx_fee,
458 }
459 },
460 _ => TransactionExecutorError::TransactionProgramExecutionFailed(exec_err),
461 }
462 },
463 _ => TransactionExecutorError::TransactionProgramExecutionFailed(exec_err),
464 }
465}