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