Skip to main content

miden_protocol/errors/
mod.rs

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