Skip to main content

miden_tx/executor/notes_checker/
checker_utils.rs

1use alloc::collections::BTreeMap;
2use alloc::vec::Vec;
3
4use miden_protocol::note::{Note, NoteId};
5use miden_standards::note::{FeeSponsorshipNote, NoteConsumptionStatus};
6
7use crate::TransactionExecutorError;
8
9// CONSTANTS
10// ================================================================================================
11
12/// Maximum number of notes that can be checked at once.
13///
14/// Fixed at an amount that should keep each run of note consumption checking to a maximum of ~50ms.
15pub const MAX_NUM_CHECKER_NOTES: usize = 20;
16
17// NOTE CONSUMPTION INFO
18// ================================================================================================
19
20/// Represents a successfully consumed note along with the number of cycles it took to execute.
21#[derive(Debug)]
22pub struct SuccessfulNote {
23    note: Note,
24    num_cycles: usize,
25}
26
27impl SuccessfulNote {
28    /// Constructs a new `SuccessfulNote`.
29    pub fn new(note: Note, num_cycles: usize) -> Self {
30        Self { note, num_cycles }
31    }
32
33    /// Returns a reference to the note.
34    pub fn note(&self) -> &Note {
35        &self.note
36    }
37
38    /// Returns the number of cycles consumed during execution.
39    pub fn num_cycles(&self) -> usize {
40        self.num_cycles
41    }
42}
43
44/// Contains the reason why a note is not included in the successful set.
45///
46/// The variants separate the note the executor blamed from the ones that merely shared its bundle.
47#[derive(Debug)]
48#[non_exhaustive]
49pub enum NoteFailure {
50    /// The note the executor blamed for the failure.
51    Blamed {
52        /// The error the failing execution produced.
53        error: TransactionExecutorError,
54        /// The number of cycles consumed by the note before it failed.
55        ///
56        /// This is `Some` when the failure was due to exceeding the cycle limit, and `None` for
57        /// other error types where the cycle count is not meaningful.
58        num_cycles: Option<usize>,
59    },
60    /// The note was rejected along with the bundle of the note it is bound to, and may well be
61    /// consumable in a different set.
62    Collateral {
63        /// The blamed note this one shared its bundle with.
64        blamed_by: NoteId,
65    },
66}
67
68/// Represents a failed note consumption.
69#[derive(Debug)]
70pub struct FailedNote {
71    note: Note,
72    failure: NoteFailure,
73}
74
75impl FailedNote {
76    /// Constructs a new `FailedNote`.
77    pub fn new(note: Note, failure: NoteFailure) -> Self {
78        Self { note, failure }
79    }
80
81    /// Returns a reference to the note.
82    pub fn note(&self) -> &Note {
83        &self.note
84    }
85
86    /// Returns why the note was not consumed.
87    pub fn failure(&self) -> &NoteFailure {
88        &self.failure
89    }
90
91    /// Returns `true` if the executor blamed this note for the failure.
92    pub fn is_blamed(&self) -> bool {
93        matches!(self.failure, NoteFailure::Blamed { .. })
94    }
95
96    /// Returns `true` if this note was rejected along with the bundle of the note it is bound to.
97    ///
98    /// This is not the negation of [`FailedNote::is_blamed`]: [`NoteFailure`] is non-exhaustive, so
99    /// a note may in future fail for a reason that is neither.
100    pub fn is_collateral(&self) -> bool {
101        matches!(self.failure, NoteFailure::Collateral { .. })
102    }
103
104    /// Returns a reference to the error, if this note was the one blamed for the failure. `None`
105    /// otherwise.
106    pub fn error(&self) -> Option<&TransactionExecutorError> {
107        match &self.failure {
108            NoteFailure::Blamed { error, .. } => Some(error),
109            NoteFailure::Collateral { .. } => None,
110        }
111    }
112
113    /// Returns the number of cycles consumed before failure, if available.
114    ///
115    /// This is `Some` when the failure was due to exceeding the cycle limit, and `None`
116    /// for other error types where the cycle count is not meaningful.
117    pub fn num_cycles(&self) -> Option<usize> {
118        match &self.failure {
119            NoteFailure::Blamed { num_cycles, .. } => *num_cycles,
120            NoteFailure::Collateral { .. } => None,
121        }
122    }
123}
124
125/// Contains information about the successful and failed consumption of notes.
126#[derive(Default, Debug)]
127pub struct NoteConsumptionInfo {
128    successful: Vec<SuccessfulNote>,
129    failed: Vec<FailedNote>,
130}
131
132impl NoteConsumptionInfo {
133    /// Creates a new [`NoteConsumptionInfo`] instance with the given successful notes.
134    pub fn new_successful(successful: Vec<SuccessfulNote>) -> Self {
135        Self { successful, ..Default::default() }
136    }
137
138    /// Creates a new [`NoteConsumptionInfo`] instance with the given successful and failed notes.
139    pub fn new(successful: Vec<SuccessfulNote>, failed: Vec<FailedNote>) -> Self {
140        Self { successful, failed }
141    }
142
143    /// Returns a reference to the successfully consumed notes.
144    pub fn successful(&self) -> &[SuccessfulNote] {
145        &self.successful
146    }
147
148    /// Returns a reference to the failed notes.
149    pub fn failed(&self) -> &[FailedNote] {
150        &self.failed
151    }
152
153    /// Consumes the struct and returns the successful and failed notes.
154    pub fn into_parts(self) -> (Vec<SuccessfulNote>, Vec<FailedNote>) {
155        (self.successful, self.failed)
156    }
157}
158
159// NOTE BUNDLE
160// ================================================================================================
161
162/// A group of input notes that has to be tested for consumability as a unit, such as a feature note
163/// and the notes which sponsor it.
164#[derive(Debug)]
165pub(super) struct NoteBundle {
166    notes: Vec<Note>,
167}
168
169impl NoteBundle {
170    /// Groups `notes` into bundles that must be consumed together.
171    ///
172    /// A FEE_SPONSORSHIP note joins the bundle of the feature note it sponsors; an unpaired
173    /// sponsorship note forms a bundle of its own, so that it fails alone rather than dropping the
174    /// notes it would otherwise have been grouped with. Every other note type forms a bundle of its
175    /// own.
176    ///
177    /// The feature note is always first in the resulting bundle (if any); bundle preserves the
178    /// relative order of the sponsorship notes in it.
179    pub(super) fn group(notes: Vec<Note>) -> Vec<Self> {
180        let note_indices: BTreeMap<NoteId, usize> =
181            notes.iter().enumerate().map(|(idx, note)| (note.id(), idx)).collect();
182
183        // Put the feature notes and orphan notes to the values with keys equal to this note index
184        // in the `note_indices`. Sponsorship notes are appended to the values which contain the
185        // corresponding feature note.
186        // Keying by index rather than by note ID keeps the bundles in the caller's order.
187        let mut bundles: BTreeMap<usize, Vec<Note>> = BTreeMap::new();
188        for (idx, note) in notes.into_iter().enumerate() {
189            // A sponsorship is only bundled when the note it sponsors is actually an input;
190            // otherwise it can only be reclaimed, which is something it has to attempt on its own.
191            match FeeSponsorshipNote::try_from(&note)
192                .ok()
193                .and_then(|sponsorship| note_indices.get(&sponsorship.feature_note_id()).copied())
194            {
195                Some(head_idx) => bundles.entry(head_idx).or_default().push(note),
196                // This note heads its own bundle, so it goes first whichever side of the notes
197                // bound to it it arrives on.
198                None => bundles.entry(idx).or_default().insert(0, note),
199            }
200        }
201
202        bundles.into_values().map(|notes| Self { notes }).collect()
203    }
204
205    /// Returns the notes forming the bundle.
206    pub(super) fn notes(&self) -> &[Note] {
207        &self.notes
208    }
209}
210
211// HELPER FUNCTIONS
212// ================================================================================================
213
214/// Handle the epilogue error during the note consumption check in the `can_consume` method.
215///
216/// The goal of this helper function is to handle the cases where the account couldn't consume the
217/// note because of some epilogue check failure, e.g. absence of the authenticator.
218pub(super) fn handle_epilogue_error(
219    epilogue_error: TransactionExecutorError,
220) -> NoteConsumptionStatus {
221    match epilogue_error {
222        // `Unauthorized` is returned for the multisig accounts if the transaction doesn't have
223        // enough signatures.
224        TransactionExecutorError::Unauthorized(_)
225        // `MissingAuthenticator` is returned for the account with the basic auth if the
226        // authenticator was not provided to the executor (UnreachableAuth).
227        | TransactionExecutorError::MissingAuthenticator => {
228            // Both these cases signal that there is a probability that the provided note could be
229            // consumed if the authentication is provided.
230            NoteConsumptionStatus::ConsumableWithAuthorization
231        },
232        // TODO: apply additional checks to get the verbose error reason
233        _ => NoteConsumptionStatus::UnconsumableConditions,
234    }
235}