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