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_crypto::merkle::mmr::MmrError;
10use miden_crypto::merkle::smt::{SmtLeafError, SmtProofError};
11use miden_crypto::utils::HexParseError;
12use miden_processor::ExecutionError;
13use miden_verifier::VerificationError;
14use thiserror::Error;
15
16use super::account::{AccountId, RoleSymbol};
17use super::asset::{AssetComposition, AssetId, 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    AccountProcedureRoot,
26    AccountStorage,
27    StorageMapKey,
28    StorageSlotId,
29    StorageSlotName,
30};
31use crate::address::AddressType;
32use crate::asset::AssetClass;
33use crate::batch::BatchId;
34use crate::block::BlockNumber;
35use crate::note::{
36    NoteAssets,
37    NoteAttachment,
38    NoteAttachmentScheme,
39    NoteAttachments,
40    NoteTag,
41    NoteType,
42    Nullifier,
43};
44use crate::transaction::TransactionId;
45use crate::utils::serde::DeserializationError;
46use crate::vm::EventId;
47use crate::{
48    ACCOUNT_UPDATE_MAX_SIZE,
49    Felt,
50    MAX_ACCOUNTS_PER_BATCH,
51    MAX_INPUT_NOTES_PER_BATCH,
52    MAX_INPUT_NOTES_PER_TX,
53    MAX_NOTE_STORAGE_ITEMS,
54    MAX_OUTPUT_NOTES_PER_TX,
55    NOTE_MAX_SIZE,
56};
57
58#[cfg(any(feature = "testing", test))]
59mod masm_error;
60#[cfg(any(feature = "testing", test))]
61pub use masm_error::MasmError;
62
63/// The errors from the MASM code of the transaction kernel.
64#[cfg(any(feature = "testing", test))]
65pub mod tx_kernel {
66    include!(concat!(env!("OUT_DIR"), "/tx_kernel_errors.rs"));
67}
68
69/// The errors from the MASM code of the Miden protocol library.
70#[cfg(any(feature = "testing", test))]
71pub mod protocol {
72    include!(concat!(env!("OUT_DIR"), "/protocol_errors.rs"));
73}
74
75// ACCOUNT COMPONENT TEMPLATE ERROR
76// ================================================================================================
77
78#[derive(Debug, Error)]
79pub enum ComponentMetadataError {
80    #[error("storage slot name `{0}` is duplicate")]
81    DuplicateSlotName(StorageSlotName),
82    #[error("storage init value name `{0}` is duplicate")]
83    DuplicateInitValueName(StorageValueName),
84    #[error("storage value name is incorrect: {0}")]
85    IncorrectStorageValueName(#[source] StorageValueNameError),
86    #[error("invalid storage schema: {0}")]
87    InvalidSchema(String),
88    #[error("type `{0}` is not valid for `{1}` slots")]
89    InvalidType(String, String),
90    #[error("error deserializing component metadata: {0}")]
91    MetadataDeserializationError(String),
92    #[error("init storage value `{0}` was not provided")]
93    InitValueNotProvided(StorageValueName),
94    #[error("invalid init storage value for `{0}`: {1}")]
95    InvalidInitStorageValue(StorageValueName, String),
96    #[error("error converting value into expected type: {0}")]
97    StorageValueParsingError(#[source] SchemaTypeError),
98    #[error("storage map contains duplicate keys")]
99    StorageMapHasDuplicateKeys(#[source] Box<dyn Error + Send + Sync + 'static>),
100    #[cfg(feature = "std")]
101    #[error("error trying to deserialize from toml")]
102    TomlDeserializationError(#[source] toml::de::Error),
103    #[cfg(feature = "std")]
104    #[error("error trying to deserialize from toml")]
105    TomlSerializationError(#[source] toml::ser::Error),
106}
107
108// ACCOUNT ERROR
109// ================================================================================================
110
111#[derive(Debug, Error)]
112pub enum AccountError {
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 procedure {0} is not contained in the provided mast forest")]
120    AccountCodeProcedureNotInMastForest(AccountProcedureRoot),
121    #[error("account code contains {0} procedures but it may contain at most {max} procedures", max = AccountCode::MAX_NUM_PROCEDURES)]
122    AccountCodeTooManyProcedures(usize),
123    #[error("account code contains a duplicate procedure with root {0}")]
124    AccountCodeDuplicateProcedureRoot(Word),
125    #[error("failed to assemble account component:\n{}", PrintDiagnostic::new(.0))]
126    AccountComponentAssemblyError(Report),
127    #[error("failed to merge components into one account code mast forest")]
128    AccountComponentMastForestMergeError(#[source] MastForestError),
129    #[error("account component contains multiple authentication procedures")]
130    AccountComponentMultipleAuthProcedures,
131    #[error("failed to update asset vault")]
132    AssetVaultUpdateError(#[source] AssetVaultError),
133    #[error("account build error: {0}")]
134    BuildError(String, #[source] Option<Box<AccountError>>),
135    #[error("failed to parse account ID from final account header")]
136    FinalAccountHeaderIdParsingFailed(#[source] AccountIdError),
137    #[error("account header data has length {actual} but it must be of length {expected}")]
138    HeaderDataIncorrectLength { actual: usize, expected: usize },
139    #[error("final nonce {new} is not strictly greater than current account nonce {current}")]
140    NonceMustIncrease { current: Felt, new: Felt },
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(
156        "an account with a seed cannot be converted into a patch since it represents an unregistered account"
157    )]
158    PatchFromAccountWithSeed,
159    #[error("seed converts to an invalid account ID")]
160    SeedConvertsToInvalidAccountId(#[source] AccountIdError),
161    #[error("storage map root {0} not found in the account storage")]
162    StorageMapRootNotFound(Word),
163    #[error("storage slot {0} is not of type map")]
164    StorageSlotNotMap(StorageSlotName),
165    #[error("storage slot {0} is not of type value")]
166    StorageSlotNotValue(StorageSlotName),
167    #[error("storage slot name {0} is assigned to more than one slot")]
168    DuplicateStorageSlotName(StorageSlotName),
169    #[error("storage does not contain a slot with name {slot_name}")]
170    StorageSlotNameNotFound { slot_name: StorageSlotName },
171    #[error("storage does not contain a slot with ID {slot_id}")]
172    StorageSlotIdNotFound { slot_id: StorageSlotId },
173    #[error("storage slots must be sorted by slot ID")]
174    UnsortedStorageSlots,
175    #[error("number of storage slots is {0} but max possible number is {max}", max = AccountStorage::MAX_NUM_STORAGE_SLOTS)]
176    StorageTooManySlots(u64),
177    #[error(
178        "failed to apply full state patch to existing account; full state patches can be converted to accounts directly"
179    )]
180    ApplyFullStatePatchToAccount,
181    #[error("patch is for account ID {patch_id} but is being applied to account {account_id}")]
182    PatchAccountIdMismatch {
183        account_id: AccountId,
184        patch_id: AccountId,
185    },
186    #[error("only account deltas representing a full account can be converted to a full account")]
187    PartialStateDeltaToAccount,
188    #[error("assets cannot be removed from a new account with an empty asset vault")]
189    AssetsRemovedFromNewAccount,
190    #[error("only account patches representing a full account can be converted to a full account")]
191    PartialStatePatchToAccount,
192    #[error("maximum number of storage map leaves exceeded")]
193    MaxNumStorageMapLeavesExceeded(#[source] MerkleError),
194    #[error("unknown storage patch operation tag {0}")]
195    UnknownStoragePatchOperation(u8),
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 type")]
237    UnknownAccountType(Box<str>),
238    #[error("failed to parse hex string into account ID")]
239    AccountIdHexParseError(#[source] HexParseError),
240    #[error("`{0}` is not a known account ID version")]
241    UnknownAccountIdVersion(u8),
242    #[error("most significant bit of account ID suffix must be zero")]
243    AccountIdSuffixMostSignificantBitMustBeZero,
244    #[error("least significant byte of account ID suffix must be zero")]
245    AccountIdSuffixLeastSignificantByteMustBeZero,
246    #[error("failed to decode bech32 string into account ID")]
247    Bech32DecodeError(#[source] Bech32Error),
248}
249
250// SLOT NAME ERROR
251// ================================================================================================
252
253#[derive(Debug, Error)]
254pub enum StorageSlotNameError {
255    #[error("slot name must only contain characters a..z, A..Z, 0..9, double colon or underscore")]
256    InvalidCharacter,
257    #[error("slot names must be separated by double colons")]
258    UnexpectedColon,
259    #[error("slot name components must not start with an underscore")]
260    UnexpectedUnderscore,
261    #[error(
262        "slot names must contain at least {} components separated by double colons",
263        StorageSlotName::MIN_NUM_COMPONENTS
264    )]
265    TooShort,
266    #[error("slot names must contain at most {} characters", StorageSlotName::MAX_LENGTH)]
267    TooLong,
268}
269
270// ACCOUNT CODE INTERFACE ERROR
271// ================================================================================================
272
273#[derive(Debug, Error)]
274pub enum AccountCodeInterfaceError {
275    #[error(
276        "account code interface must contain at least {} procedures, but only {actual} were given",
277        AccountCode::MIN_NUM_PROCEDURES
278    )]
279    TooFewProcedures { actual: usize },
280    #[error(
281        "account code interface contains {actual} procedures but it may contain at most {} procedures",
282        AccountCode::MAX_NUM_PROCEDURES
283    )]
284    TooManyProcedures { actual: usize },
285}
286
287// ACCOUNT COMPONENT NAME ERROR
288// ================================================================================================
289
290#[derive(Debug, Error)]
291pub enum AccountComponentNameError {
292    #[error(
293        "account component name must only contain characters a..z, A..Z, 0..9, double colon or underscore"
294    )]
295    InvalidCharacter,
296    #[error("account component names must be separated by double colons")]
297    UnexpectedColon,
298    #[error("account component name components must not start with an underscore")]
299    UnexpectedUnderscore,
300    #[error(
301        "account component names must contain at least {} components separated by double colons",
302        StorageSlotName::MIN_NUM_COMPONENTS
303    )]
304    TooShort,
305    #[error(
306        "account component names must contain at most {} characters",
307        StorageSlotName::MAX_LENGTH
308    )]
309    TooLong,
310}
311
312// ACCOUNT TREE ERROR
313// ================================================================================================
314
315#[derive(Debug, Error)]
316pub enum AccountTreeError {
317    #[error(
318        "account tree contains multiple account IDs that share the same prefix {duplicate_prefix}"
319    )]
320    DuplicateIdPrefix { duplicate_prefix: AccountIdPrefix },
321    #[error(
322        "entries passed to account tree contain multiple state commitments for the same account ID prefix {prefix}"
323    )]
324    DuplicateStateCommitments { prefix: AccountIdPrefix },
325    #[error("untracked account ID {id} used in partial account tree")]
326    UntrackedAccountId { id: AccountId, source: MerkleError },
327    #[error("new tree root after account witness insertion does not match previous tree root")]
328    TreeRootConflict(#[source] MerkleError),
329    #[error("failed to apply mutations to account tree")]
330    ApplyMutations(#[source] MerkleError),
331    #[error("failed to compute account tree mutations")]
332    ComputeMutations(#[source] MerkleError),
333    #[error("provided smt contains an invalid account ID in key {key}")]
334    InvalidAccountIdKey { key: Word, source: AccountIdError },
335    #[error("smt leaf's index is not a valid account ID prefix")]
336    InvalidAccountIdPrefix(#[source] AccountIdError),
337    #[error("account witness merkle path depth {0} does not match AccountTree::DEPTH")]
338    WitnessMerklePathDepthDoesNotMatchAccountTreeDepth(usize),
339}
340
341// ADDRESS ERROR
342// ================================================================================================
343
344#[derive(Debug, Error)]
345pub enum AddressError {
346    #[error("tag length {0} is too large, must be less than or equal to {max}",
347        max = NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH
348    )]
349    TagLengthTooLarge(u8),
350    #[error("unknown address interface `{0}`")]
351    UnknownAddressInterface(u16),
352    #[error("failed to decode account ID")]
353    AccountIdDecodeError(#[source] AccountIdError),
354    #[error("address separator must not be included without routing parameters")]
355    TrailingSeparator,
356    #[error("failed to decode bech32 string into an address")]
357    Bech32DecodeError(#[source] Bech32Error),
358    #[error("{error_msg}")]
359    DecodeError {
360        error_msg: Box<str>,
361        // thiserror will return this when calling Error::source on AddressError.
362        source: Option<Box<dyn Error + Send + Sync + 'static>>,
363    },
364    #[error("found unknown routing parameter key {0}")]
365    UnknownRoutingParameterKey(u8),
366}
367
368impl AddressError {
369    /// Creates an [`AddressError::DecodeError`] variant from an error message.
370    pub fn decode_error(message: impl Into<String>) -> Self {
371        let message: String = message.into();
372        Self::DecodeError { error_msg: message.into(), source: None }
373    }
374
375    /// Creates an [`AddressError::DecodeError`] variant from an error message and
376    /// a source error.
377    pub fn decode_error_with_source(
378        message: impl Into<String>,
379        source: impl Error + Send + Sync + 'static,
380    ) -> Self {
381        let message: String = message.into();
382        Self::DecodeError {
383            error_msg: message.into(),
384            source: Some(Box::new(source)),
385        }
386    }
387}
388
389// BECH32 ERROR
390// ================================================================================================
391
392#[derive(Debug, Error)]
393pub enum Bech32Error {
394    #[error(transparent)]
395    DecodeError(Box<dyn Error + Send + Sync + 'static>),
396    #[error("found unknown address type {0} which is not the expected {account_addr} account ID address type",
397      account_addr = AddressType::AccountId as u8
398    )]
399    UnknownAddressType(u8),
400    #[error("expected bech32 data to be of length {expected} but it was of length {actual}")]
401    InvalidDataLength { expected: usize, actual: usize },
402}
403
404// NETWORK ID ERROR
405// ================================================================================================
406
407#[derive(Debug, Error)]
408pub enum NetworkIdError {
409    #[error("failed to parse string into a network ID")]
410    NetworkIdParseError(#[source] Box<dyn Error + Send + Sync + 'static>),
411}
412
413// ACCOUNT DELTA ERROR
414// ================================================================================================
415
416#[derive(Debug, Error)]
417pub enum AccountDeltaError {
418    #[error("storage slot {0} was used as different slot types")]
419    StorageSlotUsedAsDifferentTypes(StorageSlotName),
420    #[error("non fungible vault can neither be added nor removed twice")]
421    DuplicateNonFungibleVaultUpdate(NonFungibleAsset),
422    #[error(
423        "fungible asset issued by faucet {faucet_id} has delta {delta} which overflows when added to current value {current}"
424    )]
425    FungibleAssetDeltaOverflow {
426        faucet_id: AccountId,
427        current: i64,
428        delta: i64,
429    },
430    #[error(
431        "account update of type `{left_update_type}` cannot be merged with account update of type `{right_update_type}`"
432    )]
433    IncompatibleAccountUpdates {
434        left_update_type: &'static str,
435        right_update_type: &'static str,
436    },
437    #[error("account delta could not be applied to account {account_id}")]
438    AccountDeltaApplicationFailed {
439        account_id: AccountId,
440        source: AccountError,
441    },
442    #[error("non-empty account storage or vault delta with zero nonce delta is not allowed")]
443    NonEmptyStorageOrVaultDeltaWithZeroNonceDelta,
444    #[error(
445        "asset issued by faucet {0} in fungible asset delta does not have fungible composition"
446    )]
447    NotAFungibleFaucetId(AccountId),
448    #[error("cannot merge two full state deltas")]
449    MergingFullStateDeltas,
450    #[error("a full state delta must only contain storage create operations")]
451    FullStateDeltaContainsNonCreateOp,
452}
453
454#[derive(Debug, Error)]
455pub enum AccountPatchError {
456    #[error("final nonce can never be set to zero")]
457    FinalNonceIsZero,
458
459    #[error(
460        "state change to an account (store, vault or code) require that the final nonce is incremented"
461    )]
462    StateChangeRequiresNonceUpdate,
463
464    #[error("account code must be provided for new accounts (with nonce = 1)")]
465    CodeMustBeProvidedForNewAccounts,
466
467    #[error("a full state patch must only contain storage create operations")]
468    FullStatePatchContainsNonCreateStorageOp,
469
470    #[error("storage slot {0} was used as different slot types")]
471    StorageSlotUsedAsDifferentTypes(StorageSlotName),
472
473    #[error("storage slot name {0} is assigned to more than one slot patch")]
474    DuplicateStorageSlotName(StorageSlotName),
475
476    #[error("number of storage slot patches is {0} but max possible number is {max}", max = AccountStorage::MAX_NUM_STORAGE_SLOTS)]
477    TooManyStorageSlotPatches(usize),
478
479    #[error(
480        "a full state patch cannot be merged on top of another patch; it must be the merge base"
481    )]
482    MergeIncomingFullStatePatch,
483
484    #[error("failed to merge storage patch for slot {0}: cannot create a slot twice")]
485    StoragePatchMergeDoubleCreate(StorageSlotName),
486
487    #[error(
488        "failed to merge storage patch for slot {0}: cannot create a slot after it was updated, which indicates it already exists"
489    )]
490    StoragePatchMergeCreateAfterUpdate(StorageSlotName),
491
492    #[error("failed to merge storage patch for slot {0}: cannot update slot after it was removed")]
493    StoragePatchMergeUpdateAfterRemove(StorageSlotName),
494
495    #[error("failed to merge storage patch for slot {0}: cannot remove a slot twice")]
496    StoragePatchMergeDoubleRemove(StorageSlotName),
497
498    #[error(
499        "nonce in the patch being merged is {new} which is not exactly one greater than current patch nonce {current}"
500    )]
501    NonceMustIncrementByOne { current: Felt, new: Felt },
502
503    #[error(
504        "patch is for account ID {actual} but is being merged into patch for account {expected}"
505    )]
506    AccountIdMismatch { expected: AccountId, actual: AccountId },
507
508    #[error(
509        "account update of type `{left_update_type}` cannot be merged with account update of type `{right_update_type}`"
510    )]
511    IncompatibleAccountUpdates {
512        left_update_type: &'static str,
513        right_update_type: &'static str,
514    },
515}
516
517// STORAGE MAP ERROR
518// ================================================================================================
519
520#[derive(Debug, Error)]
521pub enum StorageMapError {
522    #[error("map entries contain key {key} twice with values {value0} and {value1}")]
523    DuplicateKey {
524        key: StorageMapKey,
525        value0: Word,
526        value1: Word,
527    },
528    #[error("map key {key} is not present in provided SMT proof")]
529    MissingKey { key: StorageMapKey },
530}
531
532// BATCH ACCOUNT UPDATE ERROR
533// ================================================================================================
534
535#[derive(Debug, Error)]
536pub enum BatchAccountUpdateError {
537    #[error(
538        "account update for account {expected_account_id} cannot be merged with update from transaction {transaction} which was executed against account {actual_account_id}"
539    )]
540    AccountUpdateIdMismatch {
541        transaction: TransactionId,
542        expected_account_id: AccountId,
543        actual_account_id: AccountId,
544    },
545    #[error(
546        "final state commitment in account update from transaction {0} does not match initial state of current update"
547    )]
548    AccountUpdateInitialStateMismatch(TransactionId),
549    #[error("failed to merge account patch from transaction {0}")]
550    TransactionUpdateMergeError(TransactionId, #[source] Box<AccountPatchError>),
551}
552
553// ASSET ERROR
554// ================================================================================================
555
556#[derive(Debug, Error)]
557pub enum AssetError {
558    #[error(
559      "fungible asset amount {0} exceeds the max allowed amount of {max_amount}",
560      max_amount = FungibleAsset::MAX_AMOUNT
561    )]
562    FungibleAssetAmountTooBig(u64),
563    #[error("subtracting {subtrahend} from fungible asset amount {minuend} would underflow")]
564    FungibleAssetAmountNotSufficient { minuend: u64, subtrahend: u64 },
565    #[error(
566        "cannot combine fungible assets with different asset IDs: {original_id} and {other_id}"
567    )]
568    FungibleAssetInconsistentIds { original_id: AssetId, other_id: AssetId },
569    #[error("faucet account ID in asset is invalid")]
570    InvalidFaucetAccountId(#[source] Box<dyn Error + Send + Sync + 'static>),
571    #[error(
572        "asset class prefix and suffix in a non-fungible asset ID must match indices 0 and 1 in the value, but asset class was {asset_class} and value was {value}"
573    )]
574    NonFungibleAssetClassMustMatchValue { asset_class: AssetClass, value: Word },
575    #[error("asset class prefix and suffix in a fungible asset ID must be zero but was {0}")]
576    FungibleAssetClassMustBeZero(AssetClass),
577    #[error(
578        "the three most significant elements in a fungible asset's value must be zero but provided value was {0}"
579    )]
580    FungibleAssetValueMostSignificantElementsMustBeZero(Word),
581    #[error("smt proof in asset witness contains invalid ID or value")]
582    AssetWitnessInvalid(#[source] Box<AssetError>),
583    #[error("asset ID {id} is not present in the provided asset witness SMT proof")]
584    AssetWitnessMissingId { id: AssetId },
585    #[error("unknown asset composition encoding: {0}")]
586    UnknownAssetComposition(u8),
587    #[error("unknown asset delta operation encoding: {0}")]
588    UnknownAssetDeltaOperation(u8),
589    #[error("asset composition {0:?} is not supported at this operational site")]
590    UnsupportedAssetComposition(AssetComposition),
591    #[error(
592        "asset composition mismatch for faucet {faucet_id}: expected {expected:?}, found {actual:?}"
593    )]
594    AssetCompositionMismatch {
595        faucet_id: AccountId,
596        expected: AssetComposition,
597        actual: AssetComposition,
598    },
599    #[error("asset metadata byte 0x{0:02x} has reserved bits set to non-zero values")]
600    ReservedAssetMetadata(u8),
601}
602
603// TOKEN SYMBOL ERROR
604// ================================================================================================
605
606#[derive(Debug, Error)]
607pub enum TokenSymbolError {
608    #[error("token symbol value {0} cannot exceed {max}", max = TokenSymbol::MAX_ENCODED_VALUE)]
609    ValueTooLarge(u64),
610    #[error(
611        "token symbol value {0} cannot be less than {min}",
612        min = TokenSymbol::MIN_ENCODED_VALUE
613    )]
614    ValueTooSmall(u64),
615    #[error("token symbol should have length between 1 and 12 characters, but {0} was provided")]
616    InvalidLength(usize),
617    #[error("token symbol contains a character that is not uppercase ASCII")]
618    InvalidCharacter,
619    #[error("token symbol data left after decoding the specified number of characters")]
620    DataNotFullyDecoded,
621}
622
623impl From<ShortCapitalStringError> for TokenSymbolError {
624    fn from(value: ShortCapitalStringError) -> Self {
625        match value {
626            ShortCapitalStringError::ValueTooLarge(v) => Self::ValueTooLarge(v),
627            ShortCapitalStringError::ValueTooSmall(v) => Self::ValueTooSmall(v),
628            ShortCapitalStringError::InvalidLength(v) => Self::InvalidLength(v),
629            ShortCapitalStringError::InvalidCharacter => Self::InvalidCharacter,
630            ShortCapitalStringError::DataNotFullyDecoded => Self::DataNotFullyDecoded,
631        }
632    }
633}
634
635// ROLE ERROR
636// ================================================================================================
637
638#[derive(Debug, Error)]
639pub enum RoleSymbolError {
640    #[error("role symbol value {0} cannot exceed {max}", max = RoleSymbol::MAX_ENCODED_VALUE)]
641    ValueTooLarge(u64),
642    #[error("role symbol value {0} cannot be less than {min}", min = RoleSymbol::MIN_ENCODED_VALUE)]
643    ValueTooSmall(u64),
644    #[error("role symbol should have length between 1 and 12 characters, but {0} was provided")]
645    InvalidLength(usize),
646    #[error("role symbol contains a character that is not uppercase ASCII or underscore")]
647    InvalidCharacter,
648    #[error("role symbol data left after decoding the specified number of characters")]
649    DataNotFullyDecoded,
650}
651
652impl From<ShortCapitalStringError> for RoleSymbolError {
653    fn from(value: ShortCapitalStringError) -> Self {
654        match value {
655            ShortCapitalStringError::ValueTooLarge(v) => Self::ValueTooLarge(v),
656            ShortCapitalStringError::ValueTooSmall(v) => Self::ValueTooSmall(v),
657            ShortCapitalStringError::InvalidLength(v) => Self::InvalidLength(v),
658            ShortCapitalStringError::InvalidCharacter => Self::InvalidCharacter,
659            ShortCapitalStringError::DataNotFullyDecoded => Self::DataNotFullyDecoded,
660        }
661    }
662}
663
664// SHORT CAPITAL STRING ERROR
665// ================================================================================================
666
667#[derive(Debug, Error)]
668pub(crate) enum ShortCapitalStringError {
669    #[error("short capital string value {0} is too large")]
670    ValueTooLarge(u64),
671    #[error("short capital string value {0} is too small")]
672    ValueTooSmall(u64),
673    #[error(
674        "short capital string should have length between 1 and 12 characters, but {0} was provided"
675    )]
676    InvalidLength(usize),
677    #[error("short capital string contains an invalid character")]
678    InvalidCharacter,
679    #[error("short capital string data left after decoding the specified number of characters")]
680    DataNotFullyDecoded,
681}
682
683// ASSET VAULT ERROR
684// ================================================================================================
685
686#[derive(Debug, Error)]
687pub enum AssetVaultError {
688    #[error("adding fungible asset amounts would exceed maximum allowed amount")]
689    AddFungibleAssetBalanceError(#[source] AssetError),
690    #[error("provided assets contain duplicates")]
691    DuplicateAsset(#[source] MerkleError),
692    #[error("non fungible asset {0} already exists in the vault")]
693    DuplicateNonFungibleAsset(NonFungibleAsset),
694    #[error("fungible asset {0} does not exist in the vault")]
695    FungibleAssetNotFound(FungibleAsset),
696    #[error("non fungible asset {0} does not exist in the vault")]
697    NonFungibleAssetNotFound(NonFungibleAsset),
698    #[error("subtracting fungible asset amounts would underflow")]
699    SubtractFungibleAssetBalanceError(#[source] AssetError),
700    #[error("maximum number of asset vault leaves exceeded")]
701    MaxLeafEntriesExceeded(#[source] MerkleError),
702}
703
704// PARTIAL ASSET VAULT ERROR
705// ================================================================================================
706
707#[derive(Debug, Error)]
708pub enum PartialAssetVaultError {
709    #[error("partial vault contains invalid asset value {value} at ID {id}")]
710    InvalidAssetForId {
711        id: AssetId,
712        value: Word,
713        #[source]
714        source: AssetError,
715    },
716    #[error("failed to add asset proof")]
717    FailedToAddProof(#[source] MerkleError),
718    #[error("asset is not tracked in the partial vault")]
719    UntrackedAsset(#[source] MerkleError),
720}
721
722// NOTE ERROR
723// ================================================================================================
724
725#[derive(Debug, Error)]
726pub enum NoteError {
727    #[error("library does not contain a procedure with @note_script attribute")]
728    NoteScriptNoProcedureWithAttribute,
729    #[error("library contains multiple procedures with @note_script attribute")]
730    NoteScriptMultipleProceduresWithAttribute,
731    #[error("procedure at path '{0}' not found in library")]
732    NoteScriptProcedureNotFound(Box<str>),
733    #[error("procedure at path '{0}' does not have @note_script attribute")]
734    NoteScriptProcedureMissingAttribute(Box<str>),
735    #[error("note tag length {0} exceeds the maximum of {max}", max = NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH)]
736    NoteTagLengthTooLarge(u8),
737    #[error("duplicate fungible asset from issuer {0} in note")]
738    DuplicateFungibleAsset(AccountId),
739    #[error("duplicate non fungible asset {0} in note")]
740    DuplicateNonFungibleAsset(NonFungibleAsset),
741    #[error("note type {0} is inconsistent with note tag {1}")]
742    InconsistentNoteTag(NoteType, u64),
743    #[error("adding fungible asset amounts would exceed maximum allowed amount")]
744    AddFungibleAssetBalanceError(#[source] AssetError),
745    #[error("note sender is not a valid account ID")]
746    NoteSenderInvalidAccountId(#[source] AccountIdError),
747    #[error("note execution hint after block variant cannot contain u32::MAX")]
748    NoteExecutionHintAfterBlockCannotBeU32Max,
749    #[error("invalid note execution hint payload {1} for tag {0}")]
750    InvalidNoteExecutionHintPayload(u8, u32),
751    #[error(
752    "note type {0} does not match any of the valid note types {public} or {private}",
753    public = NoteType::Public,
754    private = NoteType::Private,
755    )]
756    UnknownNoteType(Box<str>),
757    #[error("block note tree index {block_note_tree_index} is out of bounds 0..={highest_index}")]
758    BlockNoteTreeIndexOutOfBounds {
759        block_note_tree_index: u16,
760        highest_index: usize,
761    },
762    #[error("note network execution requires a public note but note is of type {0}")]
763    NetworkExecutionRequiresPublicNote(NoteType),
764    #[error("failed to assemble note script:\n{}", PrintDiagnostic::new(.0))]
765    NoteScriptAssemblyError(Report),
766    #[error("failed to deserialize note script")]
767    NoteScriptDeserializationError(#[source] DeserializationError),
768    #[error("note contains {0} assets which exceeds the maximum of {max}", max = NoteAssets::MAX_NUM_ASSETS)]
769    TooManyAssets(usize),
770    #[error("note contains {0} storage items which exceeds the maximum of {max}", max = MAX_NOTE_STORAGE_ITEMS)]
771    TooManyStorageItems(usize),
772    #[error("invalid note storage length: expected {expected} items, got {actual}")]
773    InvalidNoteStorageLength { expected: usize, actual: usize },
774    #[error("note tag requires a public note but the note is of type {0}")]
775    PublicNoteRequired(NoteType),
776    #[error("note attachment content must have at least one word")]
777    NoteAttachmentContentEmpty,
778    #[error(
779        "note attachment content contains {0} words, but the maximum is {max} words",
780        max = NoteAttachment::MAX_NUM_WORDS
781    )]
782    NoteAttachmentContentTooManyWords(usize),
783    #[error(
784        "note attachments contain a total of {0} words, but the maximum allowed is {max} words",
785        max = NoteAttachments::MAX_NUM_WORDS
786    )]
787    NoteAttachmentsTooManyWords(usize),
788    #[error(
789        "attachment size {0} exceeds maximum {max}",
790        max = NoteAttachment::MAX_NUM_WORDS
791    )]
792    NoteAttachmentHeaderSizeExceeded(u8),
793    #[error("{0} attachments were provided but maximum is {max}", max = NoteAttachments::MAX_COUNT)]
794    TooManyAttachments(usize),
795    #[error("attachment scheme {0} exceeds maximum value of {max}", max = NoteAttachmentScheme::MAX)]
796    NoteAttachmentSchemeExceeded(u32),
797    #[error("attachment scheme value 0 is reserved")]
798    NoteAttachmentSchemeZeroReserved,
799    #[error("{error_msg}")]
800    Other {
801        error_msg: Box<str>,
802        // thiserror will return this when calling Error::source on NoteError.
803        source: Option<Box<dyn Error + Send + Sync + 'static>>,
804    },
805}
806
807impl NoteError {
808    /// Creates a custom error using the [`NoteError::Other`] variant from an error message.
809    pub fn other(message: impl Into<String>) -> Self {
810        let message: String = message.into();
811        Self::Other { error_msg: message.into(), source: None }
812    }
813
814    /// Creates a custom error using the [`NoteError::Other`] variant from an error message and
815    /// a source error.
816    pub fn other_with_source(
817        message: impl Into<String>,
818        source: impl Error + Send + Sync + 'static,
819    ) -> Self {
820        let message: String = message.into();
821        Self::Other {
822            error_msg: message.into(),
823            source: Some(Box::new(source)),
824        }
825    }
826}
827
828// PARTIAL BLOCKCHAIN ERROR
829// ================================================================================================
830
831#[derive(Debug, Error)]
832pub enum PartialBlockchainError {
833    #[error(
834        "block num {block_num} exceeds chain length {chain_length} implied by the partial blockchain"
835    )]
836    BlockNumTooBig {
837        chain_length: usize,
838        block_num: BlockNumber,
839    },
840
841    #[error("duplicate block {block_num} in partial blockchain")]
842    DuplicateBlock { block_num: BlockNumber },
843
844    #[error("partial blockchain does not track authentication paths for block {block_num}")]
845    UntrackedBlock { block_num: BlockNumber },
846
847    #[error(
848        "provided block header with number {block_num} and commitment {block_commitment} is not tracked by partial MMR"
849    )]
850    BlockHeaderCommitmentMismatch {
851        block_num: BlockNumber,
852        block_commitment: Word,
853        source: MmrError,
854    },
855}
856
857impl PartialBlockchainError {
858    pub fn block_num_too_big(chain_length: usize, block_num: BlockNumber) -> Self {
859        Self::BlockNumTooBig { chain_length, block_num }
860    }
861
862    pub fn duplicate_block(block_num: BlockNumber) -> Self {
863        Self::DuplicateBlock { block_num }
864    }
865
866    pub fn untracked_block(block_num: BlockNumber) -> Self {
867        Self::UntrackedBlock { block_num }
868    }
869}
870
871// TRANSACTION SCRIPT ERROR
872// ================================================================================================
873
874#[derive(Debug, Error)]
875pub enum TransactionScriptError {
876    #[error("failed to assemble transaction script:\n{}", PrintDiagnostic::new(.0))]
877    AssemblyError(Report),
878    #[error("failed to convert package to transaction script:\n{}", PrintDiagnostic::new(.0))]
879    PackageNotProgram(Report),
880    #[error("library does not contain a procedure with @transaction_script attribute")]
881    NoProcedureWithAttribute,
882    #[error("library contains multiple procedures with @transaction_script attribute")]
883    MultipleProceduresWithAttribute,
884    #[error("procedure at path '{0}' not found in library")]
885    ProcedureNotFound(Box<str>),
886    #[error("procedure at path '{0}' does not have @transaction_script attribute")]
887    ProcedureMissingAttribute(Box<str>),
888}
889
890// TRANSACTION INPUT ERROR
891// ================================================================================================
892
893#[derive(Debug, Error)]
894pub enum TransactionInputError {
895    #[error("transaction input note with nullifier {0} is a duplicate")]
896    DuplicateInputNote(Nullifier),
897    #[error("partial blockchain has length {actual} which does not match block number {expected}")]
898    InconsistentChainLength {
899        expected: BlockNumber,
900        actual: BlockNumber,
901    },
902    #[error(
903        "partial blockchain has commitment {actual} which does not match the block header's chain commitment {expected}"
904    )]
905    InconsistentChainCommitment { expected: Word, actual: Word },
906    #[error("block in which input note with id {0} was created is not in partial blockchain")]
907    InputNoteBlockNotInPartialBlockchain(NoteId),
908    #[error("input note with id {0} was not created in block {1}")]
909    InputNoteNotInBlock(NoteId, BlockNumber),
910    #[error(
911        "total number of input notes is {0} which exceeds the maximum of {MAX_INPUT_NOTES_PER_TX}"
912    )]
913    TooManyInputNotes(usize),
914}
915
916// TRANSACTION INPUTS EXTRACTION ERROR
917// ===============================================================================================
918
919#[derive(Debug, Error)]
920pub enum TransactionInputsExtractionError {
921    #[error("specified foreign account id matches the transaction input's account id")]
922    AccountNotForeign,
923    #[error("foreign account data not found in advice map for account {0}")]
924    ForeignAccountNotFound(AccountId),
925    #[error("foreign account code not found for account {0}")]
926    ForeignAccountCodeNotFound(AccountId),
927    #[error("storage header data not found in advice map for account {0}")]
928    StorageHeaderNotFound(AccountId),
929    #[error("failed to handle account data")]
930    AccountError(#[from] AccountError),
931    #[error("failed to handle merkle data")]
932    MerkleError(#[from] MerkleError),
933    #[error("failed to handle account tree data")]
934    AccountTreeError(#[from] AccountTreeError),
935    #[error("missing vault root from Merkle store")]
936    MissingVaultRoot,
937    #[error("missing storage map root from Merkle store")]
938    MissingMapRoot,
939    #[error("failed to construct SMT proof")]
940    SmtProofError(#[from] SmtProofError),
941    #[error("failed to construct an asset")]
942    AssetError(#[from] AssetError),
943    #[error("failed to handle storage map data")]
944    StorageMapError(#[from] StorageMapError),
945    #[error("failed to convert elements to leaf index: {0}")]
946    LeafConversionError(String),
947    #[error("failed to construct SMT leaf")]
948    SmtLeafError(#[from] SmtLeafError),
949}
950
951// TRANSACTION OUTPUT ERROR
952// ===============================================================================================
953
954#[derive(Debug, Error)]
955pub enum TransactionOutputError {
956    #[error("transaction output note with id {0} is a duplicate")]
957    DuplicateOutputNote(NoteId),
958    #[error("final account commitment is not in the advice map")]
959    FinalAccountCommitmentMissingInAdviceMap,
960    #[error("failed to parse final account header")]
961    FinalAccountHeaderParseFailure(#[source] AccountError),
962    #[error(
963        "output notes commitment {expected} from kernel does not match computed commitment {actual}"
964    )]
965    OutputNotesCommitmentInconsistent { expected: Word, actual: Word },
966    #[error("transaction kernel output stack is invalid: {0}")]
967    OutputStackInvalid(String),
968    #[error(
969        "total number of output notes is {0} which exceeds the maximum of {MAX_OUTPUT_NOTES_PER_TX}"
970    )]
971    TooManyOutputNotes(usize),
972    #[error("failed to process account update commitment: {0}")]
973    AccountUpdateCommitment(Box<str>),
974}
975
976// OUTPUT NOTE ERROR
977// ================================================================================================
978
979/// Errors that can occur when creating a
980/// [`PublicOutputNote`](crate::transaction::PublicOutputNote) or
981/// [`PrivateOutputNote`](crate::transaction::PrivateOutputNote).
982#[derive(Debug, Error)]
983pub enum OutputNoteError {
984    #[error("note with id {0} is private but expected a public note")]
985    NoteIsPrivate(NoteId),
986    #[error("note with id {0} is public but expected a private note")]
987    NoteIsPublic(NoteId),
988    #[error(
989        "public note with id {note_id} has size {note_size} bytes which exceeds maximum note size of {NOTE_MAX_SIZE}"
990    )]
991    NoteSizeLimitExceeded { note_id: NoteId, note_size: usize },
992}
993
994// TRANSACTION EVENT PARSING ERROR
995// ================================================================================================
996
997#[derive(Debug, Error)]
998pub enum TransactionEventError {
999    #[error("event id {0} is not a valid transaction event")]
1000    InvalidTransactionEvent(EventId),
1001}
1002
1003// TRANSACTION TRACE PARSING ERROR
1004// ================================================================================================
1005
1006#[derive(Debug, Error)]
1007pub enum TransactionTraceParsingError {
1008    #[error("trace id {0} is an unknown transaction kernel trace")]
1009    UnknownTransactionTrace(u32),
1010}
1011
1012// PROVEN TRANSACTION ERROR
1013// ================================================================================================
1014
1015#[derive(Debug, Error)]
1016pub enum ProvenTransactionError {
1017    #[error(
1018        "proven transaction's final account commitment {tx_final_commitment} and account details commitment {details_commitment} must match"
1019    )]
1020    AccountFinalCommitmentMismatch {
1021        tx_final_commitment: Word,
1022        details_commitment: Word,
1023    },
1024    #[error(
1025        "proven transaction's final account ID {tx_account_id} and account details id {details_account_id} must match"
1026    )]
1027    AccountIdMismatch {
1028        tx_account_id: AccountId,
1029        details_account_id: AccountId,
1030    },
1031    #[error("failed to construct input notes for proven transaction")]
1032    InputNotesError(TransactionInputError),
1033    #[error("private account {0} should not have account details")]
1034    PrivateAccountWithDetails(AccountId),
1035    #[error("account {0} with public state is missing its account details")]
1036    PublicStateAccountMissingDetails(AccountId),
1037    #[error("new account {id} with public state must be accompanied by a full state patch")]
1038    NewPublicStateAccountRequiresFullStatePatch { id: AccountId, source: AccountError },
1039    #[error(
1040        "existing account {0} with public state should only provide delta updates instead of full details"
1041    )]
1042    ExistingPublicStateAccountRequiresDeltaDetails(AccountId),
1043    #[error("failed to construct output notes for proven transaction")]
1044    OutputNotesError(#[source] TransactionOutputError),
1045    #[error(
1046        "account update of size {update_size} for account {account_id} exceeds maximum update size of {ACCOUNT_UPDATE_MAX_SIZE}"
1047    )]
1048    AccountUpdateSizeLimitExceeded {
1049        account_id: AccountId,
1050        update_size: usize,
1051    },
1052    #[error("proven transaction neither changed the account state, nor consumed any notes")]
1053    EmptyTransaction,
1054    #[error("failed to validate account patch in transaction account update")]
1055    AccountPatchCommitmentMismatch(#[source] Box<dyn Error + Send + Sync + 'static>),
1056    #[error("note with id {0} is both created and consumed by the transaction")]
1057    NoteCreatedAndConsumed(NoteId),
1058}
1059
1060// PROPOSED BATCH ERROR
1061// ================================================================================================
1062
1063#[derive(Debug, Error)]
1064pub enum ProposedBatchError {
1065    #[error("failed to verify transaction {transaction_id} in transaction batch")]
1066    TransactionVerificationFailed {
1067        transaction_id: TransactionId,
1068        source: TransactionVerifierError,
1069    },
1070
1071    #[error(
1072        "transaction batch has {0} input notes but at most {MAX_INPUT_NOTES_PER_BATCH} are allowed"
1073    )]
1074    TooManyInputNotes(usize),
1075
1076    #[error(
1077        "transaction batch has {0} output notes but at most {MAX_OUTPUT_NOTES_PER_BATCH} are allowed"
1078    )]
1079    TooManyOutputNotes(usize),
1080
1081    #[error(
1082        "transaction batch has {0} account updates but at most {MAX_ACCOUNTS_PER_BATCH} are allowed"
1083    )]
1084    TooManyAccountUpdates(usize),
1085
1086    #[error(
1087        "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}"
1088    )]
1089    ExpiredTransaction {
1090        transaction_id: TransactionId,
1091        transaction_expiration_num: BlockNumber,
1092        reference_block_num: BlockNumber,
1093    },
1094
1095    #[error("transaction batch must contain at least one transaction")]
1096    EmptyTransactionBatch,
1097
1098    #[error("transaction {transaction_id} appears twice in the proposed batch input")]
1099    DuplicateTransaction { transaction_id: TransactionId },
1100
1101    #[error(
1102        "transaction {second_transaction_id} consumes the note with nullifier {note_nullifier} that is also consumed by another transaction {first_transaction_id} in the batch"
1103    )]
1104    DuplicateInputNote {
1105        note_nullifier: Nullifier,
1106        first_transaction_id: TransactionId,
1107        second_transaction_id: TransactionId,
1108    },
1109
1110    #[error(
1111        "transaction {second_transaction_id} creates the note with id {note_id} that is also created by another transaction {first_transaction_id} in the batch"
1112    )]
1113    DuplicateOutputNote {
1114        note_id: NoteId,
1115        first_transaction_id: TransactionId,
1116        second_transaction_id: TransactionId,
1117    },
1118
1119    #[error(
1120        "transaction {consumed_by} that consumes the note with ID {note_id} must be ordered before transaction {created_by} that creates the note"
1121    )]
1122    NoteConsumedBeforeCreated {
1123        note_id: NoteId,
1124        consumed_by: TransactionId,
1125        created_by: TransactionId,
1126    },
1127
1128    #[error("failed to merge transaction patch into account {account_id}")]
1129    AccountUpdateError {
1130        account_id: AccountId,
1131        source: BatchAccountUpdateError,
1132    },
1133
1134    #[error(
1135        "unable to prove unauthenticated note inclusion because block {block_number} in which note with id {note_id} was created is not in partial blockchain"
1136    )]
1137    UnauthenticatedInputNoteBlockNotInPartialBlockchain {
1138        block_number: BlockNumber,
1139        note_id: NoteId,
1140    },
1141
1142    #[error(
1143        "unable to prove unauthenticated note inclusion of note {note_id} in block {block_num}"
1144    )]
1145    UnauthenticatedNoteAuthenticationFailed {
1146        note_id: NoteId,
1147        block_num: BlockNumber,
1148        source: MerkleError,
1149    },
1150
1151    #[error("partial blockchain has length {actual} which does not match block number {expected}")]
1152    InconsistentChainLength {
1153        expected: BlockNumber,
1154        actual: BlockNumber,
1155    },
1156
1157    #[error(
1158        "partial blockchain has root {actual} which does not match block header's root {expected}"
1159    )]
1160    InconsistentChainRoot { expected: Word, actual: Word },
1161
1162    #[error(
1163        "block {block_num} referenced by transaction {transaction_id} is not in the partial blockchain"
1164    )]
1165    MissingTransactionReferenceBlock {
1166        transaction_id: TransactionId,
1167        block_num: BlockNumber,
1168    },
1169
1170    #[error(
1171        "transaction {transaction_id} references block {block_num} with commitment {actual_block_commitment}, but the block in the chain with the same number has commitment {expected_block_commitment}"
1172    )]
1173    TransactionReferenceBlockCommitmentMismatch {
1174        transaction_id: TransactionId,
1175        block_num: BlockNumber,
1176        expected_block_commitment: Word,
1177        actual_block_commitment: Word,
1178    },
1179}
1180
1181// PROVEN BATCH ERROR
1182// ================================================================================================
1183
1184#[derive(Debug, Error)]
1185pub enum ProvenBatchError {
1186    #[error(
1187        "batch expiration block number {batch_expiration_block_num} is not greater than the reference block number {reference_block_num}"
1188    )]
1189    InvalidBatchExpirationBlockNum {
1190        batch_expiration_block_num: BlockNumber,
1191        reference_block_num: BlockNumber,
1192    },
1193    #[error("batch kernel execution failed")]
1194    BatchKernelExecutionFailed(#[source] ExecutionError),
1195    #[error("batch kernel produced an invalid output stack")]
1196    BatchKernelOutputInvalid(#[source] BatchOutputError),
1197}
1198
1199// BATCH OUTPUT ERROR
1200// ================================================================================================
1201
1202#[derive(Debug, Error)]
1203pub enum BatchOutputError {
1204    #[error("batch kernel output stack is invalid: {0}")]
1205    OutputStackInvalid(String),
1206    #[error("batch expiration block number {0} does not fit into a u32")]
1207    ExpirationBlockNumberTooLarge(Felt),
1208}
1209
1210// PROPOSED BLOCK ERROR
1211// ================================================================================================
1212
1213#[derive(Debug, Error)]
1214pub enum ProposedBlockError {
1215    #[error("block must contain at least one transaction batch")]
1216    EmptyBlock,
1217
1218    #[error("block must contain at most {MAX_BATCHES_PER_BLOCK} transaction batches")]
1219    TooManyBatches,
1220
1221    #[error(
1222        "batch {batch_id} expired at block {batch_expiration_block_num} but the current block number is {current_block_num}"
1223    )]
1224    ExpiredBatch {
1225        batch_id: BatchId,
1226        batch_expiration_block_num: BlockNumber,
1227        current_block_num: BlockNumber,
1228    },
1229
1230    #[error("batch {batch_id} appears twice in the block inputs")]
1231    DuplicateBatch { batch_id: BatchId },
1232
1233    #[error(
1234        "batch {second_batch_id} consumes the note with nullifier {note_nullifier} that is also consumed by another batch {first_batch_id} in the block"
1235    )]
1236    DuplicateInputNote {
1237        note_nullifier: Nullifier,
1238        first_batch_id: BatchId,
1239        second_batch_id: BatchId,
1240    },
1241
1242    #[error(
1243        "batch {second_batch_id} creates the note with ID {note_id} that is also created by another batch {first_batch_id} in the block"
1244    )]
1245    DuplicateOutputNote {
1246        note_id: NoteId,
1247        first_batch_id: BatchId,
1248        second_batch_id: BatchId,
1249    },
1250
1251    #[error(
1252        "batch {consumed_by} that consumes the note with ID {note_id} must be ordered before batch {created_by} that creates the note"
1253    )]
1254    NoteConsumedBeforeCreated {
1255        note_id: NoteId,
1256        consumed_by: BatchId,
1257        created_by: BatchId,
1258    },
1259
1260    #[error(
1261        "timestamp {provided_timestamp} does not increase monotonically compared to timestamp {previous_timestamp} from the previous block header"
1262    )]
1263    TimestampDoesNotIncreaseMonotonically {
1264        provided_timestamp: u32,
1265        previous_timestamp: u32,
1266    },
1267
1268    #[error(
1269        "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}"
1270    )]
1271    ConflictingBatchesUpdateSameAccount {
1272        account_id: AccountId,
1273        initial_state_commitment: Word,
1274        first_batch_id: BatchId,
1275        second_batch_id: BatchId,
1276    },
1277
1278    #[error(
1279        "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"
1280    )]
1281    ChainLengthNotEqualToPreviousBlockNumber {
1282        chain_length: BlockNumber,
1283        prev_block_num: BlockNumber,
1284    },
1285
1286    #[error(
1287        "partial blockchain has commitment {chain_commitment} which does not match the chain commitment {prev_block_chain_commitment} of the previous block {prev_block_num}"
1288    )]
1289    ChainRootNotEqualToPreviousBlockChainCommitment {
1290        chain_commitment: Word,
1291        prev_block_chain_commitment: Word,
1292        prev_block_num: BlockNumber,
1293    },
1294
1295    #[error(
1296        "partial blockchain is missing block {reference_block_num} referenced by batch {batch_id} in the block"
1297    )]
1298    BatchReferenceBlockMissingFromChain {
1299        reference_block_num: BlockNumber,
1300        batch_id: BatchId,
1301    },
1302
1303    #[error(
1304        "failed to prove unauthenticated note inclusion because block {block_number} in which note with id {note_id} was created is not in partial blockchain"
1305    )]
1306    UnauthenticatedInputNoteBlockNotInPartialBlockchain {
1307        block_number: BlockNumber,
1308        note_id: NoteId,
1309    },
1310
1311    #[error(
1312        "failed to prove unauthenticated note inclusion of note {note_id} in block {block_num}"
1313    )]
1314    UnauthenticatedNoteAuthenticationFailed {
1315        note_id: NoteId,
1316        block_num: BlockNumber,
1317        source: MerkleError,
1318    },
1319
1320    #[error(
1321        "unauthenticated note with nullifier {nullifier} was not created in the same block and no inclusion proof to authenticate it was provided"
1322    )]
1323    UnauthenticatedNoteConsumed { nullifier: Nullifier },
1324
1325    #[error("block inputs do not contain a proof of inclusion for account {0}")]
1326    MissingAccountWitness(AccountId),
1327
1328    #[error(
1329        "account {account_id} with state {state_commitment} cannot transition to any of the remaining states {}",
1330        remaining_state_commitments.iter().map(Word::to_hex).collect::<Vec<_>>().join(", ")
1331    )]
1332    InconsistentAccountStateTransition {
1333        account_id: AccountId,
1334        state_commitment: Word,
1335        remaining_state_commitments: Vec<Word>,
1336    },
1337
1338    #[error("no proof for nullifier {0} was provided")]
1339    NullifierProofMissing(Nullifier),
1340
1341    #[error("note with nullifier {0} is already spent")]
1342    NullifierSpent(Nullifier),
1343
1344    #[error("failed to merge transaction patch into account {account_id}")]
1345    AccountUpdateError {
1346        account_id: AccountId,
1347        source: Box<AccountPatchError>,
1348    },
1349
1350    #[error("failed to track account witness")]
1351    AccountWitnessTracking { source: AccountTreeError },
1352
1353    #[error(
1354        "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"
1355    )]
1356    StaleAccountTreeRoot {
1357        prev_block_account_root: Word,
1358        stale_account_root: Word,
1359    },
1360
1361    #[error("account ID prefix already exists in the tree")]
1362    AccountIdPrefixDuplicate { source: AccountTreeError },
1363
1364    #[error(
1365        "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"
1366    )]
1367    StaleNullifierTreeRoot {
1368        prev_block_nullifier_root: Word,
1369        stale_nullifier_root: Word,
1370    },
1371
1372    #[error("nullifier witness has a different root than the current nullifier tree root")]
1373    NullifierWitnessRootMismatch(NullifierTreeError),
1374}
1375
1376// NULLIFIER TREE ERROR
1377// ================================================================================================
1378
1379#[derive(Debug, Error)]
1380pub enum NullifierTreeError {
1381    #[error(
1382        "entries passed to nullifier tree contain multiple block numbers for the same nullifier"
1383    )]
1384    DuplicateNullifierBlockNumbers(#[source] MerkleError),
1385
1386    #[error("attempt to mark nullifier {0} as spent but it is already spent")]
1387    NullifierAlreadySpent(Nullifier),
1388
1389    #[error("maximum number of nullifier tree leaves exceeded")]
1390    MaxLeafEntriesExceeded(#[source] MerkleError),
1391
1392    #[error("nullifier {nullifier} is not tracked by the partial nullifier tree")]
1393    UntrackedNullifier {
1394        nullifier: Nullifier,
1395        source: MerkleError,
1396    },
1397
1398    #[error("new tree root after nullifier witness insertion does not match previous tree root")]
1399    TreeRootConflict(#[source] MerkleError),
1400
1401    #[error("failed to compute nullifier tree mutations")]
1402    ComputeMutations(#[source] MerkleError),
1403
1404    #[error("invalid nullifier block number")]
1405    InvalidNullifierBlockNumber(Word),
1406}
1407
1408// AUTH SCHEME ERROR
1409// ================================================================================================
1410
1411#[derive(Debug, Error)]
1412pub enum AuthSchemeError {
1413    #[error("auth scheme identifier `{0}` is not valid")]
1414    InvalidAuthSchemeIdentifier(String),
1415}
1416
1417// TRANSACTION VERIFIER ERROR
1418// ================================================================================================
1419
1420#[derive(Debug, Error)]
1421pub enum TransactionVerifierError {
1422    #[error("failed to verify transaction")]
1423    TransactionVerificationFailed(#[source] VerificationError),
1424    #[error("transaction proof security level is {actual} but must be at least {expected_minimum}")]
1425    InsufficientProofSecurityLevel { actual: u32, expected_minimum: u32 },
1426}