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