miden_tx/executor/
notes_checker.rs

1use alloc::sync::Arc;
2
3use miden_lib::{
4    account::interface::NoteAccountCompatibility, note::well_known_note::WellKnownNote,
5};
6use miden_objects::{
7    account::AccountId,
8    assembly::SourceManager,
9    block::BlockNumber,
10    note::NoteId,
11    transaction::{InputNote, InputNotes, TransactionArgs},
12};
13use winter_maybe_async::{maybe_async, maybe_await};
14
15use super::{NoteAccountExecution, TransactionExecutor, TransactionExecutorError};
16
17/// This struct performs input notes check against provided target account.
18///
19/// The check is performed using the [NoteConsumptionChecker::check_notes_consumability] procedure.
20/// Essentially runs the transaction to make sure that provided input notes could be consumed by the
21/// account.
22pub struct NoteConsumptionChecker<'a>(&'a TransactionExecutor);
23
24impl<'a> NoteConsumptionChecker<'a> {
25    /// Creates a new [`NoteConsumptionChecker`] instance with the given transaction executor.
26    pub fn new(tx_executor: &'a TransactionExecutor) -> Self {
27        NoteConsumptionChecker(tx_executor)
28    }
29
30    /// Checks whether the provided input notes could be consumed by the provided account.
31    ///
32    /// This check consists of two main steps:
33    /// - Statically check the notes: if all notes are either `P2ID` or `P2IDR` notes with correct
34    ///   inputs, return `NoteAccountExecution::Success`.
35    /// - Execute the transaction:
36    ///   - Returns `NoteAccountExecution::Success` if the execution was successful.
37    ///   - Returns `NoteAccountExecution::Failure` if some note returned an error. The fields
38    ///     associated with `Failure` variant contains the ID of the failed note, a vector of IDs of
39    ///     the notes, which were successfully executed, and the [TransactionExecutorError] if the
40    ///     check failed durning the execution stage.
41    #[maybe_async]
42    pub fn check_notes_consumability(
43        &self,
44        target_account_id: AccountId,
45        block_ref: BlockNumber,
46        input_notes: InputNotes<InputNote>,
47        tx_args: TransactionArgs,
48        source_manager: Arc<dyn SourceManager>,
49    ) -> Result<NoteAccountExecution, TransactionExecutorError> {
50        // Check input notes
51        // ----------------------------------------------------------------------------------------
52
53        let mut successful_notes = vec![];
54        for note in input_notes.iter() {
55            if let Some(well_known_note) = WellKnownNote::from_note(note.note()) {
56                if let WellKnownNote::SWAP = well_known_note {
57                    // if we encountered a SWAP note, then we have to execute the transaction
58                    // anyway, but we should continue iterating to make sure that there are no
59                    // P2ID(R) notes which return a `No`
60                    continue;
61                }
62
63                match well_known_note.check_note_inputs(note.note(), target_account_id, block_ref) {
64                    NoteAccountCompatibility::No => {
65                        // if the check failed, return a `Failure` with the vector of successfully
66                        // checked `P2ID` and `P2IDR` notes
67                        return Ok(NoteAccountExecution::Failure {
68                            failed_note_id: note.id(),
69                            successful_notes,
70                            error: None,
71                        });
72                    },
73                    // this branch is unreachable, since we are handling the SWAP note separately,
74                    // but as an extra precaution continue iterating over the notes and run the
75                    // transaction to make sure the note which returned "Maybe" could be consumed
76                    NoteAccountCompatibility::Maybe => continue,
77                    NoteAccountCompatibility::Yes => {
78                        // put the successfully checked `P2ID` or `P2IDR` note to the vector
79                        successful_notes.push(note.id());
80                    },
81                }
82            } else {
83                // if we encountered not a well known note, then we have to execute the transaction
84                // anyway, but we should continue iterating to make sure that there are no
85                // P2ID(R) notes which return a `No`
86                continue;
87            }
88        }
89
90        // if all checked notes turned out to be either `P2ID` or `P2IDR` notes and all of them
91        // passed, then we could safely return the `Success`
92        if successful_notes.len() == input_notes.num_notes() {
93            return Ok(NoteAccountExecution::Success);
94        }
95
96        // Execute transaction
97        // ----------------------------------------------------------------------------------------
98        maybe_await!(self.0.try_execute_notes(
99            target_account_id,
100            block_ref,
101            input_notes,
102            tx_args,
103            source_manager
104        ))
105    }
106}
107
108/// Helper enum for getting a result of the well known note inputs check.
109#[derive(Debug, PartialEq)]
110pub enum NoteInputsCheck {
111    Maybe,
112    No { failed_note_id: NoteId },
113}