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