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