miden_tx/errors/
mod.rs

1use alloc::boxed::Box;
2use alloc::string::String;
3use core::error::Error;
4
5use miden_lib::transaction::TransactionAdviceMapMismatch;
6use miden_objects::account::AccountId;
7use miden_objects::assembly::diagnostics::reporting::PrintDiagnostic;
8use miden_objects::block::BlockNumber;
9use miden_objects::crypto::merkle::SmtProofError;
10use miden_objects::note::NoteId;
11use miden_objects::transaction::TransactionSummary;
12use miden_objects::{
13    AccountDeltaError,
14    AccountError,
15    Felt,
16    ProvenTransactionError,
17    TransactionInputError,
18    TransactionOutputError,
19    Word,
20};
21use miden_processor::ExecutionError;
22use miden_verifier::VerificationError;
23use thiserror::Error;
24
25// NOTE EXECUTION ERROR
26// ================================================================================================
27
28#[derive(Debug, Error)]
29pub enum NoteCheckerError {
30    #[error("transaction preparation failed: {0}")]
31    TransactionPreparationFailed(#[source] TransactionExecutorError),
32    #[error("transaction execution prologue failed: {0}")]
33    PrologueExecutionFailed(#[source] TransactionExecutorError),
34    #[error("transaction execution epilogue failed: {0}")]
35    EpilogueExecutionFailed(#[source] TransactionExecutorError),
36    #[error("transaction note execution failed on note index {failed_note_index}: {error}")]
37    NoteExecutionFailed {
38        failed_note_index: usize,
39        error: TransactionExecutorError,
40    },
41
42    // new variants were created instead of modifying existing ones do decrease the number of
43    // merge conflicts during the next release
44    #[error("transaction preparation failed: {0}")]
45    TransactionPreparation(#[source] TransactionExecutorError),
46    #[error("transaction execution prologue failed: {0}")]
47    PrologueExecution(#[source] TransactionExecutorError),
48}
49
50// TRANSACTION CHECKER ERROR
51// ================================================================================================
52
53#[derive(Debug, Error)]
54pub(crate) enum TransactionCheckerError {
55    #[error("transaction preparation failed: {0}")]
56    TransactionPreparation(#[source] TransactionExecutorError),
57    #[error("transaction execution prologue failed: {0}")]
58    PrologueExecution(#[source] TransactionExecutorError),
59    #[error("transaction execution epilogue failed: {0}")]
60    EpilogueExecution(#[source] TransactionExecutorError),
61    #[error("transaction note execution failed on note index {failed_note_index}: {error}")]
62    NoteExecution {
63        failed_note_index: usize,
64        error: TransactionExecutorError,
65    },
66}
67
68impl From<TransactionCheckerError> for TransactionExecutorError {
69    fn from(error: TransactionCheckerError) -> Self {
70        match error {
71            TransactionCheckerError::TransactionPreparation(error) => error,
72            TransactionCheckerError::PrologueExecution(error) => error,
73            TransactionCheckerError::EpilogueExecution(error) => error,
74            TransactionCheckerError::NoteExecution { error, .. } => error,
75        }
76    }
77}
78
79// TRANSACTION EXECUTOR ERROR
80// ================================================================================================
81
82#[derive(Debug, Error)]
83pub enum TransactionExecutorError {
84    #[error("the advice map contains conflicting map entries")]
85    ConflictingAdviceMapEntry(#[source] TransactionAdviceMapMismatch),
86    #[error("failed to fetch transaction inputs from the data store")]
87    FetchTransactionInputsFailed(#[source] DataStoreError),
88    #[error("foreign account inputs for ID {0} are not anchored on reference block")]
89    ForeignAccountNotAnchoredInReference(AccountId),
90    #[error(
91        "execution options' cycles must be between {min_cycles} and {max_cycles}, but found {actual}"
92    )]
93    InvalidExecutionOptionsCycles {
94        min_cycles: u32,
95        max_cycles: u32,
96        actual: u32,
97    },
98    #[error("failed to create transaction inputs")]
99    InvalidTransactionInputs(#[source] TransactionInputError),
100    #[error("failed to process account update commitment: {0}")]
101    AccountUpdateCommitment(&'static str),
102    #[error(
103        "account delta commitment computed in transaction kernel ({in_kernel_commitment}) does not match account delta computed via the host ({host_commitment})"
104    )]
105    InconsistentAccountDeltaCommitment {
106        in_kernel_commitment: Word,
107        host_commitment: Word,
108    },
109    #[error("failed to remove the fee asset from the pre-fee account delta")]
110    RemoveFeeAssetFromDelta(#[source] AccountDeltaError),
111    #[error("input account ID {input_id} does not match output account ID {output_id}")]
112    InconsistentAccountId {
113        input_id: AccountId,
114        output_id: AccountId,
115    },
116    #[error("expected account nonce delta to be {expected}, found {actual}")]
117    InconsistentAccountNonceDelta { expected: Felt, actual: Felt },
118    #[error(
119        "native asset amount {account_balance} in the account vault is not sufficient to cover the transaction fee of {tx_fee}"
120    )]
121    InsufficientFee { account_balance: u64, tx_fee: u64 },
122    #[error("account witness provided for account ID {0} is invalid")]
123    InvalidAccountWitness(AccountId, #[source] SmtProofError),
124    #[error(
125        "input note {0} was created in a block past the transaction reference block number ({1})"
126    )]
127    NoteBlockPastReferenceBlock(NoteId, BlockNumber),
128    #[error("failed to create transaction host")]
129    TransactionHostCreationFailed(#[source] TransactionHostError),
130    #[error("failed to construct transaction outputs")]
131    TransactionOutputConstructionFailed(#[source] TransactionOutputError),
132    // Print the diagnostic directly instead of returning the source error. In the source error
133    // case, the diagnostic is lost if the execution error is not explicitly unwrapped.
134    #[error("failed to execute transaction kernel program:\n{}", PrintDiagnostic::new(.0))]
135    TransactionProgramExecutionFailed(ExecutionError),
136    /// This variant can be matched on to get the summary of a transaction for signing purposes.
137    // It is boxed to avoid triggering clippy::result_large_err for functions that return this type.
138    #[error("transaction is unauthorized with summary {0:?}")]
139    Unauthorized(Box<TransactionSummary>),
140    #[error(
141        "failed to respond to signature requested since no authenticator is assigned to the host"
142    )]
143    MissingAuthenticator,
144}
145
146// TRANSACTION PROVER ERROR
147// ================================================================================================
148
149#[derive(Debug, Error)]
150pub enum TransactionProverError {
151    #[error("failed to apply account delta")]
152    AccountDeltaApplyFailed(#[source] AccountError),
153    #[error("failed to remove the fee asset from the pre-fee account delta")]
154    RemoveFeeAssetFromDelta(#[source] AccountDeltaError),
155    #[error("failed to construct transaction outputs")]
156    TransactionOutputConstructionFailed(#[source] TransactionOutputError),
157    #[error("failed to build proven transaction")]
158    ProvenTransactionBuildFailed(#[source] ProvenTransactionError),
159    #[error("the advice map contains conflicting map entries")]
160    ConflictingAdviceMapEntry(#[source] TransactionAdviceMapMismatch),
161    // Print the diagnostic directly instead of returning the source error. In the source error
162    // case, the diagnostic is lost if the execution error is not explicitly unwrapped.
163    #[error("failed to execute transaction kernel program:\n{}", PrintDiagnostic::new(.0))]
164    TransactionProgramExecutionFailed(ExecutionError),
165    #[error("failed to create transaction host")]
166    TransactionHostCreationFailed(#[source] TransactionHostError),
167    /// Custom error variant for errors not covered by the other variants.
168    #[error("{error_msg}")]
169    Other {
170        error_msg: Box<str>,
171        // thiserror will return this when calling Error::source on DataStoreError.
172        source: Option<Box<dyn Error + Send + Sync + 'static>>,
173    },
174}
175
176impl TransactionProverError {
177    /// Creates a custom error using the [`TransactionProverError::Other`] variant from an error
178    /// message.
179    pub fn other(message: impl Into<String>) -> Self {
180        let message: String = message.into();
181        Self::Other { error_msg: message.into(), source: None }
182    }
183
184    /// Creates a custom error using the [`TransactionProverError::Other`] variant from an error
185    /// message and a source error.
186    pub fn other_with_source(
187        message: impl Into<String>,
188        source: impl Error + Send + Sync + 'static,
189    ) -> Self {
190        let message: String = message.into();
191        Self::Other {
192            error_msg: message.into(),
193            source: Some(Box::new(source)),
194        }
195    }
196}
197
198// TRANSACTION VERIFIER ERROR
199// ================================================================================================
200
201#[derive(Debug, Error)]
202pub enum TransactionVerifierError {
203    #[error("failed to verify transaction")]
204    TransactionVerificationFailed(#[source] VerificationError),
205    #[error("transaction proof security level is {actual} but must be at least {expected_minimum}")]
206    InsufficientProofSecurityLevel { actual: u32, expected_minimum: u32 },
207}
208
209// TRANSACTION HOST ERROR
210// ================================================================================================
211
212#[derive(Debug, Error)]
213pub enum TransactionHostError {
214    #[error("{0}")]
215    AccountProcedureIndexMapError(String),
216    #[error("failed to create account procedure info")]
217    AccountProcedureInfoCreationFailed(#[source] AccountError),
218}
219
220// DATA STORE ERROR
221// ================================================================================================
222
223#[derive(Debug, Error)]
224pub enum DataStoreError {
225    #[error("account with id {0} not found in data store")]
226    AccountNotFound(AccountId),
227    #[error("block with number {0} not found in data store")]
228    BlockNotFound(BlockNumber),
229    /// Custom error variant for implementors of the [`DataStore`](crate::executor::DataStore)
230    /// trait.
231    #[error("{error_msg}")]
232    Other {
233        error_msg: Box<str>,
234        // thiserror will return this when calling Error::source on DataStoreError.
235        source: Option<Box<dyn Error + Send + Sync + 'static>>,
236    },
237}
238
239impl DataStoreError {
240    /// Creates a custom error using the [`DataStoreError::Other`] variant from an error message.
241    pub fn other(message: impl Into<String>) -> Self {
242        let message: String = message.into();
243        Self::Other { error_msg: message.into(), source: None }
244    }
245
246    /// Creates a custom error using the [`DataStoreError::Other`] variant from an error message and
247    /// a source error.
248    pub fn other_with_source(
249        message: impl Into<String>,
250        source: impl Error + Send + Sync + 'static,
251    ) -> Self {
252        let message: String = message.into();
253        Self::Other {
254            error_msg: message.into(),
255            source: Some(Box::new(source)),
256        }
257    }
258}
259
260// AUTHENTICATION ERROR
261// ================================================================================================
262
263#[derive(Debug, Error)]
264pub enum AuthenticationError {
265    #[error("signature rejected: {0}")]
266    RejectedSignature(String),
267    #[error("unknown public key: {0}")]
268    UnknownPublicKey(String),
269    /// Custom error variant for implementors of the
270    /// [`TransactionAuthenticatior`](crate::auth::TransactionAuthenticator) trait.
271    #[error("{error_msg}")]
272    Other {
273        error_msg: Box<str>,
274        // thiserror will return this when calling Error::source on DataStoreError.
275        source: Option<Box<dyn Error + Send + Sync + 'static>>,
276    },
277}
278
279impl AuthenticationError {
280    /// Creates a custom error using the [`AuthenticationError::Other`] variant from an error
281    /// message.
282    pub fn other(message: impl Into<String>) -> Self {
283        let message: String = message.into();
284        Self::Other { error_msg: message.into(), source: None }
285    }
286
287    /// Creates a custom error using the [`AuthenticationError::Other`] variant from an error
288    /// message and a source error.
289    pub fn other_with_source(
290        message: impl Into<String>,
291        source: impl Error + Send + Sync + 'static,
292    ) -> Self {
293        let message: String = message.into();
294        Self::Other {
295            error_msg: message.into(),
296            source: Some(Box::new(source)),
297        }
298    }
299}
300
301#[cfg(test)]
302mod error_assertions {
303    use super::*;
304
305    /// Asserts at compile time that the passed error has Send + Sync + 'static bounds.
306    fn _assert_error_is_send_sync_static<E: core::error::Error + Send + Sync + 'static>(_: E) {}
307
308    fn _assert_data_store_error_bounds(err: DataStoreError) {
309        _assert_error_is_send_sync_static(err);
310    }
311
312    fn _assert_authentication_error_bounds(err: AuthenticationError) {
313        _assert_error_is_send_sync_static(err);
314    }
315}