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