1use alloc::collections::BTreeSet;
2use alloc::sync::Arc;
3
4use miden_lib::transaction::TransactionKernel;
5use miden_objects::account::AccountId;
6use miden_objects::assembly::DefaultSourceManager;
7use miden_objects::assembly::debuginfo::SourceManagerSync;
8use miden_objects::asset::Asset;
9use miden_objects::block::BlockNumber;
10use miden_objects::transaction::{
11 ExecutedTransaction,
12 InputNote,
13 InputNotes,
14 TransactionArgs,
15 TransactionInputs,
16 TransactionScript,
17};
18use miden_objects::vm::StackOutputs;
19use miden_objects::{Felt, MAX_TX_EXECUTION_CYCLES, MIN_TX_EXECUTION_CYCLES};
20use miden_processor::fast::FastProcessor;
21use miden_processor::{AdviceInputs, ExecutionError, StackInputs};
22pub use miden_processor::{ExecutionOptions, MastForestStore};
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 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 tx_inputs = TransactionInputs::new(account, block_header, blockchain, input_notes)
267 .map_err(TransactionExecutorError::InvalidTransactionInputs)?
268 .with_tx_args(tx_args);
269
270 Ok(tx_inputs)
271 }
272
273 async fn prepare_transaction(
278 &self,
279 tx_inputs: &TransactionInputs,
280 ) -> Result<
281 (TransactionExecutorHost<'store, 'auth, STORE, AUTH>, StackInputs, AdviceInputs),
282 TransactionExecutorError,
283 > {
284 let (stack_inputs, tx_advice_inputs) = TransactionKernel::prepare_inputs(tx_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_inputs.tx_script(),
298 input_notes.iter().map(|n| n.note().script()),
299 );
300
301 let account_procedure_index_map =
304 AccountProcedureIndexMap::new([tx_inputs.account().code()])
305 .map_err(TransactionExecutorError::TransactionHostCreationFailed)?;
306
307 let host = TransactionExecutorHost::new(
308 tx_inputs.account(),
309 input_notes.clone(),
310 self.data_store,
311 script_mast_store,
312 account_procedure_index_map,
313 self.authenticator,
314 tx_inputs.block_header().block_num(),
315 self.source_manager.clone(),
316 );
317
318 let advice_inputs = tx_advice_inputs.into_advice_inputs();
319
320 Ok((host, stack_inputs, advice_inputs))
321 }
322}
323
324fn build_executed_transaction<STORE: DataStore + Sync, AUTH: TransactionAuthenticator + Sync>(
329 mut advice_inputs: AdviceInputs,
330 tx_inputs: TransactionInputs,
331 stack_outputs: StackOutputs,
332 host: TransactionExecutorHost<STORE, AUTH>,
333) -> Result<ExecutedTransaction, TransactionExecutorError> {
334 let (
337 pre_fee_account_delta,
338 _input_notes,
339 output_notes,
340 accessed_foreign_account_code,
341 generated_signatures,
342 tx_progress,
343 ) = host.into_parts();
344
345 let tx_outputs =
346 TransactionKernel::from_transaction_parts(&stack_outputs, &advice_inputs, output_notes)
347 .map_err(TransactionExecutorError::TransactionOutputConstructionFailed)?;
348
349 let pre_fee_delta_commitment = pre_fee_account_delta.to_commitment();
350 if tx_outputs.account_delta_commitment != pre_fee_delta_commitment {
351 return Err(TransactionExecutorError::InconsistentAccountDeltaCommitment {
352 in_kernel_commitment: tx_outputs.account_delta_commitment,
353 host_commitment: pre_fee_delta_commitment,
354 });
355 }
356
357 let mut post_fee_account_delta = pre_fee_account_delta;
359 post_fee_account_delta
360 .vault_mut()
361 .remove_asset(Asset::from(tx_outputs.fee))
362 .map_err(TransactionExecutorError::RemoveFeeAssetFromDelta)?;
363
364 let initial_account = tx_inputs.account();
365 let final_account = &tx_outputs.account;
366
367 if initial_account.id() != final_account.id() {
368 return Err(TransactionExecutorError::InconsistentAccountId {
369 input_id: initial_account.id(),
370 output_id: final_account.id(),
371 });
372 }
373
374 let nonce_delta = final_account.nonce() - initial_account.nonce();
376 if nonce_delta != post_fee_account_delta.nonce_delta() {
377 return Err(TransactionExecutorError::InconsistentAccountNonceDelta {
378 expected: nonce_delta,
379 actual: post_fee_account_delta.nonce_delta(),
380 });
381 }
382
383 advice_inputs.map.extend(generated_signatures);
385
386 let tx_inputs = tx_inputs
389 .with_foreign_account_code(accessed_foreign_account_code)
390 .with_advice_inputs(advice_inputs);
391
392 Ok(ExecutedTransaction::new(
393 tx_inputs,
394 tx_outputs,
395 post_fee_account_delta,
396 tx_progress.into(),
397 ))
398}
399
400fn validate_input_notes(
404 notes: &InputNotes<InputNote>,
405 block_ref: BlockNumber,
406) -> Result<BTreeSet<BlockNumber>, TransactionExecutorError> {
407 let mut ref_blocks: BTreeSet<BlockNumber> = BTreeSet::new();
410 for note in notes.iter() {
411 if let Some(location) = note.location() {
412 if location.block_num() > block_ref {
413 return Err(TransactionExecutorError::NoteBlockPastReferenceBlock(
414 note.id(),
415 block_ref,
416 ));
417 }
418 ref_blocks.insert(location.block_num());
419 }
420 }
421
422 Ok(ref_blocks)
423}
424
425fn validate_num_cycles(num_cycles: u32) -> Result<(), TransactionExecutorError> {
427 if !(MIN_TX_EXECUTION_CYCLES..=MAX_TX_EXECUTION_CYCLES).contains(&num_cycles) {
428 Err(TransactionExecutorError::InvalidExecutionOptionsCycles {
429 min_cycles: MIN_TX_EXECUTION_CYCLES,
430 max_cycles: MAX_TX_EXECUTION_CYCLES,
431 actual: num_cycles,
432 })
433 } else {
434 Ok(())
435 }
436}
437
438fn map_execution_error(exec_err: ExecutionError) -> TransactionExecutorError {
445 match exec_err {
446 ExecutionError::EventError { ref error, .. } => {
447 match error.downcast_ref::<TransactionKernelError>() {
448 Some(TransactionKernelError::Unauthorized(summary)) => {
449 TransactionExecutorError::Unauthorized(summary.clone())
450 },
451 Some(TransactionKernelError::InsufficientFee { account_balance, tx_fee }) => {
452 TransactionExecutorError::InsufficientFee {
453 account_balance: *account_balance,
454 tx_fee: *tx_fee,
455 }
456 },
457 Some(TransactionKernelError::MissingAuthenticator) => {
458 TransactionExecutorError::MissingAuthenticator
459 },
460 _ => TransactionExecutorError::TransactionProgramExecutionFailed(exec_err),
461 }
462 },
463 _ => TransactionExecutorError::TransactionProgramExecutionFailed(exec_err),
464 }
465}