1use alloc::{collections::BTreeSet, sync::Arc, vec::Vec};
2
3use miden_lib::transaction::TransactionKernel;
4use miden_objects::{
5 Felt, MAX_TX_EXECUTION_CYCLES, MIN_TX_EXECUTION_CYCLES, ZERO,
6 account::AccountId,
7 assembly::SourceManager,
8 block::{BlockHeader, BlockNumber},
9 note::NoteId,
10 transaction::{
11 AccountInputs, ExecutedTransaction, InputNote, InputNotes, TransactionArgs,
12 TransactionInputs, TransactionScript,
13 },
14 vm::StackOutputs,
15};
16pub use vm_processor::MastForestStore;
17use vm_processor::{AdviceInputs, ExecutionOptions, MemAdviceProvider, Process, RecAdviceProvider};
18use winter_maybe_async::{maybe_async, maybe_await};
19
20use super::{TransactionExecutorError, TransactionHost};
21use crate::auth::TransactionAuthenticator;
22
23mod data_store;
24pub use data_store::DataStore;
25
26mod notes_checker;
27pub use notes_checker::{NoteConsumptionChecker, NoteInputsCheck};
28
29pub struct TransactionExecutor {
42 data_store: Arc<dyn DataStore>,
43 authenticator: Option<Arc<dyn TransactionAuthenticator>>,
44 exec_options: ExecutionOptions,
45}
46
47impl TransactionExecutor {
48 pub fn new(
54 data_store: Arc<dyn DataStore>,
55 authenticator: Option<Arc<dyn TransactionAuthenticator>>,
56 ) -> Self {
57 const _: () = assert!(MIN_TX_EXECUTION_CYCLES <= MAX_TX_EXECUTION_CYCLES);
58
59 Self {
60 data_store,
61 authenticator,
62 exec_options: ExecutionOptions::new(
63 Some(MAX_TX_EXECUTION_CYCLES),
64 MIN_TX_EXECUTION_CYCLES,
65 false,
66 false,
67 )
68 .expect("Must not fail while max cycles is more than min trace length"),
69 }
70 }
71
72 pub fn with_debug_mode(mut self) -> Self {
78 self.exec_options = self.exec_options.with_debugging(true);
79 self
80 }
81
82 pub fn with_tracing(mut self) -> Self {
88 self.exec_options = self.exec_options.with_tracing();
89 self
90 }
91
92 #[maybe_async]
120 pub fn execute_transaction(
121 &self,
122 account_id: AccountId,
123 block_ref: BlockNumber,
124 notes: InputNotes<InputNote>,
125 tx_args: TransactionArgs,
126 source_manager: Arc<dyn SourceManager>,
127 ) -> Result<ExecutedTransaction, TransactionExecutorError> {
128 let mut ref_blocks = validate_input_notes(¬es, block_ref)?;
129 ref_blocks.insert(block_ref);
130
131 let (account, seed, ref_block, mmr) =
132 maybe_await!(self.data_store.get_transaction_inputs(account_id, ref_blocks))
133 .map_err(TransactionExecutorError::FetchTransactionInputsFailed)?;
134
135 validate_account_inputs(&tx_args, &ref_block)?;
136
137 let tx_inputs = TransactionInputs::new(account, seed, ref_block, mmr, notes)
138 .map_err(TransactionExecutorError::InvalidTransactionInputs)?;
139
140 let (stack_inputs, advice_inputs) =
141 TransactionKernel::prepare_inputs(&tx_inputs, &tx_args, None)
142 .map_err(TransactionExecutorError::InvalidTransactionInputs)?;
143
144 let advice_recorder: RecAdviceProvider = advice_inputs.into();
145
146 let mut host = TransactionHost::new(
147 tx_inputs.account().into(),
148 advice_recorder,
149 self.data_store.clone(),
150 self.authenticator.clone(),
151 tx_args.foreign_account_code_commitments(),
152 )
153 .map_err(TransactionExecutorError::TransactionHostCreationFailed)?;
154
155 let result = vm_processor::execute(
157 &TransactionKernel::main(),
158 stack_inputs,
159 &mut host,
160 self.exec_options,
161 source_manager,
162 )
163 .map_err(TransactionExecutorError::TransactionProgramExecutionFailed)?;
164
165 build_executed_transaction(tx_args, tx_inputs, result.stack_outputs().clone(), host)
166 }
167
168 #[maybe_async]
187 pub fn execute_tx_view_script(
188 &self,
189 account_id: AccountId,
190 block_ref: BlockNumber,
191 tx_script: TransactionScript,
192 advice_inputs: AdviceInputs,
193 foreign_account_inputs: Vec<AccountInputs>,
194 source_manager: Arc<dyn SourceManager>,
195 ) -> Result<[Felt; 16], TransactionExecutorError> {
196 let ref_blocks = [block_ref].into_iter().collect();
197 let (account, seed, ref_block, mmr) =
198 maybe_await!(self.data_store.get_transaction_inputs(account_id, ref_blocks))
199 .map_err(TransactionExecutorError::FetchTransactionInputsFailed)?;
200 let tx_args = TransactionArgs::new(
201 Some(tx_script.clone()),
202 None,
203 Default::default(),
204 foreign_account_inputs,
205 );
206
207 validate_account_inputs(&tx_args, &ref_block)?;
208
209 let tx_inputs = TransactionInputs::new(account, seed, ref_block, mmr, Default::default())
210 .map_err(TransactionExecutorError::InvalidTransactionInputs)?;
211
212 let (stack_inputs, advice_inputs) =
213 TransactionKernel::prepare_inputs(&tx_inputs, &tx_args, Some(advice_inputs))
214 .map_err(TransactionExecutorError::InvalidTransactionInputs)?;
215 let advice_recorder: RecAdviceProvider = advice_inputs.into();
216
217 let mut host = TransactionHost::new(
218 tx_inputs.account().into(),
219 advice_recorder,
220 self.data_store.clone(),
221 self.authenticator.clone(),
222 tx_args.foreign_account_code_commitments(),
223 )
224 .map_err(TransactionExecutorError::TransactionHostCreationFailed)?;
225
226 let mut process = Process::new(
227 TransactionKernel::tx_script_main().kernel().clone(),
228 stack_inputs,
229 self.exec_options,
230 )
231 .with_source_manager(source_manager);
232 let stack_outputs = process
233 .execute(&TransactionKernel::tx_script_main(), &mut host)
234 .map_err(TransactionExecutorError::TransactionProgramExecutionFailed)?;
235
236 Ok(*stack_outputs)
237 }
238
239 #[maybe_async]
259 pub(crate) fn try_execute_notes(
260 &self,
261 account_id: AccountId,
262 block_ref: BlockNumber,
263 notes: InputNotes<InputNote>,
264 tx_args: TransactionArgs,
265 source_manager: Arc<dyn SourceManager>,
266 ) -> Result<NoteAccountExecution, TransactionExecutorError> {
267 let mut ref_blocks = validate_input_notes(¬es, block_ref)?;
268 ref_blocks.insert(block_ref);
269
270 let (account, seed, ref_block, mmr) =
271 maybe_await!(self.data_store.get_transaction_inputs(account_id, ref_blocks))
272 .map_err(TransactionExecutorError::FetchTransactionInputsFailed)?;
273
274 validate_account_inputs(&tx_args, &ref_block)?;
275
276 let tx_inputs = TransactionInputs::new(account, seed, ref_block, mmr, notes)
277 .map_err(TransactionExecutorError::InvalidTransactionInputs)?;
278
279 let (stack_inputs, advice_inputs) =
280 TransactionKernel::prepare_inputs(&tx_inputs, &tx_args, None)
281 .map_err(TransactionExecutorError::InvalidTransactionInputs)?;
282
283 let advice_provider: MemAdviceProvider = advice_inputs.into();
284
285 let mut host = TransactionHost::new(
286 tx_inputs.account().into(),
287 advice_provider,
288 self.data_store.clone(),
289 self.authenticator.clone(),
290 tx_args.foreign_account_code_commitments(),
291 )
292 .map_err(TransactionExecutorError::TransactionHostCreationFailed)?;
293
294 let result = vm_processor::execute(
296 &TransactionKernel::main(),
297 stack_inputs,
298 &mut host,
299 self.exec_options,
300 source_manager,
301 )
302 .map_err(TransactionExecutorError::TransactionProgramExecutionFailed);
303
304 match result {
305 Ok(_) => Ok(NoteAccountExecution::Success),
306 Err(tx_execution_error) => {
307 let notes = host.tx_progress().note_execution();
308
309 if notes.is_empty() {
312 return Err(tx_execution_error);
313 }
314
315 let ((last_note, last_note_interval), success_notes) = notes
316 .split_last()
317 .expect("notes vector should not be empty because we just checked");
318
319 if last_note_interval.end().is_some() {
322 return Err(tx_execution_error);
323 }
324
325 Ok(NoteAccountExecution::Failure {
326 failed_note_id: *last_note,
327 successful_notes: success_notes.iter().map(|(note, _)| *note).collect(),
328 error: Some(tx_execution_error),
329 })
330 },
331 }
332 }
333}
334
335fn build_executed_transaction(
340 tx_args: TransactionArgs,
341 tx_inputs: TransactionInputs,
342 stack_outputs: StackOutputs,
343 host: TransactionHost<RecAdviceProvider>,
344) -> Result<ExecutedTransaction, TransactionExecutorError> {
345 let (advice_recorder, account_delta, output_notes, generated_signatures, tx_progress) =
346 host.into_parts();
347
348 let (mut advice_witness, _, map, _store) = advice_recorder.finalize();
349
350 let tx_outputs =
351 TransactionKernel::from_transaction_parts(&stack_outputs, &map.into(), output_notes)
352 .map_err(TransactionExecutorError::TransactionOutputConstructionFailed)?;
353
354 let final_account = &tx_outputs.account;
355
356 let initial_account = tx_inputs.account();
357
358 if initial_account.id() != final_account.id() {
359 return Err(TransactionExecutorError::InconsistentAccountId {
360 input_id: initial_account.id(),
361 output_id: final_account.id(),
362 });
363 }
364
365 let nonce_delta = final_account.nonce() - initial_account.nonce();
367 if nonce_delta == ZERO {
368 if account_delta.nonce().is_some() {
369 return Err(TransactionExecutorError::InconsistentAccountNonceDelta {
370 expected: None,
371 actual: account_delta.nonce(),
372 });
373 }
374 } else if final_account.nonce() != account_delta.nonce().unwrap_or_default() {
375 return Err(TransactionExecutorError::InconsistentAccountNonceDelta {
376 expected: Some(final_account.nonce()),
377 actual: account_delta.nonce(),
378 });
379 }
380
381 advice_witness.extend_map(generated_signatures);
383
384 Ok(ExecutedTransaction::new(
385 tx_inputs,
386 tx_outputs,
387 account_delta,
388 tx_args,
389 advice_witness,
390 tx_progress.into(),
391 ))
392}
393
394fn validate_account_inputs(
396 tx_args: &TransactionArgs,
397 ref_block: &BlockHeader,
398) -> Result<(), TransactionExecutorError> {
399 for foreign_account in tx_args.foreign_account_inputs() {
401 let computed_account_root = foreign_account.compute_account_root().map_err(|err| {
402 TransactionExecutorError::InvalidAccountWitness(foreign_account.id(), err)
403 })?;
404 if computed_account_root != ref_block.account_root() {
405 return Err(TransactionExecutorError::ForeignAccountNotAnchoredInReference(
406 foreign_account.id(),
407 ));
408 }
409 }
410 Ok(())
411}
412
413fn validate_input_notes(
417 notes: &InputNotes<InputNote>,
418 block_ref: BlockNumber,
419) -> Result<BTreeSet<BlockNumber>, TransactionExecutorError> {
420 let mut ref_blocks: BTreeSet<BlockNumber> = BTreeSet::new();
423 for note in notes.iter() {
424 if let Some(location) = note.location() {
425 if location.block_num() > block_ref {
426 return Err(TransactionExecutorError::NoteBlockPastReferenceBlock(
427 note.id(),
428 block_ref,
429 ));
430 }
431 ref_blocks.insert(location.block_num());
432 }
433 }
434
435 Ok(ref_blocks)
436}
437
438#[derive(Debug)]
447pub enum NoteAccountExecution {
448 Success,
449 Failure {
450 failed_note_id: NoteId,
451 successful_notes: Vec<NoteId>,
452 error: Option<TransactionExecutorError>,
453 },
454}