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