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