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