miden_objects/
errors.rs

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