Skip to main content

miden_tx/errors/
mod.rs

1use alloc::boxed::Box;
2use alloc::string::String;
3use alloc::vec::Vec;
4use core::error::Error;
5
6use miden_processor::ExecutionError;
7use miden_processor::serde::DeserializationError;
8use miden_protocol::account::auth::{PublicKeyCommitment, Signature};
9use miden_protocol::account::{AccountId, StorageMapKey};
10use miden_protocol::assembly::diagnostics::reporting::PrintDiagnostic;
11use miden_protocol::asset::AssetId;
12use miden_protocol::block::BlockNumber;
13use miden_protocol::crypto::merkle::smt::SmtProofError;
14use miden_protocol::errors::{
15    AccountDeltaError,
16    AssetError,
17    NoteError,
18    OutputNoteError,
19    ProvenTransactionError,
20    TransactionInputError,
21    TransactionOutputError,
22};
23use miden_protocol::note::{NoteId, PartialNoteMetadata};
24use miden_protocol::transaction::{TransactionEventId, TransactionSummary};
25use miden_protocol::{Felt, Word};
26use thiserror::Error;
27
28// NOTE EXECUTION ERROR
29// ================================================================================================
30
31#[derive(Debug, Error)]
32pub enum NoteCheckerError {
33    #[error("invalid input note count {0} is out of range)")]
34    InputNoteCountOutOfRange(usize),
35    #[error("transaction preparation failed: {0}")]
36    TransactionPreparation(#[source] TransactionExecutorError),
37    #[error("transaction execution prologue failed: {0}")]
38    PrologueExecution(#[source] TransactionExecutorError),
39}
40
41// TRANSACTION CHECKER ERROR
42// ================================================================================================
43
44#[derive(Debug, Error)]
45pub(crate) enum TransactionCheckerError {
46    #[error("transaction preparation failed: {0}")]
47    TransactionPreparation(#[source] TransactionExecutorError),
48    #[error("transaction execution prologue failed: {0}")]
49    PrologueExecution(#[source] TransactionExecutorError),
50    #[error("transaction execution epilogue failed: {error}")]
51    EpilogueExecution {
52        error: TransactionExecutorError,
53        /// Cycle counts for notes that executed successfully before the epilogue failed.
54        successful_notes_cycle_counts: Vec<usize>,
55    },
56    #[error("transaction note execution failed on note index {failed_note_index}: {error}")]
57    NoteExecution {
58        failed_note_index: usize,
59        error: TransactionExecutorError,
60        /// Cycle counts for notes that executed successfully before the failed note.
61        successful_notes_cycle_counts: Vec<usize>,
62        /// The number of cycles consumed by the failed note before it errored.
63        ///
64        /// This is `Some` when the failure was due to exceeding the cycle limit, and `None`
65        /// for other error types where the cycle count is not meaningful.
66        failed_note_cycle_count: Option<usize>,
67    },
68}
69
70impl From<TransactionCheckerError> for TransactionExecutorError {
71    fn from(error: TransactionCheckerError) -> Self {
72        match error {
73            TransactionCheckerError::TransactionPreparation(error) => error,
74            TransactionCheckerError::PrologueExecution(error) => error,
75            TransactionCheckerError::EpilogueExecution { error, .. } => error,
76            TransactionCheckerError::NoteExecution { error, .. } => error,
77        }
78    }
79}
80
81// TRANSACTION EXECUTOR ERROR
82// ================================================================================================
83
84#[derive(Debug, Error)]
85pub enum TransactionExecutorError {
86    #[error("failed to fetch transaction inputs from the data store")]
87    FetchTransactionInputsFailed(#[source] DataStoreError),
88    #[error("failed to fetch asset witnesses from the data store")]
89    FetchAssetWitnessFailed(#[source] DataStoreError),
90    #[error("foreign account inputs for ID {0} are not anchored on reference block")]
91    ForeignAccountNotAnchoredInReference(AccountId),
92    #[error(
93        "execution options' cycles must be between {min_cycles} and {max_cycles}, but found {actual}"
94    )]
95    InvalidExecutionOptionsCycles {
96        min_cycles: u32,
97        max_cycles: u32,
98        actual: u32,
99    },
100    #[error("failed to create transaction inputs")]
101    InvalidTransactionInputs(#[source] TransactionInputError),
102    #[error("failed to process account update commitment: {0}")]
103    AccountUpdateCommitment(&'static str),
104    #[error(
105        "account patch commitment computed in transaction kernel ({in_kernel_commitment}) does not match account patch computed via the host ({host_commitment})"
106    )]
107    InconsistentAccountPatchCommitment {
108        in_kernel_commitment: Word,
109        host_commitment: Word,
110    },
111    #[error("input account ID {input_id} does not match output account ID {output_id}")]
112    InconsistentAccountId {
113        input_id: AccountId,
114        output_id: AccountId,
115    },
116    #[error("account witness provided for account ID {0} is invalid")]
117    InvalidAccountWitness(AccountId, #[source] SmtProofError),
118    #[error(
119        "input note {0} was created in a block past the transaction reference block number ({1})"
120    )]
121    NoteBlockPastReferenceBlock(NoteId, BlockNumber),
122    #[error("failed to construct transaction outputs")]
123    TransactionOutputConstructionFailed(#[source] TransactionOutputError),
124    // Print the diagnostic directly instead of returning the source error. In the source error
125    // case, the diagnostic is lost if the execution error is not explicitly unwrapped.
126    #[error("failed to execute transaction kernel program:\n{}", PrintDiagnostic::new(.0))]
127    TransactionProgramExecutionFailed(ExecutionError),
128    /// This variant can be matched on to get the summary of a transaction for signing purposes.
129    // It is boxed to avoid triggering clippy::result_large_err for functions that return this type.
130    #[error("transaction is unauthorized with summary {0:?}")]
131    Unauthorized(Box<TransactionSummary>),
132    #[error(
133        "failed to respond to signature requested since no authenticator is assigned to the host"
134    )]
135    MissingAuthenticator,
136    #[error("received an auth request event emitted outside the authentication procedure")]
137    AuthRequestOutsideAuthProcedure,
138    #[error("received privileged event {0} emitted outside the tx kernel context")]
139    PrivilegedEventFromOutsideTransactionKernelContext(TransactionEventId),
140}
141
142#[cfg(any(test, feature = "testing"))]
143impl TransactionExecutorError {
144    pub fn unwrap_unauthorized_err(self) -> Box<TransactionSummary> {
145        match self {
146            TransactionExecutorError::Unauthorized(transaction_summary) => transaction_summary,
147            other => panic!("expected TransactionExecutorError::Unauthorized, got {other}"),
148        }
149    }
150}
151
152// TRANSACTION PROVER ERROR
153// ================================================================================================
154
155#[derive(Debug, Error)]
156pub enum TransactionProverError {
157    #[error("failed to construct transaction outputs")]
158    TransactionOutputConstructionFailed(#[source] TransactionOutputError),
159    #[error("failed to shrink output note")]
160    OutputNoteShrinkFailed(#[source] OutputNoteError),
161    #[error("failed to build proven transaction")]
162    ProvenTransactionBuildFailed(#[source] ProvenTransactionError),
163    // Print the diagnostic directly instead of returning the source error. In the source error
164    // case, the diagnostic is lost if the execution error is not explicitly unwrapped.
165    #[error("failed to execute transaction kernel program:\n{}", PrintDiagnostic::new(.0))]
166    TransactionProgramExecutionFailed(ExecutionError),
167    /// Custom error variant for errors not covered by the other variants.
168    #[error("{error_msg}")]
169    Other {
170        error_msg: Box<str>,
171        // thiserror will return this when calling Error::source on DataStoreError.
172        source: Option<Box<dyn Error + Send + Sync + 'static>>,
173    },
174}
175
176impl TransactionProverError {
177    /// Creates a custom error using the [`TransactionProverError::Other`] variant from an error
178    /// message.
179    pub fn other(message: impl Into<String>) -> Self {
180        let message: String = message.into();
181        Self::Other { error_msg: message.into(), source: None }
182    }
183
184    /// Creates a custom error using the [`TransactionProverError::Other`] variant from an error
185    /// message and a source error.
186    pub fn other_with_source(
187        message: impl Into<String>,
188        source: impl Error + Send + Sync + 'static,
189    ) -> Self {
190        let message: String = message.into();
191        Self::Other {
192            error_msg: message.into(),
193            source: Some(Box::new(source)),
194        }
195    }
196}
197
198// TRANSACTION KERNEL ERROR
199// ================================================================================================
200
201#[derive(Debug, Error)]
202pub enum TransactionKernelError {
203    #[error("failed to add asset to account delta")]
204    AccountDeltaAddAssetFailed(#[source] AccountDeltaError),
205    #[error("failed to remove asset from account delta")]
206    AccountDeltaRemoveAssetFailed(#[source] AccountDeltaError),
207    #[error("failed to add asset to note")]
208    FailedToAddAssetToNote(#[source] NoteError),
209    #[error("note storage has commitment {actual} but expected commitment {expected}")]
210    InvalidNoteStorage { expected: Word, actual: Word },
211    #[error(
212        "failed to respond to signature requested since no authenticator is assigned to the host"
213    )]
214    MissingAuthenticator,
215    #[error("received an auth request event emitted outside the authentication procedure")]
216    AuthRequestOutsideAuthProcedure,
217    #[error("received privileged event {0} emitted outside the tx kernel context")]
218    PrivilegedEventFromOutsideTransactionKernelContext(TransactionEventId),
219    #[error("failed to generate signature")]
220    SignatureGenerationFailed(#[source] AuthenticationError),
221    #[error("transaction returned unauthorized event but a commitment did not match: {0}")]
222    TransactionSummaryCommitmentMismatch(#[source] Box<dyn Error + Send + Sync + 'static>),
223    #[error(
224        "transaction summary binds expiration delta {actual} but the transaction's expiration delta is {expected}"
225    )]
226    TransactionSummaryExpirationDeltaMismatch { expected: u16, actual: u16 },
227    #[error("failed to construct transaction summary")]
228    TransactionSummaryConstructionFailed(#[source] Box<dyn Error + Send + Sync + 'static>),
229    #[error("asset data extracted from the stack by event handler `{handler}` is not well formed")]
230    MalformedAssetInEventHandler {
231        handler: &'static str,
232        source: AssetError,
233    },
234    #[error(
235        "note storage data extracted from the advice map by the event handler is not well formed"
236    )]
237    MalformedNoteStorage(#[source] NoteError),
238    #[error(
239        "note script data `{data:?}` extracted from the advice map by the event handler is not well formed"
240    )]
241    MalformedNoteScript {
242        data: Vec<Felt>,
243        source: DeserializationError,
244    },
245    #[error(
246        "encoded signature under advice map key {signature_key} has {actual} elements, but a valid encoded signature has between 1 and {max} elements",
247        max = Signature::MAX_NUM_ENCODED_SIGNATURE_FELTS
248    )]
249    InvalidEncodedSignatureLength { signature_key: Word, actual: usize },
250    #[error("recipient data `{0:?}` in the advice provider is not well formed")]
251    MalformedRecipientData(Vec<Felt>),
252    #[error("cannot add asset to note with index {0}, note does not exist in the advice provider")]
253    MissingNote(usize),
254    #[error(
255        "public note with metadata {0:?} and recipient digest {1} is missing details in the advice provider"
256    )]
257    PublicNoteMissingDetails(PartialNoteMetadata, Word),
258    #[error(
259        "commitment of note attachment advice data is {actual} which does not match commitment {provided} provided to add_attachment"
260    )]
261    NoteAttachmentCommitmentMismatch { actual: Word, provided: Word },
262    #[error(
263        "note storage in advice provider contains fewer items ({actual}) than specified ({specified}) by its number of storage items"
264    )]
265    TooFewElementsForNoteStorage { specified: u64, actual: u64 },
266    #[error("account procedure with procedure root {0} is not in the account procedure index map")]
267    UnknownAccountProcedure(Word),
268    #[error("code commitment {0} is not in the account procedure index map")]
269    UnknownCodeCommitment(Word),
270    #[error("account storage slots number is missing in memory at address {0}")]
271    AccountStorageSlotsNumMissing(u32),
272    #[error("account nonce can only be incremented once")]
273    NonceCanOnlyIncrementOnce,
274    #[error(
275        "failed to get inputs for foreign account {foreign_account_id} from data store at reference block {ref_block}"
276    )]
277    GetForeignAccountInputs {
278        foreign_account_id: AccountId,
279        ref_block: BlockNumber,
280        // thiserror will return this when calling Error::source on TransactionKernelError.
281        source: DataStoreError,
282    },
283    #[error(
284        "failed to get vault asset witness from data store for vault root {vault_root} and asset_id {asset_id}"
285    )]
286    GetVaultAssetWitness {
287        vault_root: Word,
288        asset_id: AssetId,
289        // thiserror will return this when calling Error::source on TransactionKernelError.
290        source: DataStoreError,
291    },
292    #[error(
293        "failed to get storage map witness from data store for map root {map_root} and map_key {map_key}"
294    )]
295    GetStorageMapWitness {
296        map_root: Word,
297        map_key: StorageMapKey,
298        // thiserror will return this when calling Error::source on TransactionKernelError.
299        source: DataStoreError,
300    },
301    /// This variant signals that a signature over the contained commitments is required, but
302    /// missing.
303    #[error("transaction requires a signature")]
304    Unauthorized(Box<TransactionSummary>),
305    /// A generic error returned when the transaction kernel did not behave as expected.
306    #[error("{message}")]
307    Other {
308        message: Box<str>,
309        // thiserror will return this when calling Error::source on TransactionKernelError.
310        source: Option<Box<dyn Error + Send + Sync + 'static>>,
311    },
312}
313
314impl TransactionKernelError {
315    /// Creates a custom error using the [`TransactionKernelError::Other`] variant from an error
316    /// message.
317    pub fn other(message: impl Into<String>) -> Self {
318        let message: String = message.into();
319        Self::Other { message: message.into(), source: None }
320    }
321
322    /// Creates a custom error using the [`TransactionKernelError::Other`] variant from an error
323    /// message and a source error.
324    pub fn other_with_source(
325        message: impl Into<String>,
326        source: impl Error + Send + Sync + 'static,
327    ) -> Self {
328        let message: String = message.into();
329        Self::Other {
330            message: message.into(),
331            source: Some(Box::new(source)),
332        }
333    }
334}
335
336// DATA STORE ERROR
337// ================================================================================================
338
339#[derive(Debug, Error)]
340pub enum DataStoreError {
341    #[error("account with id {0} not found in data store")]
342    AccountNotFound(AccountId),
343    #[error("block with number {0} not found in data store")]
344    BlockNotFound(BlockNumber),
345    /// Custom error variant for implementors of the [`DataStore`](crate::executor::DataStore)
346    /// trait.
347    #[error("{error_msg}")]
348    Other {
349        error_msg: Box<str>,
350        // thiserror will return this when calling Error::source on DataStoreError.
351        source: Option<Box<dyn Error + Send + Sync + 'static>>,
352    },
353}
354
355impl DataStoreError {
356    /// Creates a custom error using the [`DataStoreError::Other`] variant from an error message.
357    pub fn other(message: impl Into<String>) -> Self {
358        let message: String = message.into();
359        Self::Other { error_msg: message.into(), source: None }
360    }
361
362    /// Creates a custom error using the [`DataStoreError::Other`] variant from an error message and
363    /// a source error.
364    pub fn other_with_source(
365        message: impl Into<String>,
366        source: impl Error + Send + Sync + 'static,
367    ) -> Self {
368        let message: String = message.into();
369        Self::Other {
370            error_msg: message.into(),
371            source: Some(Box::new(source)),
372        }
373    }
374}
375
376// AUTHENTICATION ERROR
377// ================================================================================================
378
379#[derive(Debug, Error)]
380pub enum AuthenticationError {
381    #[error("signature rejected: {0}")]
382    RejectedSignature(String),
383    #[error("public key `{0}` is not contained in the authenticator's keys")]
384    UnknownPublicKey(PublicKeyCommitment),
385    /// Custom error variant for implementors of the
386    /// [`TransactionAuthenticator`](crate::auth::TransactionAuthenticator) trait.
387    #[error("{error_msg}")]
388    Other {
389        error_msg: Box<str>,
390        // thiserror will return this when calling Error::source on DataStoreError.
391        source: Option<Box<dyn Error + Send + Sync + 'static>>,
392    },
393}
394
395impl AuthenticationError {
396    /// Creates a custom error using the [`AuthenticationError::Other`] variant from an error
397    /// message.
398    pub fn other(message: impl Into<String>) -> Self {
399        let message: String = message.into();
400        Self::Other { error_msg: message.into(), source: None }
401    }
402
403    /// Creates a custom error using the [`AuthenticationError::Other`] variant from an error
404    /// message and a source error.
405    pub fn other_with_source(
406        message: impl Into<String>,
407        source: impl Error + Send + Sync + 'static,
408    ) -> Self {
409        let message: String = message.into();
410        Self::Other {
411            error_msg: message.into(),
412            source: Some(Box::new(source)),
413        }
414    }
415}
416
417#[cfg(test)]
418mod error_assertions {
419    use super::*;
420
421    /// Asserts at compile time that the passed error has Send + Sync + 'static bounds.
422    fn _assert_error_is_send_sync_static<E: core::error::Error + Send + Sync + 'static>(_: E) {}
423
424    fn _assert_data_store_error_bounds(err: DataStoreError) {
425        _assert_error_is_send_sync_static(err);
426    }
427
428    fn _assert_authentication_error_bounds(err: AuthenticationError) {
429        _assert_error_is_send_sync_static(err);
430    }
431
432    fn _assert_transaction_kernel_error_bounds(err: TransactionKernelError) {
433        _assert_error_is_send_sync_static(err);
434    }
435}