Skip to main content

miden_protocol/errors/
mod.rs

1use alloc::boxed::Box;
2use alloc::string::String;
3use alloc::vec::Vec;
4use core::error::Error;
5
6use miden_assembly::Report;
7use miden_assembly::diagnostics::reporting::PrintDiagnostic;
8use miden_core::Felt;
9use miden_core::mast::MastForestError;
10use miden_crypto::merkle::mmr::MmrError;
11use miden_crypto::merkle::smt::{SmtLeafError, SmtProofError};
12use miden_crypto::utils::HexParseError;
13use thiserror::Error;
14
15use super::account::AccountId;
16use super::asset::{AssetVaultKey, FungibleAsset, NonFungibleAsset, TokenSymbol};
17use super::crypto::merkle::MerkleError;
18use super::note::NoteId;
19use super::{MAX_BATCHES_PER_BLOCK, MAX_OUTPUT_NOTES_PER_BATCH, Word};
20use crate::account::component::{SchemaTypeError, StorageValueName, StorageValueNameError};
21use crate::account::{
22    AccountCode,
23    AccountIdPrefix,
24    AccountStorage,
25    AccountType,
26    StorageMapKey,
27    StorageSlotId,
28    StorageSlotName,
29};
30use crate::address::AddressType;
31use crate::asset::AssetId;
32use crate::batch::BatchId;
33use crate::block::BlockNumber;
34use crate::note::{
35    NoteAssets,
36    NoteAttachmentArray,
37    NoteAttachmentKind,
38    NoteAttachmentScheme,
39    NoteTag,
40    NoteType,
41    Nullifier,
42};
43use crate::transaction::TransactionId;
44use crate::utils::serde::DeserializationError;
45use crate::vm::EventId;
46use crate::{
47    ACCOUNT_UPDATE_MAX_SIZE,
48    MAX_ACCOUNTS_PER_BATCH,
49    MAX_INPUT_NOTES_PER_BATCH,
50    MAX_INPUT_NOTES_PER_TX,
51    MAX_NOTE_STORAGE_ITEMS,
52    MAX_OUTPUT_NOTES_PER_TX,
53    NOTE_MAX_SIZE,
54};
55
56#[cfg(any(feature = "testing", test))]
57mod masm_error;
58#[cfg(any(feature = "testing", test))]
59pub use masm_error::MasmError;
60
61/// The errors from the MASM code of the transaction kernel.
62#[cfg(any(feature = "testing", test))]
63pub mod tx_kernel {
64    include!(concat!(env!("OUT_DIR"), "/tx_kernel_errors.rs"));
65}
66
67/// The errors from the MASM code of the Miden protocol library.
68#[cfg(any(feature = "testing", test))]
69pub mod protocol {
70    include!(concat!(env!("OUT_DIR"), "/protocol_errors.rs"));
71}
72
73// ACCOUNT COMPONENT TEMPLATE ERROR
74// ================================================================================================
75
76#[derive(Debug, Error)]
77pub enum ComponentMetadataError {
78    #[error("storage slot name `{0}` is duplicate")]
79    DuplicateSlotName(StorageSlotName),
80    #[error("storage init value name `{0}` is duplicate")]
81    DuplicateInitValueName(StorageValueName),
82    #[error("storage value name is incorrect: {0}")]
83    IncorrectStorageValueName(#[source] StorageValueNameError),
84    #[error("invalid storage schema: {0}")]
85    InvalidSchema(String),
86    #[error("type `{0}` is not valid for `{1}` slots")]
87    InvalidType(String, String),
88    #[error("error deserializing component metadata: {0}")]
89    MetadataDeserializationError(String),
90    #[error("init storage value `{0}` was not provided")]
91    InitValueNotProvided(StorageValueName),
92    #[error("invalid init storage value for `{0}`: {1}")]
93    InvalidInitStorageValue(StorageValueName, String),
94    #[error("error converting value into expected type: {0}")]
95    StorageValueParsingError(#[source] SchemaTypeError),
96    #[error("storage map contains duplicate keys")]
97    StorageMapHasDuplicateKeys(#[source] Box<dyn Error + Send + Sync + 'static>),
98    #[cfg(feature = "std")]
99    #[error("error trying to deserialize from toml")]
100    TomlDeserializationError(#[source] toml::de::Error),
101    #[cfg(feature = "std")]
102    #[error("error trying to deserialize from toml")]
103    TomlSerializationError(#[source] toml::ser::Error),
104}
105
106// ACCOUNT ERROR
107// ================================================================================================
108
109#[derive(Debug, Error)]
110pub enum AccountError {
111    #[error("failed to deserialize account code")]
112    AccountCodeDeserializationError(#[source] DeserializationError),
113    #[error("account code does not contain an auth component")]
114    AccountCodeNoAuthComponent,
115    #[error("account code contains multiple auth components")]
116    AccountCodeMultipleAuthComponents,
117    #[error("account code must contain at least one non-auth procedure")]
118    AccountCodeNoProcedures,
119    #[error("account code contains {0} procedures but it may contain at most {max} procedures", max = AccountCode::MAX_NUM_PROCEDURES)]
120    AccountCodeTooManyProcedures(usize),
121    #[error("failed to assemble account component:\n{}", PrintDiagnostic::new(.0))]
122    AccountComponentAssemblyError(Report),
123    #[error("failed to merge components into one account code mast forest")]
124    AccountComponentMastForestMergeError(#[source] MastForestError),
125    #[error("account component contains multiple authentication procedures")]
126    AccountComponentMultipleAuthProcedures,
127    #[error("failed to update asset vault")]
128    AssetVaultUpdateError(#[source] AssetVaultError),
129    #[error("account build error: {0}")]
130    BuildError(String, #[source] Option<Box<AccountError>>),
131    #[error("failed to parse account ID from final account header")]
132    FinalAccountHeaderIdParsingFailed(#[source] AccountIdError),
133    #[error("account header data has length {actual} but it must be of length {expected}")]
134    HeaderDataIncorrectLength { actual: usize, expected: usize },
135    #[error("active account nonce {current} plus increment {increment} overflows a felt to {new}")]
136    NonceOverflow {
137        current: Felt,
138        increment: Felt,
139        new: Felt,
140    },
141    #[error(
142        "digest of the seed has {actual} trailing zeroes but must have at least {expected} trailing zeroes"
143    )]
144    SeedDigestTooFewTrailingZeros { expected: u32, actual: u32 },
145    #[error("account ID {actual} computed from seed does not match ID {expected} on account")]
146    AccountIdSeedMismatch { actual: AccountId, expected: AccountId },
147    #[error("account ID seed was provided for an existing account")]
148    ExistingAccountWithSeed,
149    #[error("account ID seed was not provided for a new account")]
150    NewAccountMissingSeed,
151    #[error(
152        "an account with a seed cannot be converted into a delta since it represents an unregistered account"
153    )]
154    DeltaFromAccountWithSeed,
155    #[error("seed converts to an invalid account ID")]
156    SeedConvertsToInvalidAccountId(#[source] AccountIdError),
157    #[error("storage map root {0} not found in the account storage")]
158    StorageMapRootNotFound(Word),
159    #[error("storage slot {0} is not of type map")]
160    StorageSlotNotMap(StorageSlotName),
161    #[error("storage slot {0} is not of type value")]
162    StorageSlotNotValue(StorageSlotName),
163    #[error("storage slot name {0} is assigned to more than one slot")]
164    DuplicateStorageSlotName(StorageSlotName),
165    #[error("storage does not contain a slot with name {slot_name}")]
166    StorageSlotNameNotFound { slot_name: StorageSlotName },
167    #[error("storage does not contain a slot with ID {slot_id}")]
168    StorageSlotIdNotFound { slot_id: StorageSlotId },
169    #[error("storage slots must be sorted by slot ID")]
170    UnsortedStorageSlots,
171    #[error("number of storage slots is {0} but max possible number is {max}", max = AccountStorage::MAX_NUM_STORAGE_SLOTS)]
172    StorageTooManySlots(u64),
173    #[error(
174        "account component at index {component_index} is incompatible with account of type {account_type}"
175    )]
176    UnsupportedComponentForAccountType {
177        account_type: AccountType,
178        component_index: usize,
179    },
180    #[error(
181        "failed to apply full state delta to existing account; full state deltas can be converted to accounts directly"
182    )]
183    ApplyFullStateDeltaToAccount,
184    #[error("only account deltas representing a full account can be converted to a full account")]
185    PartialStateDeltaToAccount,
186    #[error("maximum number of storage map leaves exceeded")]
187    MaxNumStorageMapLeavesExceeded(#[source] MerkleError),
188    /// This variant can be used by methods that are not inherent to the account but want to return
189    /// this error type.
190    #[error("{error_msg}")]
191    Other {
192        error_msg: Box<str>,
193        // thiserror will return this when calling Error::source on AccountError.
194        source: Option<Box<dyn Error + Send + Sync + 'static>>,
195    },
196}
197
198impl AccountError {
199    /// Creates a custom error using the [`AccountError::Other`] variant from an error message.
200    pub fn other(message: impl Into<String>) -> Self {
201        let message: String = message.into();
202        Self::Other { error_msg: message.into(), source: None }
203    }
204
205    /// Creates a custom error using the [`AccountError::Other`] variant from an error message and
206    /// a source error.
207    pub fn other_with_source(
208        message: impl Into<String>,
209        source: impl Error + Send + Sync + 'static,
210    ) -> Self {
211        let message: String = message.into();
212        Self::Other {
213            error_msg: message.into(),
214            source: Some(Box::new(source)),
215        }
216    }
217}
218
219// ACCOUNT ID ERROR
220// ================================================================================================
221
222#[derive(Debug, Error)]
223pub enum AccountIdError {
224    #[error("failed to convert bytes into account ID prefix field element")]
225    AccountIdInvalidPrefixFieldElement(#[source] DeserializationError),
226    #[error("failed to convert bytes into account ID suffix field element")]
227    AccountIdInvalidSuffixFieldElement(#[source] DeserializationError),
228    #[error("`{0}` is not a known account storage mode")]
229    UnknownAccountStorageMode(Box<str>),
230    #[error(r#"`{0}` is not a known account type, expected one of "FungibleFaucet", "NonFungibleFaucet", "RegularAccountImmutableCode" or "RegularAccountUpdatableCode""#)]
231    UnknownAccountType(Box<str>),
232    #[error("failed to parse hex string into account ID")]
233    AccountIdHexParseError(#[source] HexParseError),
234    #[error("`{0}` is not a known account ID version")]
235    UnknownAccountIdVersion(u8),
236    #[error("most significant bit of account ID suffix must be zero")]
237    AccountIdSuffixMostSignificantBitMustBeZero,
238    #[error("least significant byte of account ID suffix must be zero")]
239    AccountIdSuffixLeastSignificantByteMustBeZero,
240    #[error("failed to decode bech32 string into account ID")]
241    Bech32DecodeError(#[source] Bech32Error),
242}
243
244// SLOT NAME ERROR
245// ================================================================================================
246
247#[derive(Debug, Error)]
248pub enum StorageSlotNameError {
249    #[error("slot name must only contain characters a..z, A..Z, 0..9, double colon or underscore")]
250    InvalidCharacter,
251    #[error("slot names must be separated by double colons")]
252    UnexpectedColon,
253    #[error("slot name components must not start with an underscore")]
254    UnexpectedUnderscore,
255    #[error(
256        "slot names must contain at least {} components separated by double colons",
257        StorageSlotName::MIN_NUM_COMPONENTS
258    )]
259    TooShort,
260    #[error("slot names must contain at most {} characters", StorageSlotName::MAX_LENGTH)]
261    TooLong,
262}
263
264// ACCOUNT TREE ERROR
265// ================================================================================================
266
267#[derive(Debug, Error)]
268pub enum AccountTreeError {
269    #[error(
270        "account tree contains multiple account IDs that share the same prefix {duplicate_prefix}"
271    )]
272    DuplicateIdPrefix { duplicate_prefix: AccountIdPrefix },
273    #[error(
274        "entries passed to account tree contain multiple state commitments for the same account ID prefix {prefix}"
275    )]
276    DuplicateStateCommitments { prefix: AccountIdPrefix },
277    #[error("untracked account ID {id} used in partial account tree")]
278    UntrackedAccountId { id: AccountId, source: MerkleError },
279    #[error("new tree root after account witness insertion does not match previous tree root")]
280    TreeRootConflict(#[source] MerkleError),
281    #[error("failed to apply mutations to account tree")]
282    ApplyMutations(#[source] MerkleError),
283    #[error("failed to compute account tree mutations")]
284    ComputeMutations(#[source] MerkleError),
285    #[error("provided smt contains an invalid account ID in key {key}")]
286    InvalidAccountIdKey { key: Word, source: AccountIdError },
287    #[error("smt leaf's index is not a valid account ID prefix")]
288    InvalidAccountIdPrefix(#[source] AccountIdError),
289    #[error("account witness merkle path depth {0} does not match AccountTree::DEPTH")]
290    WitnessMerklePathDepthDoesNotMatchAccountTreeDepth(usize),
291}
292
293// ADDRESS ERROR
294// ================================================================================================
295
296#[derive(Debug, Error)]
297pub enum AddressError {
298    #[error("tag length {0} is too large, must be less than or equal to {max}",
299        max = NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH
300    )]
301    TagLengthTooLarge(u8),
302    #[error("unknown address interface `{0}`")]
303    UnknownAddressInterface(u16),
304    #[error("failed to decode account ID")]
305    AccountIdDecodeError(#[source] AccountIdError),
306    #[error("address separator must not be included without routing parameters")]
307    TrailingSeparator,
308    #[error("failed to decode bech32 string into an address")]
309    Bech32DecodeError(#[source] Bech32Error),
310    #[error("{error_msg}")]
311    DecodeError {
312        error_msg: Box<str>,
313        // thiserror will return this when calling Error::source on AddressError.
314        source: Option<Box<dyn Error + Send + Sync + 'static>>,
315    },
316    #[error("found unknown routing parameter key {0}")]
317    UnknownRoutingParameterKey(u8),
318}
319
320impl AddressError {
321    /// Creates an [`AddressError::DecodeError`] variant from an error message.
322    pub fn decode_error(message: impl Into<String>) -> Self {
323        let message: String = message.into();
324        Self::DecodeError { error_msg: message.into(), source: None }
325    }
326
327    /// Creates an [`AddressError::DecodeError`] variant from an error message and
328    /// a source error.
329    pub fn decode_error_with_source(
330        message: impl Into<String>,
331        source: impl Error + Send + Sync + 'static,
332    ) -> Self {
333        let message: String = message.into();
334        Self::DecodeError {
335            error_msg: message.into(),
336            source: Some(Box::new(source)),
337        }
338    }
339}
340
341// BECH32 ERROR
342// ================================================================================================
343
344#[derive(Debug, Error)]
345pub enum Bech32Error {
346    #[error(transparent)]
347    DecodeError(Box<dyn Error + Send + Sync + 'static>),
348    #[error("found unknown address type {0} which is not the expected {account_addr} account ID address type",
349      account_addr = AddressType::AccountId as u8
350    )]
351    UnknownAddressType(u8),
352    #[error("expected bech32 data to be of length {expected} but it was of length {actual}")]
353    InvalidDataLength { expected: usize, actual: usize },
354}
355
356// NETWORK ID ERROR
357// ================================================================================================
358
359#[derive(Debug, Error)]
360pub enum NetworkIdError {
361    #[error("failed to parse string into a network ID")]
362    NetworkIdParseError(#[source] Box<dyn Error + Send + Sync + 'static>),
363}
364
365// ACCOUNT DELTA ERROR
366// ================================================================================================
367
368#[derive(Debug, Error)]
369pub enum AccountDeltaError {
370    #[error("storage slot {0} was used as different slot types")]
371    StorageSlotUsedAsDifferentTypes(StorageSlotName),
372    #[error("non fungible vault can neither be added nor removed twice")]
373    DuplicateNonFungibleVaultUpdate(NonFungibleAsset),
374    #[error(
375        "fungible asset issued by faucet {faucet_id} has delta {delta} which overflows when added to current value {current}"
376    )]
377    FungibleAssetDeltaOverflow {
378        faucet_id: AccountId,
379        current: i64,
380        delta: i64,
381    },
382    #[error(
383        "account update of type `{left_update_type}` cannot be merged with account update of type `{right_update_type}`"
384    )]
385    IncompatibleAccountUpdates {
386        left_update_type: &'static str,
387        right_update_type: &'static str,
388    },
389    #[error("account delta could not be applied to account {account_id}")]
390    AccountDeltaApplicationFailed {
391        account_id: AccountId,
392        source: AccountError,
393    },
394    #[error("non-empty account storage or vault delta with zero nonce delta is not allowed")]
395    NonEmptyStorageOrVaultDeltaWithZeroNonceDelta,
396    #[error(
397        "account nonce increment {current} plus the other nonce increment {increment} overflows a felt to {new}"
398    )]
399    NonceIncrementOverflow {
400        current: Felt,
401        increment: Felt,
402        new: Felt,
403    },
404    #[error("account ID {0} in fungible asset delta is not of type fungible faucet")]
405    NotAFungibleFaucetId(AccountId),
406    #[error("cannot merge two full state deltas")]
407    MergingFullStateDeltas,
408}
409
410// STORAGE MAP ERROR
411// ================================================================================================
412
413#[derive(Debug, Error)]
414pub enum StorageMapError {
415    #[error("map entries contain key {key} twice with values {value0} and {value1}")]
416    DuplicateKey {
417        key: StorageMapKey,
418        value0: Word,
419        value1: Word,
420    },
421    #[error("map key {key} is not present in provided SMT proof")]
422    MissingKey { key: StorageMapKey },
423}
424
425// BATCH ACCOUNT UPDATE ERROR
426// ================================================================================================
427
428#[derive(Debug, Error)]
429pub enum BatchAccountUpdateError {
430    #[error(
431        "account update for account {expected_account_id} cannot be merged with update from transaction {transaction} which was executed against account {actual_account_id}"
432    )]
433    AccountUpdateIdMismatch {
434        transaction: TransactionId,
435        expected_account_id: AccountId,
436        actual_account_id: AccountId,
437    },
438    #[error(
439        "final state commitment in account update from transaction {0} does not match initial state of current update"
440    )]
441    AccountUpdateInitialStateMismatch(TransactionId),
442    #[error("failed to merge account delta from transaction {0}")]
443    TransactionUpdateMergeError(TransactionId, #[source] Box<AccountDeltaError>),
444}
445
446// ASSET ERROR
447// ================================================================================================
448
449#[derive(Debug, Error)]
450pub enum AssetError {
451    #[error(
452      "fungible asset amount {0} exceeds the max allowed amount of {max_amount}",
453      max_amount = FungibleAsset::MAX_AMOUNT
454    )]
455    FungibleAssetAmountTooBig(u64),
456    #[error("subtracting {subtrahend} from fungible asset amount {minuend} would underflow")]
457    FungibleAssetAmountNotSufficient { minuend: u64, subtrahend: u64 },
458    #[error(
459        "cannot combine fungible assets with different vault keys: {original_key} and {other_key}"
460    )]
461    FungibleAssetInconsistentVaultKeys {
462        original_key: AssetVaultKey,
463        other_key: AssetVaultKey,
464    },
465    #[error("faucet account ID in asset is invalid")]
466    InvalidFaucetAccountId(#[source] Box<dyn Error + Send + Sync + 'static>),
467    #[error(
468      "faucet id {0} of type {id_type} must be of type {expected_ty} for fungible assets",
469      id_type = .0.account_type(),
470      expected_ty = AccountType::FungibleFaucet
471    )]
472    FungibleFaucetIdTypeMismatch(AccountId),
473    #[error(
474        "asset ID prefix and suffix in a non-fungible asset's vault key must match indices 0 and 1 in the value, but asset ID was {asset_id} and value was {value}"
475    )]
476    NonFungibleAssetIdMustMatchValue { asset_id: AssetId, value: Word },
477    #[error("asset ID prefix and suffix in a fungible asset's vault key must be zero but was {0}")]
478    FungibleAssetIdMustBeZero(AssetId),
479    #[error(
480        "the three most significant elements in a fungible asset's value must be zero but provided value was {0}"
481    )]
482    FungibleAssetValueMostSignificantElementsMustBeZero(Word),
483    #[error(
484      "faucet id {0} of type {id_type} must be of type {expected_ty} for non fungible assets",
485      id_type = .0.account_type(),
486      expected_ty = AccountType::NonFungibleFaucet
487    )]
488    NonFungibleFaucetIdTypeMismatch(AccountId),
489    #[error("smt proof in asset witness contains invalid key or value")]
490    AssetWitnessInvalid(#[source] Box<AssetError>),
491    #[error("invalid native asset callbacks encoding: {0}")]
492    InvalidAssetCallbackFlag(u8),
493}
494
495// TOKEN SYMBOL ERROR
496// ================================================================================================
497
498#[derive(Debug, Error)]
499pub enum TokenSymbolError {
500    #[error("token symbol value {0} cannot exceed {max}", max = TokenSymbol::MAX_ENCODED_VALUE)]
501    ValueTooLarge(u64),
502    #[error(
503        "token symbol value {0} cannot be less than {min}",
504        min = TokenSymbol::MIN_ENCODED_VALUE
505    )]
506    ValueTooSmall(u64),
507    #[error("token symbol should have length between 1 and 12 characters, but {0} was provided")]
508    InvalidLength(usize),
509    #[error("token symbol contains a character that is not uppercase ASCII")]
510    InvalidCharacter,
511    #[error("token symbol data left after decoding the specified number of characters")]
512    DataNotFullyDecoded,
513}
514
515// ASSET VAULT ERROR
516// ================================================================================================
517
518#[derive(Debug, Error)]
519pub enum AssetVaultError {
520    #[error("adding fungible asset amounts would exceed maximum allowed amount")]
521    AddFungibleAssetBalanceError(#[source] AssetError),
522    #[error("provided assets contain duplicates")]
523    DuplicateAsset(#[source] MerkleError),
524    #[error("non fungible asset {0} already exists in the vault")]
525    DuplicateNonFungibleAsset(NonFungibleAsset),
526    #[error("fungible asset {0} does not exist in the vault")]
527    FungibleAssetNotFound(FungibleAsset),
528    #[error("faucet id {0} is not a fungible faucet id")]
529    NotAFungibleFaucetId(AccountId),
530    #[error("non fungible asset {0} does not exist in the vault")]
531    NonFungibleAssetNotFound(NonFungibleAsset),
532    #[error("subtracting fungible asset amounts would underflow")]
533    SubtractFungibleAssetBalanceError(#[source] AssetError),
534    #[error("maximum number of asset vault leaves exceeded")]
535    MaxLeafEntriesExceeded(#[source] MerkleError),
536}
537
538// PARTIAL ASSET VAULT ERROR
539// ================================================================================================
540
541#[derive(Debug, Error)]
542pub enum PartialAssetVaultError {
543    #[error("provided SMT entry {entry} is not a valid asset")]
544    InvalidAssetInSmt { entry: Word, source: AssetError },
545    #[error("failed to add asset proof")]
546    FailedToAddProof(#[source] MerkleError),
547    #[error("asset is not tracked in the partial vault")]
548    UntrackedAsset(#[source] MerkleError),
549}
550
551// NOTE ERROR
552// ================================================================================================
553
554#[derive(Debug, Error)]
555pub enum NoteError {
556    #[error("library does not contain a procedure with @note_script attribute")]
557    NoteScriptNoProcedureWithAttribute,
558    #[error("library contains multiple procedures with @note_script attribute")]
559    NoteScriptMultipleProceduresWithAttribute,
560    #[error("procedure at path '{0}' not found in library")]
561    NoteScriptProcedureNotFound(Box<str>),
562    #[error("procedure at path '{0}' does not have @note_script attribute")]
563    NoteScriptProcedureMissingAttribute(Box<str>),
564    #[error("note tag length {0} exceeds the maximum of {max}", max = NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH)]
565    NoteTagLengthTooLarge(u8),
566    #[error("duplicate fungible asset from issuer {0} in note")]
567    DuplicateFungibleAsset(AccountId),
568    #[error("duplicate non fungible asset {0} in note")]
569    DuplicateNonFungibleAsset(NonFungibleAsset),
570    #[error("note type {0} is inconsistent with note tag {1}")]
571    InconsistentNoteTag(NoteType, u64),
572    #[error("adding fungible asset amounts would exceed maximum allowed amount")]
573    AddFungibleAssetBalanceError(#[source] AssetError),
574    #[error("note sender is not a valid account ID")]
575    NoteSenderInvalidAccountId(#[source] AccountIdError),
576    #[error("note execution hint after block variant cannot contain u32::MAX")]
577    NoteExecutionHintAfterBlockCannotBeU32Max,
578    #[error("invalid note execution hint payload {1} for tag {0}")]
579    InvalidNoteExecutionHintPayload(u8, u32),
580    #[error(
581    "note type {0} does not match any of the valid note types {public} or {private}",
582    public = NoteType::Public,
583    private = NoteType::Private,
584    )]
585    UnknownNoteType(Box<str>),
586    #[error("block note tree index {block_note_tree_index} is out of bounds 0..={highest_index}")]
587    BlockNoteTreeIndexOutOfBounds {
588        block_note_tree_index: u16,
589        highest_index: usize,
590    },
591    #[error("note network execution requires a public note but note is of type {0}")]
592    NetworkExecutionRequiresPublicNote(NoteType),
593    #[error("failed to assemble note script:\n{}", PrintDiagnostic::new(.0))]
594    NoteScriptAssemblyError(Report),
595    #[error("failed to deserialize note script")]
596    NoteScriptDeserializationError(#[source] DeserializationError),
597    #[error("note contains {0} assets which exceeds the maximum of {max}", max = NoteAssets::MAX_NUM_ASSETS)]
598    TooManyAssets(usize),
599    #[error("note contains {0} storage items which exceeds the maximum of {max}", max = MAX_NOTE_STORAGE_ITEMS)]
600    TooManyStorageItems(usize),
601    #[error("invalid note storage length: expected {expected} items, got {actual}")]
602    InvalidNoteStorageLength { expected: usize, actual: usize },
603    #[error("note tag requires a public note but the note is of type {0}")]
604    PublicNoteRequired(NoteType),
605    #[error(
606        "note attachment cannot commit to more than {} elements",
607        NoteAttachmentArray::MAX_NUM_ELEMENTS
608    )]
609    NoteAttachmentArraySizeExceeded(usize),
610    #[error("unknown note attachment kind {0}")]
611    UnknownNoteAttachmentKind(u8),
612    #[error("note attachment of kind None must have attachment scheme None")]
613    AttachmentKindNoneMustHaveAttachmentSchemeNone,
614    #[error(
615        "note attachment kind mismatch: header has {header_kind:?} but attachment has {attachment_kind:?}"
616    )]
617    AttachmentKindMismatch {
618        header_kind: NoteAttachmentKind,
619        attachment_kind: NoteAttachmentKind,
620    },
621    #[error(
622        "note attachment scheme mismatch: header has {header_scheme:?} but attachment has {attachment_scheme:?}"
623    )]
624    AttachmentSchemeMismatch {
625        header_scheme: NoteAttachmentScheme,
626        attachment_scheme: NoteAttachmentScheme,
627    },
628    #[error("{error_msg}")]
629    Other {
630        error_msg: Box<str>,
631        // thiserror will return this when calling Error::source on NoteError.
632        source: Option<Box<dyn Error + Send + Sync + 'static>>,
633    },
634}
635
636impl NoteError {
637    /// Creates a custom error using the [`NoteError::Other`] variant from an error message.
638    pub fn other(message: impl Into<String>) -> Self {
639        let message: String = message.into();
640        Self::Other { error_msg: message.into(), source: None }
641    }
642
643    /// Creates a custom error using the [`NoteError::Other`] variant from an error message and
644    /// a source error.
645    pub fn other_with_source(
646        message: impl Into<String>,
647        source: impl Error + Send + Sync + 'static,
648    ) -> Self {
649        let message: String = message.into();
650        Self::Other {
651            error_msg: message.into(),
652            source: Some(Box::new(source)),
653        }
654    }
655}
656
657// PARTIAL BLOCKCHAIN ERROR
658// ================================================================================================
659
660#[derive(Debug, Error)]
661pub enum PartialBlockchainError {
662    #[error(
663        "block num {block_num} exceeds chain length {chain_length} implied by the partial blockchain"
664    )]
665    BlockNumTooBig {
666        chain_length: usize,
667        block_num: BlockNumber,
668    },
669
670    #[error("duplicate block {block_num} in partial blockchain")]
671    DuplicateBlock { block_num: BlockNumber },
672
673    #[error("partial blockchain does not track authentication paths for block {block_num}")]
674    UntrackedBlock { block_num: BlockNumber },
675
676    #[error(
677        "provided block header with number {block_num} and commitment {block_commitment} is not tracked by partial MMR"
678    )]
679    BlockHeaderCommitmentMismatch {
680        block_num: BlockNumber,
681        block_commitment: Word,
682        source: MmrError,
683    },
684}
685
686impl PartialBlockchainError {
687    pub fn block_num_too_big(chain_length: usize, block_num: BlockNumber) -> Self {
688        Self::BlockNumTooBig { chain_length, block_num }
689    }
690
691    pub fn duplicate_block(block_num: BlockNumber) -> Self {
692        Self::DuplicateBlock { block_num }
693    }
694
695    pub fn untracked_block(block_num: BlockNumber) -> Self {
696        Self::UntrackedBlock { block_num }
697    }
698}
699
700// TRANSACTION SCRIPT ERROR
701// ================================================================================================
702
703#[derive(Debug, Error)]
704pub enum TransactionScriptError {
705    #[error("failed to assemble transaction script:\n{}", PrintDiagnostic::new(.0))]
706    AssemblyError(Report),
707}
708
709// TRANSACTION INPUT ERROR
710// ================================================================================================
711
712#[derive(Debug, Error)]
713pub enum TransactionInputError {
714    #[error("transaction input note with nullifier {0} is a duplicate")]
715    DuplicateInputNote(Nullifier),
716    #[error("partial blockchain has length {actual} which does not match block number {expected}")]
717    InconsistentChainLength {
718        expected: BlockNumber,
719        actual: BlockNumber,
720    },
721    #[error(
722        "partial blockchain has commitment {actual} which does not match the block header's chain commitment {expected}"
723    )]
724    InconsistentChainCommitment { expected: Word, actual: Word },
725    #[error("block in which input note with id {0} was created is not in partial blockchain")]
726    InputNoteBlockNotInPartialBlockchain(NoteId),
727    #[error("input note with id {0} was not created in block {1}")]
728    InputNoteNotInBlock(NoteId, BlockNumber),
729    #[error(
730        "total number of input notes is {0} which exceeds the maximum of {MAX_INPUT_NOTES_PER_TX}"
731    )]
732    TooManyInputNotes(usize),
733}
734
735// TRANSACTION INPUTS EXTRACTION ERROR
736// ===============================================================================================
737
738#[derive(Debug, Error)]
739pub enum TransactionInputsExtractionError {
740    #[error("specified foreign account id matches the transaction input's account id")]
741    AccountNotForeign,
742    #[error("foreign account data not found in advice map for account {0}")]
743    ForeignAccountNotFound(AccountId),
744    #[error("foreign account code not found for account {0}")]
745    ForeignAccountCodeNotFound(AccountId),
746    #[error("storage header data not found in advice map for account {0}")]
747    StorageHeaderNotFound(AccountId),
748    #[error("failed to handle account data")]
749    AccountError(#[from] AccountError),
750    #[error("failed to handle merkle data")]
751    MerkleError(#[from] MerkleError),
752    #[error("failed to handle account tree data")]
753    AccountTreeError(#[from] AccountTreeError),
754    #[error("missing vault root from Merkle store")]
755    MissingVaultRoot,
756    #[error("missing storage map root from Merkle store")]
757    MissingMapRoot,
758    #[error("failed to construct SMT proof")]
759    SmtProofError(#[from] SmtProofError),
760    #[error("failed to construct an asset")]
761    AssetError(#[from] AssetError),
762    #[error("failed to handle storage map data")]
763    StorageMapError(#[from] StorageMapError),
764    #[error("failed to convert elements to leaf index: {0}")]
765    LeafConversionError(String),
766    #[error("failed to construct SMT leaf")]
767    SmtLeafError(#[from] SmtLeafError),
768}
769
770// TRANSACTION OUTPUT ERROR
771// ===============================================================================================
772
773#[derive(Debug, Error)]
774pub enum TransactionOutputError {
775    #[error("transaction output note with id {0} is a duplicate")]
776    DuplicateOutputNote(NoteId),
777    #[error("final account commitment is not in the advice map")]
778    FinalAccountCommitmentMissingInAdviceMap,
779    #[error("fee asset is not a fungible asset")]
780    FeeAssetNotFungibleAsset(#[source] AssetError),
781    #[error("failed to parse final account header")]
782    FinalAccountHeaderParseFailure(#[source] AccountError),
783    #[error(
784        "output notes commitment {expected} from kernel does not match computed commitment {actual}"
785    )]
786    OutputNotesCommitmentInconsistent { expected: Word, actual: Word },
787    #[error("transaction kernel output stack is invalid: {0}")]
788    OutputStackInvalid(String),
789    #[error(
790        "total number of output notes is {0} which exceeds the maximum of {MAX_OUTPUT_NOTES_PER_TX}"
791    )]
792    TooManyOutputNotes(usize),
793    #[error("failed to process account update commitment: {0}")]
794    AccountUpdateCommitment(Box<str>),
795}
796
797// OUTPUT NOTE ERROR
798// ================================================================================================
799
800/// Errors that can occur when creating a
801/// [`PublicOutputNote`](crate::transaction::PublicOutputNote) or
802/// [`PrivateNoteHeader`](crate::transaction::PrivateNoteHeader).
803#[derive(Debug, Error)]
804pub enum OutputNoteError {
805    #[error("note with id {0} is private but expected a public note")]
806    NoteIsPrivate(NoteId),
807    #[error("note with id {0} is public but expected a private note")]
808    NoteIsPublic(NoteId),
809    #[error(
810        "public note with id {note_id} has size {note_size} bytes which exceeds maximum note size of {NOTE_MAX_SIZE}"
811    )]
812    NoteSizeLimitExceeded { note_id: NoteId, note_size: usize },
813}
814
815// TRANSACTION EVENT PARSING ERROR
816// ================================================================================================
817
818#[derive(Debug, Error)]
819pub enum TransactionEventError {
820    #[error("event id {0} is not a valid transaction event")]
821    InvalidTransactionEvent(EventId),
822}
823
824// TRANSACTION TRACE PARSING ERROR
825// ================================================================================================
826
827#[derive(Debug, Error)]
828pub enum TransactionTraceParsingError {
829    #[error("trace id {0} is an unknown transaction kernel trace")]
830    UnknownTransactionTrace(u32),
831}
832
833// PROVEN TRANSACTION ERROR
834// ================================================================================================
835
836#[derive(Debug, Error)]
837pub enum ProvenTransactionError {
838    #[error(
839        "proven transaction's final account commitment {tx_final_commitment} and account details commitment {details_commitment} must match"
840    )]
841    AccountFinalCommitmentMismatch {
842        tx_final_commitment: Word,
843        details_commitment: Word,
844    },
845    #[error(
846        "proven transaction's final account ID {tx_account_id} and account details id {details_account_id} must match"
847    )]
848    AccountIdMismatch {
849        tx_account_id: AccountId,
850        details_account_id: AccountId,
851    },
852    #[error("failed to construct input notes for proven transaction")]
853    InputNotesError(TransactionInputError),
854    #[error("private account {0} should not have account details")]
855    PrivateAccountWithDetails(AccountId),
856    #[error("account {0} with public state is missing its account details")]
857    PublicStateAccountMissingDetails(AccountId),
858    #[error("new account {id} with public state must be accompanied by a full state delta")]
859    NewPublicStateAccountRequiresFullStateDelta { id: AccountId, source: AccountError },
860    #[error(
861        "existing account {0} with public state should only provide delta updates instead of full details"
862    )]
863    ExistingPublicStateAccountRequiresDeltaDetails(AccountId),
864    #[error("failed to construct output notes for proven transaction")]
865    OutputNotesError(#[source] TransactionOutputError),
866    #[error(
867        "account update of size {update_size} for account {account_id} exceeds maximum update size of {ACCOUNT_UPDATE_MAX_SIZE}"
868    )]
869    AccountUpdateSizeLimitExceeded {
870        account_id: AccountId,
871        update_size: usize,
872    },
873    #[error("proven transaction neither changed the account state, nor consumed any notes")]
874    EmptyTransaction,
875    #[error("failed to validate account delta in transaction account update")]
876    AccountDeltaCommitmentMismatch(#[source] Box<dyn Error + Send + Sync + 'static>),
877}
878
879// PROPOSED BATCH ERROR
880// ================================================================================================
881
882#[derive(Debug, Error)]
883pub enum ProposedBatchError {
884    #[error(
885        "transaction batch has {0} input notes but at most {MAX_INPUT_NOTES_PER_BATCH} are allowed"
886    )]
887    TooManyInputNotes(usize),
888
889    #[error(
890        "transaction batch has {0} output notes but at most {MAX_OUTPUT_NOTES_PER_BATCH} are allowed"
891    )]
892    TooManyOutputNotes(usize),
893
894    #[error(
895        "transaction batch has {0} account updates but at most {MAX_ACCOUNTS_PER_BATCH} are allowed"
896    )]
897    TooManyAccountUpdates(usize),
898
899    #[error(
900        "transaction {transaction_id} expires at block number {transaction_expiration_num} which is not greater than the number of the batch's reference block {reference_block_num}"
901    )]
902    ExpiredTransaction {
903        transaction_id: TransactionId,
904        transaction_expiration_num: BlockNumber,
905        reference_block_num: BlockNumber,
906    },
907
908    #[error("transaction batch must contain at least one transaction")]
909    EmptyTransactionBatch,
910
911    #[error("transaction {transaction_id} appears twice in the proposed batch input")]
912    DuplicateTransaction { transaction_id: TransactionId },
913
914    #[error(
915        "transaction {second_transaction_id} consumes the note with nullifier {note_nullifier} that is also consumed by another transaction {first_transaction_id} in the batch"
916    )]
917    DuplicateInputNote {
918        note_nullifier: Nullifier,
919        first_transaction_id: TransactionId,
920        second_transaction_id: TransactionId,
921    },
922
923    #[error(
924        "transaction {second_transaction_id} creates the note with id {note_id} that is also created by another transaction {first_transaction_id} in the batch"
925    )]
926    DuplicateOutputNote {
927        note_id: NoteId,
928        first_transaction_id: TransactionId,
929        second_transaction_id: TransactionId,
930    },
931
932    #[error(
933        "note commitment mismatch for note {id}: (input: {input_commitment}, output: {output_commitment})"
934    )]
935    NoteCommitmentMismatch {
936        id: NoteId,
937        input_commitment: Word,
938        output_commitment: Word,
939    },
940
941    #[error("failed to merge transaction delta into account {account_id}")]
942    AccountUpdateError {
943        account_id: AccountId,
944        source: BatchAccountUpdateError,
945    },
946
947    #[error(
948        "unable to prove unauthenticated note inclusion because block {block_number} in which note with id {note_id} was created is not in partial blockchain"
949    )]
950    UnauthenticatedInputNoteBlockNotInPartialBlockchain {
951        block_number: BlockNumber,
952        note_id: NoteId,
953    },
954
955    #[error(
956        "unable to prove unauthenticated note inclusion of note {note_id} in block {block_num}"
957    )]
958    UnauthenticatedNoteAuthenticationFailed {
959        note_id: NoteId,
960        block_num: BlockNumber,
961        source: MerkleError,
962    },
963
964    #[error("partial blockchain has length {actual} which does not match block number {expected}")]
965    InconsistentChainLength {
966        expected: BlockNumber,
967        actual: BlockNumber,
968    },
969
970    #[error(
971        "partial blockchain has root {actual} which does not match block header's root {expected}"
972    )]
973    InconsistentChainRoot { expected: Word, actual: Word },
974
975    #[error(
976        "block {block_reference} referenced by transaction {transaction_id} is not in the partial blockchain"
977    )]
978    MissingTransactionBlockReference {
979        block_reference: Word,
980        transaction_id: TransactionId,
981    },
982}
983
984// PROVEN BATCH ERROR
985// ================================================================================================
986
987#[derive(Debug, Error)]
988pub enum ProvenBatchError {
989    #[error("failed to verify transaction {transaction_id} in transaction batch")]
990    TransactionVerificationFailed {
991        transaction_id: TransactionId,
992        source: Box<dyn Error + Send + Sync + 'static>,
993    },
994    #[error(
995        "batch expiration block number {batch_expiration_block_num} is not greater than the reference block number {reference_block_num}"
996    )]
997    InvalidBatchExpirationBlockNum {
998        batch_expiration_block_num: BlockNumber,
999        reference_block_num: BlockNumber,
1000    },
1001}
1002
1003// PROPOSED BLOCK ERROR
1004// ================================================================================================
1005
1006#[derive(Debug, Error)]
1007pub enum ProposedBlockError {
1008    #[error("block must contain at least one transaction batch")]
1009    EmptyBlock,
1010
1011    #[error("block must contain at most {MAX_BATCHES_PER_BLOCK} transaction batches")]
1012    TooManyBatches,
1013
1014    #[error(
1015        "batch {batch_id} expired at block {batch_expiration_block_num} but the current block number is {current_block_num}"
1016    )]
1017    ExpiredBatch {
1018        batch_id: BatchId,
1019        batch_expiration_block_num: BlockNumber,
1020        current_block_num: BlockNumber,
1021    },
1022
1023    #[error("batch {batch_id} appears twice in the block inputs")]
1024    DuplicateBatch { batch_id: BatchId },
1025
1026    #[error(
1027        "batch {second_batch_id} consumes the note with nullifier {note_nullifier} that is also consumed by another batch {first_batch_id} in the block"
1028    )]
1029    DuplicateInputNote {
1030        note_nullifier: Nullifier,
1031        first_batch_id: BatchId,
1032        second_batch_id: BatchId,
1033    },
1034
1035    #[error(
1036        "batch {second_batch_id} creates the note with ID {note_id} that is also created by another batch {first_batch_id} in the block"
1037    )]
1038    DuplicateOutputNote {
1039        note_id: NoteId,
1040        first_batch_id: BatchId,
1041        second_batch_id: BatchId,
1042    },
1043
1044    #[error(
1045        "timestamp {provided_timestamp} does not increase monotonically compared to timestamp {previous_timestamp} from the previous block header"
1046    )]
1047    TimestampDoesNotIncreaseMonotonically {
1048        provided_timestamp: u32,
1049        previous_timestamp: u32,
1050    },
1051
1052    #[error(
1053        "account {account_id} is updated from the same initial state commitment {initial_state_commitment} by multiple conflicting batches with IDs {first_batch_id} and {second_batch_id}"
1054    )]
1055    ConflictingBatchesUpdateSameAccount {
1056        account_id: AccountId,
1057        initial_state_commitment: Word,
1058        first_batch_id: BatchId,
1059        second_batch_id: BatchId,
1060    },
1061
1062    #[error(
1063        "partial blockchain has length {chain_length} which does not match the block number {prev_block_num} of the previous block referenced by the to-be-built block"
1064    )]
1065    ChainLengthNotEqualToPreviousBlockNumber {
1066        chain_length: BlockNumber,
1067        prev_block_num: BlockNumber,
1068    },
1069
1070    #[error(
1071        "partial blockchain has commitment {chain_commitment} which does not match the chain commitment {prev_block_chain_commitment} of the previous block {prev_block_num}"
1072    )]
1073    ChainRootNotEqualToPreviousBlockChainCommitment {
1074        chain_commitment: Word,
1075        prev_block_chain_commitment: Word,
1076        prev_block_num: BlockNumber,
1077    },
1078
1079    #[error(
1080        "partial blockchain is missing block {reference_block_num} referenced by batch {batch_id} in the block"
1081    )]
1082    BatchReferenceBlockMissingFromChain {
1083        reference_block_num: BlockNumber,
1084        batch_id: BatchId,
1085    },
1086
1087    #[error(
1088        "note commitment mismatch for note {id}: (input: {input_commitment}, output: {output_commitment})"
1089    )]
1090    NoteCommitmentMismatch {
1091        id: NoteId,
1092        input_commitment: Word,
1093        output_commitment: Word,
1094    },
1095
1096    #[error(
1097        "failed to prove unauthenticated note inclusion because block {block_number} in which note with id {note_id} was created is not in partial blockchain"
1098    )]
1099    UnauthenticatedInputNoteBlockNotInPartialBlockchain {
1100        block_number: BlockNumber,
1101        note_id: NoteId,
1102    },
1103
1104    #[error(
1105        "failed to prove unauthenticated note inclusion of note {note_id} in block {block_num}"
1106    )]
1107    UnauthenticatedNoteAuthenticationFailed {
1108        note_id: NoteId,
1109        block_num: BlockNumber,
1110        source: MerkleError,
1111    },
1112
1113    #[error(
1114        "unauthenticated note with nullifier {nullifier} was not created in the same block and no inclusion proof to authenticate it was provided"
1115    )]
1116    UnauthenticatedNoteConsumed { nullifier: Nullifier },
1117
1118    #[error("block inputs do not contain a proof of inclusion for account {0}")]
1119    MissingAccountWitness(AccountId),
1120
1121    #[error(
1122        "account {account_id} with state {state_commitment} cannot transition to any of the remaining states {}",
1123        remaining_state_commitments.iter().map(Word::to_hex).collect::<Vec<_>>().join(", ")
1124    )]
1125    InconsistentAccountStateTransition {
1126        account_id: AccountId,
1127        state_commitment: Word,
1128        remaining_state_commitments: Vec<Word>,
1129    },
1130
1131    #[error("no proof for nullifier {0} was provided")]
1132    NullifierProofMissing(Nullifier),
1133
1134    #[error("note with nullifier {0} is already spent")]
1135    NullifierSpent(Nullifier),
1136
1137    #[error("failed to merge transaction delta into account {account_id}")]
1138    AccountUpdateError {
1139        account_id: AccountId,
1140        source: Box<AccountDeltaError>,
1141    },
1142
1143    #[error("failed to track account witness")]
1144    AccountWitnessTracking { source: AccountTreeError },
1145
1146    #[error(
1147        "account tree root of the previous block header is {prev_block_account_root} but the root of the partial tree computed from account witnesses is {stale_account_root}, indicating that the witnesses are stale"
1148    )]
1149    StaleAccountTreeRoot {
1150        prev_block_account_root: Word,
1151        stale_account_root: Word,
1152    },
1153
1154    #[error("account ID prefix already exists in the tree")]
1155    AccountIdPrefixDuplicate { source: AccountTreeError },
1156
1157    #[error(
1158        "nullifier tree root of the previous block header is {prev_block_nullifier_root} but the root of the partial tree computed from nullifier witnesses is {stale_nullifier_root}, indicating that the witnesses are stale"
1159    )]
1160    StaleNullifierTreeRoot {
1161        prev_block_nullifier_root: Word,
1162        stale_nullifier_root: Word,
1163    },
1164
1165    #[error("nullifier witness has a different root than the current nullifier tree root")]
1166    NullifierWitnessRootMismatch(NullifierTreeError),
1167}
1168
1169// FEE ERROR
1170// ================================================================================================
1171
1172#[derive(Debug, Error)]
1173pub enum FeeError {
1174    #[error("native asset of the chain must be a fungible faucet but was of type {account_type}")]
1175    NativeAssetIdNotFungible { account_type: AccountType },
1176}
1177
1178// NULLIFIER TREE ERROR
1179// ================================================================================================
1180
1181#[derive(Debug, Error)]
1182pub enum NullifierTreeError {
1183    #[error(
1184        "entries passed to nullifier tree contain multiple block numbers for the same nullifier"
1185    )]
1186    DuplicateNullifierBlockNumbers(#[source] MerkleError),
1187
1188    #[error("attempt to mark nullifier {0} as spent but it is already spent")]
1189    NullifierAlreadySpent(Nullifier),
1190
1191    #[error("maximum number of nullifier tree leaves exceeded")]
1192    MaxLeafEntriesExceeded(#[source] MerkleError),
1193
1194    #[error("nullifier {nullifier} is not tracked by the partial nullifier tree")]
1195    UntrackedNullifier {
1196        nullifier: Nullifier,
1197        source: MerkleError,
1198    },
1199
1200    #[error("new tree root after nullifier witness insertion does not match previous tree root")]
1201    TreeRootConflict(#[source] MerkleError),
1202
1203    #[error("failed to compute nullifier tree mutations")]
1204    ComputeMutations(#[source] MerkleError),
1205
1206    #[error("invalid nullifier block number")]
1207    InvalidNullifierBlockNumber(Word),
1208}
1209
1210// AUTH SCHEME ERROR
1211// ================================================================================================
1212
1213#[derive(Debug, Error)]
1214pub enum AuthSchemeError {
1215    #[error("auth scheme identifier `{0}` is not valid")]
1216    InvalidAuthSchemeIdentifier(String),
1217}