miden_tx/errors/
mod.rs

1use alloc::boxed::Box;
2use alloc::string::String;
3use alloc::vec::Vec;
4use core::error::Error;
5
6use miden_lib::transaction::TransactionAdviceMapMismatch;
7use miden_objects::account::AccountId;
8use miden_objects::account::auth::PublicKeyCommitment;
9use miden_objects::assembly::diagnostics::reporting::PrintDiagnostic;
10use miden_objects::asset::AssetVaultKey;
11use miden_objects::block::BlockNumber;
12use miden_objects::crypto::merkle::SmtProofError;
13use miden_objects::note::{NoteId, NoteMetadata};
14use miden_objects::transaction::TransactionSummary;
15use miden_objects::{
16    AccountDeltaError,
17    AccountError,
18    AssetError,
19    Felt,
20    NoteError,
21    ProvenTransactionError,
22    TransactionInputError,
23    TransactionOutputError,
24    Word,
25};
26use miden_processor::{DeserializationError, ExecutionError};
27use miden_verifier::VerificationError;
28use thiserror::Error;
29
30// NOTE EXECUTION ERROR
31// ================================================================================================
32
33#[derive(Debug, Error)]
34pub enum NoteCheckerError {
35    #[error("invalid input note count {0} is out of range)")]
36    InputNoteCountOutOfRange(usize),
37    #[error("transaction preparation failed: {0}")]
38    TransactionPreparation(#[source] TransactionExecutorError),
39    #[error("transaction execution prologue failed: {0}")]
40    PrologueExecution(#[source] TransactionExecutorError),
41}
42
43// TRANSACTION CHECKER ERROR
44// ================================================================================================
45
46#[derive(Debug, Error)]
47pub(crate) enum TransactionCheckerError {
48    #[error("transaction preparation failed: {0}")]
49    TransactionPreparation(#[source] TransactionExecutorError),
50    #[error("transaction execution prologue failed: {0}")]
51    PrologueExecution(#[source] TransactionExecutorError),
52    #[error("transaction execution epilogue failed: {0}")]
53    EpilogueExecution(#[source] TransactionExecutorError),
54    #[error("transaction note execution failed on note index {failed_note_index}: {error}")]
55    NoteExecution {
56        failed_note_index: usize,
57        error: TransactionExecutorError,
58    },
59}
60
61impl From<TransactionCheckerError> for TransactionExecutorError {
62    fn from(error: TransactionCheckerError) -> Self {
63        match error {
64            TransactionCheckerError::TransactionPreparation(error) => error,
65            TransactionCheckerError::PrologueExecution(error) => error,
66            TransactionCheckerError::EpilogueExecution(error) => error,
67            TransactionCheckerError::NoteExecution { error, .. } => error,
68        }
69    }
70}
71
72// TRANSACTION EXECUTOR ERROR
73// ================================================================================================
74
75#[derive(Debug, Error)]
76pub enum TransactionExecutorError {
77    #[error("the advice map contains conflicting map entries")]
78    ConflictingAdviceMapEntry(#[source] TransactionAdviceMapMismatch),
79    #[error("failed to fetch transaction inputs from the data store")]
80    FetchTransactionInputsFailed(#[source] DataStoreError),
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 create transaction host")]
122    TransactionHostCreationFailed(#[source] TransactionHostError),
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}
138
139// TRANSACTION PROVER ERROR
140// ================================================================================================
141
142#[derive(Debug, Error)]
143pub enum TransactionProverError {
144    #[error("failed to apply account delta")]
145    AccountDeltaApplyFailed(#[source] AccountError),
146    #[error("failed to remove the fee asset from the pre-fee account delta")]
147    RemoveFeeAssetFromDelta(#[source] AccountDeltaError),
148    #[error("failed to construct transaction outputs")]
149    TransactionOutputConstructionFailed(#[source] TransactionOutputError),
150    #[error("failed to build proven transaction")]
151    ProvenTransactionBuildFailed(#[source] ProvenTransactionError),
152    #[error("the advice map contains conflicting map entries")]
153    ConflictingAdviceMapEntry(#[source] TransactionAdviceMapMismatch),
154    // Print the diagnostic directly instead of returning the source error. In the source error
155    // case, the diagnostic is lost if the execution error is not explicitly unwrapped.
156    #[error("failed to execute transaction kernel program:\n{}", PrintDiagnostic::new(.0))]
157    TransactionProgramExecutionFailed(ExecutionError),
158    #[error("failed to create account procedure index map")]
159    CreateAccountProcedureIndexMap(#[source] TransactionHostError),
160    #[error("failed to create transaction host")]
161    TransactionHostCreationFailed(#[source] TransactionHostError),
162    /// Custom error variant for errors not covered by the other variants.
163    #[error("{error_msg}")]
164    Other {
165        error_msg: Box<str>,
166        // thiserror will return this when calling Error::source on DataStoreError.
167        source: Option<Box<dyn Error + Send + Sync + 'static>>,
168    },
169}
170
171impl TransactionProverError {
172    /// Creates a custom error using the [`TransactionProverError::Other`] variant from an error
173    /// message.
174    pub fn other(message: impl Into<String>) -> Self {
175        let message: String = message.into();
176        Self::Other { error_msg: message.into(), source: None }
177    }
178
179    /// Creates a custom error using the [`TransactionProverError::Other`] variant from an error
180    /// message and a source error.
181    pub fn other_with_source(
182        message: impl Into<String>,
183        source: impl Error + Send + Sync + 'static,
184    ) -> Self {
185        let message: String = message.into();
186        Self::Other {
187            error_msg: message.into(),
188            source: Some(Box::new(source)),
189        }
190    }
191}
192
193// TRANSACTION VERIFIER ERROR
194// ================================================================================================
195
196#[derive(Debug, Error)]
197pub enum TransactionVerifierError {
198    #[error("failed to verify transaction")]
199    TransactionVerificationFailed(#[source] VerificationError),
200    #[error("transaction proof security level is {actual} but must be at least {expected_minimum}")]
201    InsufficientProofSecurityLevel { actual: u32, expected_minimum: u32 },
202}
203
204// TRANSACTION HOST ERROR
205// ================================================================================================
206
207#[derive(Debug, Error)]
208pub enum TransactionHostError {
209    #[error("{0}")]
210    AccountProcedureIndexMapError(String),
211    #[error("failed to create account procedure info")]
212    AccountProcedureInfoCreationFailed(#[source] AccountError),
213}
214
215// TRANSACTION KERNEL ERROR
216// ================================================================================================
217
218#[derive(Debug, Error)]
219pub enum TransactionKernelError {
220    #[error("failed to add asset to account delta")]
221    AccountDeltaAddAssetFailed(#[source] AccountDeltaError),
222    #[error("failed to remove asset from account delta")]
223    AccountDeltaRemoveAssetFailed(#[source] AccountDeltaError),
224    #[error("failed to add asset to note")]
225    FailedToAddAssetToNote(#[source] NoteError),
226    #[error("note input data has hash {actual} but expected hash {expected}")]
227    InvalidNoteInputs { expected: Word, actual: Word },
228    #[error(
229        "storage slot index {actual} is invalid, must be smaller than the number of account storage slots {max}"
230    )]
231    InvalidStorageSlotIndex { max: u64, actual: u64 },
232    #[error(
233        "failed to respond to signature requested since no authenticator is assigned to the host"
234    )]
235    MissingAuthenticator,
236    #[error("failed to generate signature")]
237    SignatureGenerationFailed(#[source] AuthenticationError),
238    #[error("transaction returned unauthorized event but a commitment did not match: {0}")]
239    TransactionSummaryCommitmentMismatch(#[source] Box<dyn Error + Send + Sync + 'static>),
240    #[error("failed to construct transaction summary")]
241    TransactionSummaryConstructionFailed(#[source] Box<dyn Error + Send + Sync + 'static>),
242    #[error("asset data extracted from the stack by event handler `{handler}` is not well formed")]
243    MalformedAssetInEventHandler {
244        handler: &'static str,
245        source: AssetError,
246    },
247    #[error(
248        "note inputs data extracted from the advice map by the event handler is not well formed"
249    )]
250    MalformedNoteInputs(#[source] NoteError),
251    #[error("note metadata created by the event handler is not well formed")]
252    MalformedNoteMetadata(#[source] NoteError),
253    #[error(
254        "note script data `{data:?}` extracted from the advice map by the event handler is not well formed"
255    )]
256    MalformedNoteScript {
257        data: Vec<Felt>,
258        source: DeserializationError,
259    },
260    #[error("recipient data `{0:?}` in the advice provider is not well formed")]
261    MalformedRecipientData(Vec<Felt>),
262    #[error("cannot add asset to note with index {0}, note does not exist in the advice provider")]
263    MissingNote(u64),
264    #[error(
265        "public note with metadata {0:?} and recipient digest {1} is missing details in the advice provider"
266    )]
267    PublicNoteMissingDetails(NoteMetadata, Word),
268    #[error(
269        "note input data in advice provider contains fewer elements ({actual}) than specified ({specified}) by its inputs length"
270    )]
271    TooFewElementsForNoteInputs { specified: u64, actual: u64 },
272    #[error("account procedure with procedure root {0} is not in the account procedure index map")]
273    UnknownAccountProcedure(Word),
274    #[error("code commitment {0} is not in the account procedure index map")]
275    UnknownCodeCommitment(Word),
276    #[error("account storage slots number is missing in memory at address {0}")]
277    AccountStorageSlotsNumMissing(u32),
278    #[error("account nonce can only be incremented once")]
279    NonceCanOnlyIncrementOnce,
280    #[error("failed to convert fee asset into fungible asset")]
281    FailedToConvertFeeAsset(#[source] AssetError),
282    #[error(
283        "failed to get inputs for foreign account {foreign_account_id} from data store at reference block {ref_block}"
284    )]
285    GetForeignAccountInputs {
286        foreign_account_id: AccountId,
287        ref_block: BlockNumber,
288        // thiserror will return this when calling Error::source on TransactionKernelError.
289        source: DataStoreError,
290    },
291    #[error(
292        "failed to get vault asset witness from data store for vault root {vault_root} and vault_key {asset_key}"
293    )]
294    GetVaultAssetWitness {
295        vault_root: Word,
296        asset_key: AssetVaultKey,
297        // thiserror will return this when calling Error::source on TransactionKernelError.
298        source: DataStoreError,
299    },
300    #[error(
301        "failed to get storage map witness from data store for map root {map_root} and map_key {map_key}"
302    )]
303    GetStorageMapWitness {
304        map_root: Word,
305        map_key: Word,
306        // thiserror will return this when calling Error::source on TransactionKernelError.
307        source: DataStoreError,
308    },
309    #[error(
310        "native asset amount {account_balance} in the account vault is not sufficient to cover the transaction fee of {tx_fee}"
311    )]
312    InsufficientFee { account_balance: u64, tx_fee: u64 },
313    /// This variant signals that a signature over the contained commitments is required, but
314    /// missing.
315    #[error("transaction requires a signature")]
316    Unauthorized(Box<TransactionSummary>),
317    /// A generic error returned when the transaction kernel did not behave as expected.
318    #[error("{message}")]
319    Other {
320        message: Box<str>,
321        // thiserror will return this when calling Error::source on TransactionKernelError.
322        source: Option<Box<dyn Error + Send + Sync + 'static>>,
323    },
324}
325
326impl TransactionKernelError {
327    /// Creates a custom error using the [`TransactionKernelError::Other`] variant from an error
328    /// message.
329    pub fn other(message: impl Into<String>) -> Self {
330        let message: String = message.into();
331        Self::Other { message: message.into(), source: None }
332    }
333
334    /// Creates a custom error using the [`TransactionKernelError::Other`] variant from an error
335    /// message and a source error.
336    pub fn other_with_source(
337        message: impl Into<String>,
338        source: impl Error + Send + Sync + 'static,
339    ) -> Self {
340        let message: String = message.into();
341        Self::Other {
342            message: message.into(),
343            source: Some(Box::new(source)),
344        }
345    }
346}
347
348// DATA STORE ERROR
349// ================================================================================================
350
351#[derive(Debug, Error)]
352pub enum DataStoreError {
353    #[error("account with id {0} not found in data store")]
354    AccountNotFound(AccountId),
355    #[error("block with number {0} not found in data store")]
356    BlockNotFound(BlockNumber),
357    #[error("note script with root {0} not found in data store")]
358    NoteScriptNotFound(Word),
359    /// Custom error variant for implementors of the [`DataStore`](crate::executor::DataStore)
360    /// trait.
361    #[error("{error_msg}")]
362    Other {
363        error_msg: Box<str>,
364        // thiserror will return this when calling Error::source on DataStoreError.
365        source: Option<Box<dyn Error + Send + Sync + 'static>>,
366    },
367}
368
369impl DataStoreError {
370    /// Creates a custom error using the [`DataStoreError::Other`] variant from an error message.
371    pub fn other(message: impl Into<String>) -> Self {
372        let message: String = message.into();
373        Self::Other { error_msg: message.into(), source: None }
374    }
375
376    /// Creates a custom error using the [`DataStoreError::Other`] variant from an error message and
377    /// a source error.
378    pub fn other_with_source(
379        message: impl Into<String>,
380        source: impl Error + Send + Sync + 'static,
381    ) -> Self {
382        let message: String = message.into();
383        Self::Other {
384            error_msg: message.into(),
385            source: Some(Box::new(source)),
386        }
387    }
388}
389
390// AUTHENTICATION ERROR
391// ================================================================================================
392
393#[derive(Debug, Error)]
394pub enum AuthenticationError {
395    #[error("signature rejected: {0}")]
396    RejectedSignature(String),
397    #[error("public key `{0}` is not contained in the authenticator's keys")]
398    UnknownPublicKey(PublicKeyCommitment),
399    /// Custom error variant for implementors of the
400    /// [`TransactionAuthenticator`](crate::auth::TransactionAuthenticator) trait.
401    #[error("{error_msg}")]
402    Other {
403        error_msg: Box<str>,
404        // thiserror will return this when calling Error::source on DataStoreError.
405        source: Option<Box<dyn Error + Send + Sync + 'static>>,
406    },
407}
408
409impl AuthenticationError {
410    /// Creates a custom error using the [`AuthenticationError::Other`] variant from an error
411    /// message.
412    pub fn other(message: impl Into<String>) -> Self {
413        let message: String = message.into();
414        Self::Other { error_msg: message.into(), source: None }
415    }
416
417    /// Creates a custom error using the [`AuthenticationError::Other`] variant from an error
418    /// message and a source error.
419    pub fn other_with_source(
420        message: impl Into<String>,
421        source: impl Error + Send + Sync + 'static,
422    ) -> Self {
423        let message: String = message.into();
424        Self::Other {
425            error_msg: message.into(),
426            source: Some(Box::new(source)),
427        }
428    }
429}
430
431#[cfg(test)]
432mod error_assertions {
433    use super::*;
434
435    /// Asserts at compile time that the passed error has Send + Sync + 'static bounds.
436    fn _assert_error_is_send_sync_static<E: core::error::Error + Send + Sync + 'static>(_: E) {}
437
438    fn _assert_data_store_error_bounds(err: DataStoreError) {
439        _assert_error_is_send_sync_static(err);
440    }
441
442    fn _assert_authentication_error_bounds(err: AuthenticationError) {
443        _assert_error_is_send_sync_static(err);
444    }
445
446    fn _assert_transaction_kernel_error_bounds(err: TransactionKernelError) {
447        _assert_error_is_send_sync_static(err);
448    }
449}