Skip to main content

miden_protocol/errors/
mod.rs

1use alloc::boxed::Box;
2use alloc::string::String;
3use alloc::vec::Vec;
4use core::error::Error;
5
6use miden_assembly::Report;
7use miden_assembly::diagnostics::reporting::PrintDiagnostic;
8use miden_core::mast::MastForestError;
9use miden_crypto::merkle::mmr::MmrError;
10use miden_crypto::merkle::smt::{SmtLeafError, SmtProofError};
11use miden_crypto::utils::HexParseError;
12use miden_processor::ExecutionError;
13use miden_verifier::VerificationError;
14use thiserror::Error;
15
16use super::account::{AccountId, RoleSymbol};
17use super::asset::{Asset, AssetComposition, AssetId, FungibleAsset, TokenSymbol};
18use super::crypto::merkle::MerkleError;
19use super::note::NoteId;
20use super::{
21    MAX_ACCOUNTS_PER_BLOCK,
22    MAX_BATCHES_PER_BLOCK,
23    MAX_INPUT_NOTES_PER_BLOCK,
24    MAX_OUTPUT_NOTES_PER_BATCH,
25    Word,
26};
27use crate::account::component::{SchemaTypeError, StorageValueName, StorageValueNameError};
28use crate::account::delta::AssetDeltaOperation;
29use crate::account::{
30    AccountCode,
31    AccountHeader,
32    AccountIdPrefix,
33    AccountProcedureRoot,
34    AccountStorage,
35    AccountVaultDelta,
36    StorageMapKey,
37    StorageSlotId,
38    StorageSlotName,
39};
40use crate::address::AddressType;
41use crate::asset::AssetClass;
42use crate::batch::BatchId;
43use crate::block::{BlockNumber, ValidatorConfig};
44use crate::note::{
45    NoteAssets,
46    NoteAttachment,
47    NoteAttachmentScheme,
48    NoteAttachments,
49    NoteTag,
50    NoteType,
51    Nullifier,
52};
53use crate::protocol_config::KernelConfig;
54use crate::script::MastForestScriptError;
55use crate::transaction::TransactionId;
56use crate::utils::serde::DeserializationError;
57use crate::vm::EventId;
58use crate::{
59    ACCOUNT_UPDATE_MAX_SIZE,
60    Felt,
61    MAX_ACCOUNTS_PER_BATCH,
62    MAX_INPUT_NOTES_PER_BATCH,
63    MAX_INPUT_NOTES_PER_TX,
64    MAX_NOTE_STORAGE_ITEMS,
65    MAX_OUTPUT_NOTES_PER_TX,
66    NOTE_MAX_SIZE,
67};
68
69#[cfg(any(feature = "testing", test))]
70mod masm_error;
71#[cfg(any(feature = "testing", test))]
72pub use masm_error::MasmError;
73
74/// The errors from the MASM code of the transaction kernel.
75#[cfg(any(feature = "testing", test))]
76pub mod tx_kernel {
77    include!(concat!(env!("OUT_DIR"), "/tx_kernel_errors.rs"));
78}
79
80/// The errors from the MASM code of the Miden protocol library.
81#[cfg(any(feature = "testing", test))]
82pub mod protocol {
83    include!(concat!(env!("OUT_DIR"), "/protocol_errors.rs"));
84}
85
86// ACCOUNT COMPONENT TEMPLATE ERROR
87// ================================================================================================
88
89#[derive(Debug, Error)]
90pub enum ComponentMetadataError {
91    #[error("storage slot name `{0}` is duplicate")]
92    DuplicateSlotName(StorageSlotName),
93    #[error("storage init value name `{0}` is duplicate")]
94    DuplicateInitValueName(StorageValueName),
95    #[error("storage value name is incorrect: {0}")]
96    IncorrectStorageValueName(#[source] StorageValueNameError),
97    #[error("invalid storage schema: {0}")]
98    InvalidSchema(String),
99    #[error("type `{0}` is not valid for `{1}` slots")]
100    InvalidType(String, String),
101    #[error("error deserializing component metadata: {0}")]
102    MetadataDeserializationError(String),
103    #[error("init storage value `{0}` was not provided")]
104    InitValueNotProvided(StorageValueName),
105    #[error("invalid init storage value for `{0}`: {1}")]
106    InvalidInitStorageValue(StorageValueName, String),
107    #[error("error converting value into expected type: {0}")]
108    StorageValueParsingError(#[source] SchemaTypeError),
109    #[error("storage map contains duplicate keys")]
110    StorageMapHasDuplicateKeys(#[source] Box<dyn Error + Send + Sync + 'static>),
111    #[cfg(feature = "std")]
112    #[error("error trying to deserialize from toml")]
113    TomlDeserializationError(#[source] toml::de::Error),
114    #[cfg(feature = "std")]
115    #[error("error trying to deserialize from toml")]
116    TomlSerializationError(#[source] toml::ser::Error),
117}
118
119// ACCOUNT ERROR
120// ================================================================================================
121
122#[derive(Debug, Error)]
123pub enum AccountError {
124    #[error("account code does not contain an auth component")]
125    AccountCodeNoAuthComponent,
126    #[error("account code contains multiple auth components")]
127    AccountCodeMultipleAuthComponents,
128    #[error("account code must contain at least one non-auth procedure")]
129    AccountCodeNoProcedures,
130    #[error("account procedure {0} is not contained in the provided mast forest")]
131    AccountCodeProcedureNotInMastForest(AccountProcedureRoot),
132    #[error("account code contains {0} procedures but it may contain at most {max} procedures", max = AccountCode::MAX_NUM_PROCEDURES)]
133    AccountCodeTooManyProcedures(usize),
134    #[error("account code contains a duplicate procedure with root {0}")]
135    AccountCodeDuplicateProcedureRoot(AccountProcedureRoot),
136    #[error(
137        "account code procedures following the authentication procedure are not sorted in ascending order"
138    )]
139    AccountCodeProceduresUnsorted,
140    #[error("failed to assemble account component:\n{}", PrintDiagnostic::new(.0))]
141    AccountComponentAssemblyError(Report),
142    #[error("failed to merge components into one account code mast forest")]
143    AccountComponentMastForestMergeError(#[source] MastForestError),
144    #[error("account component contains multiple authentication procedures")]
145    AccountComponentMultipleAuthProcedures,
146    #[error(
147        "storage of account {0} contains an asset callback slot but its asset callback flag is disabled, so the callback would never be invoked"
148    )]
149    AssetCallbackSlotWithDisabledFlag(AccountId),
150    #[error("failed to update asset vault")]
151    AssetVaultUpdateError(#[source] AssetVaultError),
152    #[error("account build error: {0}")]
153    BuildError(String, #[source] Option<Box<AccountError>>),
154    #[error("failed to parse account ID from final account header")]
155    FinalAccountHeaderIdParsingFailed(#[source] AccountIdError),
156    #[error("account header data has length {actual} but it must be of length {expected}",
157        expected = AccountHeader::NUM_ELEMENTS
158    )]
159    UnexpectedHeaderLength { actual: usize },
160    #[error("account has an unsupported version {0}")]
161    UnsupportedAccountVersion(u64),
162    #[error("final nonce {new} is not strictly greater than current account nonce {current}")]
163    NonceMustIncrease { current: Felt, new: Felt },
164    #[error(
165        "digest of the seed has {actual} trailing zeroes but must have at least {expected} trailing zeroes"
166    )]
167    SeedDigestTooFewTrailingZeros { expected: u32, actual: u32 },
168    #[error("account ID {actual} computed from seed does not match ID {expected} on account")]
169    AccountIdSeedMismatch { actual: AccountId, expected: AccountId },
170    #[error("account ID seed was provided for an existing account")]
171    ExistingAccountWithSeed,
172    #[error("account ID seed was not provided for a new account")]
173    NewAccountMissingSeed,
174    #[error(
175        "an account with a seed cannot be converted into a delta since it represents an unregistered account"
176    )]
177    DeltaFromAccountWithSeed,
178    #[error(
179        "an account with a seed cannot be converted into a patch since it represents an unregistered account"
180    )]
181    PatchFromAccountWithSeed,
182    #[error("seed converts to an invalid account ID")]
183    SeedConvertsToInvalidAccountId(#[source] AccountIdError),
184    #[error("storage map root {0} not found in the account storage")]
185    StorageMapRootNotFound(Word),
186    #[error("storage slot {0} is not of type map")]
187    StorageSlotNotMap(StorageSlotName),
188    #[error("storage slot {0} is not of type value")]
189    StorageSlotNotValue(StorageSlotName),
190    #[error("storage slot name {0} is assigned to more than one slot")]
191    DuplicateStorageSlotName(StorageSlotName),
192    #[error("storage does not contain a slot with name {slot_name}")]
193    StorageSlotNameNotFound { slot_name: StorageSlotName },
194    #[error("storage does not contain a slot with ID {slot_id}")]
195    StorageSlotIdNotFound { slot_id: StorageSlotId },
196    #[error("storage slots must be sorted by slot ID")]
197    UnsortedStorageSlots,
198    #[error("reserved element of a storage slot must be zero but was {0}")]
199    StorageSlotReservedElementNotZero(Felt),
200    #[error("number of storage slots is {0} but max possible number is {max}", max = AccountStorage::MAX_NUM_STORAGE_SLOTS)]
201    StorageTooManySlots(u64),
202    #[error(
203        "failed to apply full state patch to existing account; full state patches can be converted to accounts directly"
204    )]
205    ApplyFullStatePatchToAccount,
206    #[error("patch is for account ID {patch_id} but is being applied to account {account_id}")]
207    PatchAccountIdMismatch {
208        account_id: AccountId,
209        patch_id: AccountId,
210    },
211    #[error("only account deltas representing a full account can be converted to a full account")]
212    PartialStateDeltaToAccount,
213    #[error("assets cannot be removed from a new account with an empty asset vault")]
214    AssetsRemovedFromNewAccount,
215    #[error("only account patches representing a full account can be converted to a full account")]
216    PartialStatePatchToAccount,
217    #[error("maximum number of storage map leaves exceeded")]
218    MaxNumStorageMapLeavesExceeded(#[source] MerkleError),
219    #[error("unknown storage patch operation tag {0}")]
220    UnknownStoragePatchOperation(u8),
221    /// This variant can be used by methods that are not inherent to the account but want to return
222    /// this error type.
223    #[error("{error_msg}")]
224    Other {
225        error_msg: Box<str>,
226        // thiserror will return this when calling Error::source on AccountError.
227        source: Option<Box<dyn Error + Send + Sync + 'static>>,
228    },
229}
230
231impl AccountError {
232    /// Creates a custom error using the [`AccountError::Other`] variant from an error message.
233    pub fn other(message: impl Into<String>) -> Self {
234        let message: String = message.into();
235        Self::Other { error_msg: message.into(), source: None }
236    }
237
238    /// Creates a custom error using the [`AccountError::Other`] variant from an error message and
239    /// a source error.
240    pub fn other_with_source(
241        message: impl Into<String>,
242        source: impl Error + Send + Sync + 'static,
243    ) -> Self {
244        let message: String = message.into();
245        Self::Other {
246            error_msg: message.into(),
247            source: Some(Box::new(source)),
248        }
249    }
250}
251
252/// Error returned when account update details are incompatible with an account ID.
253#[derive(Debug)]
254pub(crate) enum AccountUpdateDetailsValidationError {
255    PrivateAccountWithDetails(AccountId),
256    PublicStateAccountMissingDetails(AccountId),
257    AccountIdMismatch {
258        account_id: AccountId,
259        patch_account_id: AccountId,
260    },
261}
262
263/// Error returned when serialized account update details exceed the size limit.
264#[derive(Debug)]
265pub(crate) struct AccountUpdateSizeValidationError {
266    pub(crate) account_id: AccountId,
267    pub(crate) update_size: usize,
268}
269
270/// Error returned when a new public account cannot be reconstructed from its update details.
271#[derive(Debug)]
272pub(crate) enum NewPublicAccountValidationError {
273    RequiresFullStatePatch {
274        id: AccountId,
275        source: AccountError,
276    },
277    FinalCommitmentMismatch {
278        final_state_commitment: Word,
279        account_commitment: Word,
280    },
281}
282
283// ACCOUNT ID ERROR
284// ================================================================================================
285
286#[derive(Debug, Error)]
287pub enum AccountIdError {
288    #[error("failed to convert bytes into account ID prefix field element")]
289    AccountIdInvalidPrefixFieldElement(#[source] DeserializationError),
290    #[error("failed to convert bytes into account ID suffix field element")]
291    AccountIdInvalidSuffixFieldElement(#[source] DeserializationError),
292    #[error("`{0}` is not a known account type")]
293    UnknownAccountType(Box<str>),
294    #[error("failed to parse hex string into account ID")]
295    AccountIdHexParseError(#[source] HexParseError),
296    #[error("`{0}` is not a known account ID version")]
297    UnknownAccountIdVersion(u8),
298    #[error("most significant bit of account ID suffix must be zero")]
299    AccountIdSuffixMostSignificantBitMustBeZero,
300    #[error("least significant byte of account ID suffix must be zero")]
301    AccountIdSuffixLeastSignificantByteMustBeZero,
302    #[error("failed to decode bech32 string into account ID")]
303    Bech32DecodeError(#[source] Bech32Error),
304}
305
306// SLOT NAME ERROR
307// ================================================================================================
308
309#[derive(Debug, Error)]
310pub enum StorageSlotNameError {
311    #[error("slot name must only contain characters a..z, A..Z, 0..9, double colon or underscore")]
312    InvalidCharacter,
313    #[error("slot names must be separated by double colons")]
314    UnexpectedColon,
315    #[error("slot name components must not start with an underscore")]
316    UnexpectedUnderscore,
317    #[error(
318        "slot names must contain at least {} components separated by double colons",
319        StorageSlotName::MIN_NUM_COMPONENTS
320    )]
321    TooShort,
322    #[error("slot names must contain at most {} characters", StorageSlotName::MAX_LENGTH)]
323    TooLong,
324}
325
326// ACCOUNT CODE INTERFACE ERROR
327// ================================================================================================
328
329#[derive(Debug, Error)]
330pub enum AccountCodeInterfaceError {
331    #[error(
332        "account code interface must contain at least {} procedures, but only {actual} were given",
333        AccountCode::MIN_NUM_PROCEDURES
334    )]
335    TooFewProcedures { actual: usize },
336    #[error(
337        "account code interface contains {actual} procedures but it may contain at most {} procedures",
338        AccountCode::MAX_NUM_PROCEDURES
339    )]
340    TooManyProcedures { actual: usize },
341}
342
343// ACCOUNT COMPONENT NAME ERROR
344// ================================================================================================
345
346#[derive(Debug, Error)]
347pub enum AccountComponentNameError {
348    #[error(
349        "account component name must only contain characters a..z, A..Z, 0..9, double colon or underscore"
350    )]
351    InvalidCharacter,
352    #[error("account component names must be separated by double colons")]
353    UnexpectedColon,
354    #[error("account component name components must not start with an underscore")]
355    UnexpectedUnderscore,
356    #[error(
357        "account component names must contain at least {} components separated by double colons",
358        StorageSlotName::MIN_NUM_COMPONENTS
359    )]
360    TooShort,
361    #[error(
362        "account component names must contain at most {} characters",
363        StorageSlotName::MAX_LENGTH
364    )]
365    TooLong,
366}
367
368// ACCOUNT TREE ERROR
369// ================================================================================================
370
371#[derive(Debug, Error)]
372pub enum AccountTreeError {
373    #[error(
374        "account tree contains multiple account IDs that share the same prefix {duplicate_prefix}"
375    )]
376    DuplicateIdPrefix { duplicate_prefix: AccountIdPrefix },
377    #[error(
378        "entries passed to account tree contain multiple state commitments for the same account ID prefix {prefix}"
379    )]
380    DuplicateStateCommitments { prefix: AccountIdPrefix },
381    #[error("untracked account ID {id} used in partial account tree")]
382    UntrackedAccountId { id: AccountId, source: MerkleError },
383    #[error("new tree root after account witness insertion does not match previous tree root")]
384    TreeRootConflict(#[source] MerkleError),
385    #[error("failed to apply mutations to account tree")]
386    ApplyMutations(#[source] MerkleError),
387    #[error("failed to compute account tree mutations")]
388    ComputeMutations(#[source] MerkleError),
389    #[error("provided smt contains an invalid account ID in key {key}")]
390    InvalidAccountIdKey { key: Word, source: AccountIdError },
391    #[error("smt leaf's index is not a valid account ID prefix")]
392    InvalidAccountIdPrefix(#[source] AccountIdError),
393    #[error("account witness merkle path depth {0} does not match AccountTree::DEPTH")]
394    WitnessMerklePathDepthDoesNotMatchAccountTreeDepth(usize),
395}
396
397// ADDRESS ERROR
398// ================================================================================================
399
400#[derive(Debug, Error)]
401pub enum AddressError {
402    #[error("tag length {0} is too large, must be less than or equal to {max}",
403        max = NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH
404    )]
405    TagLengthTooLarge(u8),
406    #[error("unknown address interface `{0}`")]
407    UnknownAddressInterface(u16),
408    #[error("failed to decode account ID")]
409    AccountIdDecodeError(#[source] AccountIdError),
410    #[error("address separator must not be included without routing parameters")]
411    TrailingSeparator,
412    #[error("failed to decode bech32 string into an address")]
413    Bech32DecodeError(#[source] Bech32Error),
414    #[error("{error_msg}")]
415    DecodeError {
416        error_msg: Box<str>,
417        // thiserror will return this when calling Error::source on AddressError.
418        source: Option<Box<dyn Error + Send + Sync + 'static>>,
419    },
420    #[error("found unknown routing parameter key {0}")]
421    UnknownRoutingParameterKey(u8),
422}
423
424impl AddressError {
425    /// Creates an [`AddressError::DecodeError`] variant from an error message.
426    pub fn decode_error(message: impl Into<String>) -> Self {
427        let message: String = message.into();
428        Self::DecodeError { error_msg: message.into(), source: None }
429    }
430
431    /// Creates an [`AddressError::DecodeError`] variant from an error message and
432    /// a source error.
433    pub fn decode_error_with_source(
434        message: impl Into<String>,
435        source: impl Error + Send + Sync + 'static,
436    ) -> Self {
437        let message: String = message.into();
438        Self::DecodeError {
439            error_msg: message.into(),
440            source: Some(Box::new(source)),
441        }
442    }
443}
444
445// BECH32 ERROR
446// ================================================================================================
447
448#[derive(Debug, Error)]
449pub enum Bech32Error {
450    #[error(transparent)]
451    DecodeError(Box<dyn Error + Send + Sync + 'static>),
452    #[error("found unknown address type {0} which is not the expected {account_addr} account ID address type",
453      account_addr = AddressType::AccountId as u8
454    )]
455    UnknownAddressType(u8),
456    #[error("expected bech32 data to be of length {expected} but it was of length {actual}")]
457    InvalidDataLength { expected: usize, actual: usize },
458}
459
460// NETWORK ID ERROR
461// ================================================================================================
462
463#[derive(Debug, Error)]
464pub enum NetworkIdError {
465    #[error("failed to parse string into a network ID")]
466    NetworkIdParseError(#[source] Box<dyn Error + Send + Sync + 'static>),
467}
468
469// ACCOUNT DELTA ERROR
470// ================================================================================================
471
472#[derive(Debug, Error)]
473pub enum AccountDeltaError {
474    #[error("storage slot {0} was used as different slot types")]
475    StorageSlotUsedAsDifferentTypes(StorageSlotName),
476    #[error("asset {0} is changed by more than one asset delta")]
477    DuplicateAssetDelta(AssetId),
478    #[error(
479        "number of {delta_op} operations in account vault delta is {num_ops} but max is {max}",
480        max = AccountVaultDelta::MAX_ASSETS_PER_DELTA_OP
481    )]
482    TooManyVaultAssetDeltas {
483        delta_op: AssetDeltaOperation,
484        num_ops: usize,
485    },
486    #[error(
487        "account update of type `{left_update_type}` cannot be merged with account update of type `{right_update_type}`"
488    )]
489    IncompatibleAccountUpdates {
490        left_update_type: &'static str,
491        right_update_type: &'static str,
492    },
493    #[error("account delta could not be applied to account {account_id}")]
494    AccountDeltaApplicationFailed {
495        account_id: AccountId,
496        source: AccountError,
497    },
498    #[error("non-empty account storage or vault delta with zero nonce delta is not allowed")]
499    NonEmptyStorageOrVaultDeltaWithZeroNonceDelta,
500    #[error("cannot merge two full state deltas")]
501    MergingFullStateDeltas,
502    #[error("a full state delta must only contain storage create operations")]
503    FullStateDeltaContainsNonCreateOp,
504}
505
506#[derive(Debug, Error)]
507pub enum AccountPatchError {
508    #[error("final nonce can never be set to zero")]
509    FinalNonceIsZero,
510
511    #[error(
512        "state change to an account (store, vault or code) require that the final nonce is incremented"
513    )]
514    StateChangeRequiresNonceUpdate,
515
516    #[error("account code must be provided for new accounts (with nonce = 1)")]
517    CodeMustBeProvidedForNewAccounts,
518
519    #[error("a full state patch must only contain storage create operations")]
520    FullStatePatchContainsNonCreateStorageOp,
521
522    #[error("storage slot {0} was used as different slot types")]
523    StorageSlotUsedAsDifferentTypes(StorageSlotName),
524
525    #[error("storage slot name {0} is assigned to more than one slot patch")]
526    DuplicateStorageSlotName(StorageSlotName),
527
528    #[error("number of storage slot patches is {0} but max possible number is {max}", max = AccountStorage::MAX_NUM_STORAGE_SLOTS)]
529    TooManyStorageSlotPatches(usize),
530
531    #[error(
532        "a full state patch cannot be merged on top of another patch; it must be the merge base"
533    )]
534    MergeIncomingFullStatePatch,
535
536    #[error("failed to merge storage patch for slot {0}: cannot create a slot twice")]
537    StoragePatchMergeDoubleCreate(StorageSlotName),
538
539    #[error(
540        "failed to merge storage patch for slot {0}: cannot create a slot after it was updated, which indicates it already exists"
541    )]
542    StoragePatchMergeCreateAfterUpdate(StorageSlotName),
543
544    #[error("failed to merge storage patch for slot {0}: cannot update slot after it was removed")]
545    StoragePatchMergeUpdateAfterRemove(StorageSlotName),
546
547    #[error("failed to merge storage patch for slot {0}: cannot remove a slot twice")]
548    StoragePatchMergeDoubleRemove(StorageSlotName),
549
550    #[error(
551        "nonce in the patch being merged is {new} which is not exactly one greater than current patch nonce {current}"
552    )]
553    NonceMustIncrementByOne { current: Felt, new: Felt },
554
555    #[error(
556        "patch is for account ID {actual} but is being merged into patch for account {expected}"
557    )]
558    AccountIdMismatch { expected: AccountId, actual: AccountId },
559
560    #[error(
561        "account update of type `{left_update_type}` cannot be merged with account update of type `{right_update_type}`"
562    )]
563    IncompatibleAccountUpdates {
564        left_update_type: &'static str,
565        right_update_type: &'static str,
566    },
567}
568
569// STORAGE MAP ERROR
570// ================================================================================================
571
572#[derive(Debug, Error)]
573pub enum StorageMapError {
574    #[error("map entries contain key {key} twice with values {value0} and {value1}")]
575    DuplicateKey {
576        key: StorageMapKey,
577        value0: Word,
578        value1: Word,
579    },
580    #[error("map key {key} is not present in provided SMT proof")]
581    MissingKey { key: StorageMapKey },
582    #[error("maximum number of storage map leaf entries exceeded")]
583    MaxLeafEntriesExceeded(#[source] MerkleError),
584}
585
586// BATCH ACCOUNT UPDATE ERROR
587// ================================================================================================
588
589#[derive(Debug, Error)]
590pub enum BatchAccountUpdateError {
591    #[error(
592        "account update of size {update_size} for account {account_id} exceeds maximum update size of {ACCOUNT_UPDATE_MAX_SIZE}"
593    )]
594    AccountUpdateSizeLimitExceeded {
595        account_id: AccountId,
596        update_size: usize,
597    },
598    #[error("private account {0} should not have account details")]
599    PrivateAccountWithDetails(AccountId),
600    #[error("account {0} with public state is missing its account details")]
601    PublicStateAccountMissingDetails(AccountId),
602    #[error(
603        "batch account update's account ID {account_id} and account patch ID {patch_account_id} must match"
604    )]
605    AccountIdMismatch {
606        account_id: AccountId,
607        patch_account_id: AccountId,
608    },
609    #[error("new account {id} with public state must be accompanied by a full state patch")]
610    NewPublicStateAccountRequiresFullStatePatch { id: AccountId, source: AccountError },
611    #[error(
612        "batch account update's final commitment {final_state_commitment} and reconstructed account commitment {account_commitment} must match"
613    )]
614    AccountFinalCommitmentMismatch {
615        final_state_commitment: Word,
616        account_commitment: Word,
617    },
618    #[error(
619        "account update for account {expected_account_id} cannot be merged with update from transaction {transaction} which was executed against account {actual_account_id}"
620    )]
621    AccountUpdateIdMismatch {
622        transaction: TransactionId,
623        expected_account_id: AccountId,
624        actual_account_id: AccountId,
625    },
626    #[error(
627        "final state commitment in account update from transaction {0} does not match initial state of current update"
628    )]
629    AccountUpdateInitialStateMismatch(TransactionId),
630    #[error("failed to merge account patch from transaction {0}")]
631    TransactionUpdateMergeError(TransactionId, #[source] Box<AccountPatchError>),
632}
633
634// BLOCK ACCOUNT UPDATE ERROR
635// ================================================================================================
636
637#[derive(Debug, Error)]
638pub enum BlockAccountUpdateError {
639    #[error("private account {0} should not have account details")]
640    PrivateAccountWithDetails(AccountId),
641    #[error("account {0} with public state is missing its account details")]
642    PublicStateAccountMissingDetails(AccountId),
643    #[error(
644        "block account update's account ID {account_id} and account patch ID {patch_account_id} must match"
645    )]
646    AccountIdMismatch {
647        account_id: AccountId,
648        patch_account_id: AccountId,
649    },
650    #[error("new account {id} with public state must be accompanied by a full state patch")]
651    NewPublicStateAccountRequiresFullStatePatch { id: AccountId, source: AccountError },
652    #[error(
653        "block account update's final commitment {final_state_commitment} and reconstructed account commitment {account_commitment} must match"
654    )]
655    AccountFinalCommitmentMismatch {
656        final_state_commitment: Word,
657        account_commitment: Word,
658    },
659}
660
661// BLOCK BODY ERROR
662// ================================================================================================
663
664#[derive(Debug, Error)]
665pub enum BlockBodyError {
666    #[error("block has {0} account updates but at most {MAX_ACCOUNTS_PER_BLOCK} are allowed")]
667    TooManyAccountUpdates(usize),
668    #[error("block has {0} nullifiers but at most {MAX_INPUT_NOTES_PER_BLOCK} are allowed")]
669    TooManyNullifiers(usize),
670    #[error("block has {0} output note batches but at most {MAX_BATCHES_PER_BLOCK} are allowed")]
671    TooManyOutputNoteBatches(usize),
672    #[error(
673        "output note batch {batch_index} has {note_count} notes but at most {MAX_OUTPUT_NOTES_PER_BATCH} are allowed"
674    )]
675    TooManyOutputNotes { batch_index: usize, note_count: usize },
676    #[error("output note batch {batch_index} contains invalid note index {note_index}")]
677    InvalidOutputNoteIndex { batch_index: usize, note_index: usize },
678    #[error("output note batch {batch_index} contains note index {note_index} twice")]
679    DuplicateOutputNoteIndex { batch_index: usize, note_index: usize },
680    #[error("output note {0} appears twice in the block body")]
681    DuplicateOutputNote(NoteId),
682    #[error("account update for {0} appears twice in the block body")]
683    DuplicateAccountUpdate(AccountId),
684    #[error("nullifier {0} appears twice in the block body")]
685    DuplicateNullifier(Nullifier),
686    #[error("transaction {0} appears twice in the block body")]
687    DuplicateTransaction(TransactionId),
688}
689
690// ASSET ERROR
691// ================================================================================================
692
693#[derive(Debug, Error)]
694pub enum AssetError {
695    #[error(
696      "fungible asset amount {0} exceeds the max allowed amount of {max_amount}",
697      max_amount = FungibleAsset::MAX_AMOUNT
698    )]
699    FungibleAssetAmountTooBig(u64),
700    #[error("subtracting {subtrahend} from fungible asset amount {minuend} would underflow")]
701    FungibleAssetAmountNotSufficient { minuend: u64, subtrahend: u64 },
702    #[error(
703        "cannot combine fungible assets with different asset IDs: {original_id} and {other_id}"
704    )]
705    FungibleAssetInconsistentIds { original_id: AssetId, other_id: AssetId },
706    #[error("faucet account ID in asset is invalid")]
707    InvalidFaucetAccountId(#[source] Box<dyn Error + Send + Sync + 'static>),
708    #[error(
709        "asset class prefix and suffix in a non-fungible asset ID must match indices 0 and 1 in the value, but asset class was {asset_class} and value was {value}"
710    )]
711    NonFungibleAssetClassMustMatchValue { asset_class: AssetClass, value: Word },
712    #[error("asset class prefix and suffix in a fungible asset ID must be zero but was {0}")]
713    FungibleAssetClassMustBeZero(AssetClass),
714    #[error(
715        "the three most significant elements in a fungible asset's value must be zero but provided value was {0}"
716    )]
717    FungibleAssetValueMostSignificantElementsMustBeZero(Word),
718    #[error("smt proof in asset witness contains invalid ID or value")]
719    AssetWitnessInvalid(#[source] Box<AssetError>),
720    #[error("asset ID {id} is not present in the provided asset witness SMT proof")]
721    AssetWitnessMissingId { id: AssetId },
722    #[error("unknown asset composition encoding: {0}")]
723    UnknownAssetComposition(u8),
724    #[error("unknown asset delta operation encoding: {0}")]
725    UnknownAssetDeltaOperation(u8),
726    #[error("asset composition {0:?} is not supported at this operational site")]
727    UnsupportedAssetComposition(AssetComposition),
728    #[error(
729        "asset composition mismatch for faucet {faucet_id}: expected {expected:?}, found {actual:?}"
730    )]
731    AssetCompositionMismatch {
732        faucet_id: AccountId,
733        expected: AssetComposition,
734        actual: AssetComposition,
735    },
736    #[error("asset metadata byte 0x{0:02x} has reserved bits set to non-zero values")]
737    ReservedAssetMetadata(u8),
738    #[error("unknown asset ID version: {0}")]
739    UnknownAssetIdVersion(u8),
740}
741
742// TOKEN SYMBOL ERROR
743// ================================================================================================
744
745#[derive(Debug, Error)]
746pub enum TokenSymbolError {
747    #[error("token symbol value {0} cannot exceed {max}", max = TokenSymbol::MAX_ENCODED_VALUE)]
748    ValueTooLarge(u64),
749    #[error(
750        "token symbol value {0} cannot be less than {min}",
751        min = TokenSymbol::MIN_ENCODED_VALUE
752    )]
753    ValueTooSmall(u64),
754    #[error("token symbol should have length between 1 and 12 characters, but {0} was provided")]
755    InvalidLength(usize),
756    #[error("token symbol contains a character that is not uppercase ASCII")]
757    InvalidCharacter,
758    #[error("token symbol data left after decoding the specified number of characters")]
759    DataNotFullyDecoded,
760}
761
762impl From<ShortCapitalStringError> for TokenSymbolError {
763    fn from(value: ShortCapitalStringError) -> Self {
764        match value {
765            ShortCapitalStringError::ValueTooLarge(v) => Self::ValueTooLarge(v),
766            ShortCapitalStringError::ValueTooSmall(v) => Self::ValueTooSmall(v),
767            ShortCapitalStringError::InvalidLength(v) => Self::InvalidLength(v),
768            ShortCapitalStringError::InvalidCharacter => Self::InvalidCharacter,
769            ShortCapitalStringError::DataNotFullyDecoded => Self::DataNotFullyDecoded,
770        }
771    }
772}
773
774// ROLE ERROR
775// ================================================================================================
776
777#[derive(Debug, Error)]
778pub enum RoleSymbolError {
779    #[error("role symbol value {0} cannot exceed {max}", max = RoleSymbol::MAX_ENCODED_VALUE)]
780    ValueTooLarge(u64),
781    #[error("role symbol value {0} cannot be less than {min}", min = RoleSymbol::MIN_ENCODED_VALUE)]
782    ValueTooSmall(u64),
783    #[error("role symbol should have length between 1 and 12 characters, but {0} was provided")]
784    InvalidLength(usize),
785    #[error("role symbol contains a character that is not uppercase ASCII or underscore")]
786    InvalidCharacter,
787    #[error("role symbol data left after decoding the specified number of characters")]
788    DataNotFullyDecoded,
789}
790
791impl From<ShortCapitalStringError> for RoleSymbolError {
792    fn from(value: ShortCapitalStringError) -> Self {
793        match value {
794            ShortCapitalStringError::ValueTooLarge(v) => Self::ValueTooLarge(v),
795            ShortCapitalStringError::ValueTooSmall(v) => Self::ValueTooSmall(v),
796            ShortCapitalStringError::InvalidLength(v) => Self::InvalidLength(v),
797            ShortCapitalStringError::InvalidCharacter => Self::InvalidCharacter,
798            ShortCapitalStringError::DataNotFullyDecoded => Self::DataNotFullyDecoded,
799        }
800    }
801}
802
803// SHORT CAPITAL STRING ERROR
804// ================================================================================================
805
806#[derive(Debug, Error)]
807pub(crate) enum ShortCapitalStringError {
808    #[error("short capital string value {0} is too large")]
809    ValueTooLarge(u64),
810    #[error("short capital string value {0} is too small")]
811    ValueTooSmall(u64),
812    #[error(
813        "short capital string should have length between 1 and 12 characters, but {0} was provided"
814    )]
815    InvalidLength(usize),
816    #[error("short capital string contains an invalid character")]
817    InvalidCharacter,
818    #[error("short capital string data left after decoding the specified number of characters")]
819    DataNotFullyDecoded,
820}
821
822// ASSET VAULT ERROR
823// ================================================================================================
824
825#[derive(Debug, Error)]
826pub enum AssetVaultError {
827    #[error("adding fungible asset amounts would exceed maximum allowed amount")]
828    AddFungibleAssetBalanceError(#[source] AssetError),
829    #[error("provided assets contain duplicates")]
830    DuplicateAsset(#[source] MerkleError),
831    #[error("non fungible asset {0} already exists in the vault")]
832    DuplicateNonFungibleAsset(Asset),
833    #[error("fungible asset {0} does not exist in the vault")]
834    FungibleAssetNotFound(FungibleAsset),
835    #[error("non fungible asset {0} does not exist in the vault")]
836    NonFungibleAssetNotFound(Asset),
837    #[error("subtracting fungible asset amounts would underflow")]
838    SubtractFungibleAssetBalanceError(#[source] AssetError),
839    #[error("maximum number of asset vault leaves exceeded")]
840    MaxLeafEntriesExceeded(#[source] MerkleError),
841}
842
843// PARTIAL ASSET VAULT ERROR
844// ================================================================================================
845
846#[derive(Debug, Error)]
847pub enum PartialAssetVaultError {
848    #[error("duplicate asset ID {0} in partial vault")]
849    DuplicateAssetId(AssetId),
850    #[error("partial vault contains invalid asset value {value} at ID {id}")]
851    InvalidAssetForId {
852        id: AssetId,
853        value: Word,
854        #[source]
855        source: AssetError,
856    },
857    #[error("failed to add asset proof")]
858    FailedToAddProof(#[source] MerkleError),
859    #[error("asset is not tracked in the partial vault")]
860    UntrackedAsset(#[source] MerkleError),
861}
862
863// NOTE ERROR
864// ================================================================================================
865
866#[derive(Debug, Error)]
867pub enum NoteError {
868    #[error("error while creating note script: {0}")]
869    MastForestScript(#[source] MastForestScriptError),
870    #[error("note tag length {0} exceeds the maximum of {max}", max = NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH)]
871    NoteTagLengthTooLarge(u8),
872    #[error("duplicate fungible asset from issuer {0} in note")]
873    DuplicateFungibleAsset(AccountId),
874    #[error("duplicate non fungible asset {0} in note")]
875    DuplicateNonFungibleAsset(Asset),
876    #[error("note type {0} is inconsistent with note tag {1}")]
877    InconsistentNoteTag(NoteType, u64),
878    #[error("adding fungible asset amounts would exceed maximum allowed amount")]
879    AddFungibleAssetBalanceError(#[source] AssetError),
880    #[error("note sender is not a valid account ID")]
881    NoteSenderInvalidAccountId(#[source] AccountIdError),
882    #[error("note execution hint after block variant cannot contain u32::MAX")]
883    NoteExecutionHintAfterBlockCannotBeU32Max,
884    #[error("invalid note execution hint payload {1} for tag {0}")]
885    InvalidNoteExecutionHintPayload(u8, u32),
886    #[error(
887    "note type {0} does not match any of the valid note types {public} or {private}",
888    public = NoteType::Public,
889    private = NoteType::Private,
890    )]
891    UnknownNoteType(Box<str>),
892    #[error("block note tree index {block_note_tree_index} is out of bounds 0..={highest_index}")]
893    BlockNoteTreeIndexOutOfBounds {
894        block_note_tree_index: u16,
895        highest_index: usize,
896    },
897    #[error("note network execution requires a public note but note is of type {0}")]
898    NetworkExecutionRequiresPublicNote(NoteType),
899    #[error("failed to assemble note script:\n{}", PrintDiagnostic::new(.0))]
900    NoteScriptAssemblyError(Report),
901    #[error("failed to deserialize note script")]
902    NoteScriptDeserializationError(#[source] DeserializationError),
903    #[error("note contains {0} assets which exceeds the maximum of {max}", max = NoteAssets::MAX_NUM_ASSETS)]
904    TooManyAssets(usize),
905    #[error("note contains {0} storage items which exceeds the maximum of {max}", max = MAX_NOTE_STORAGE_ITEMS)]
906    TooManyStorageItems(usize),
907    #[error("invalid note storage length: expected {expected} items, got {actual}")]
908    InvalidNoteStorageLength { expected: usize, actual: usize },
909    #[error("note tag requires a public note but the note is of type {0}")]
910    PublicNoteRequired(NoteType),
911    #[error("note attachment content must have at least one word")]
912    NoteAttachmentContentEmpty,
913    #[error(
914        "note attachment content contains {0} words, but the maximum is {max} words",
915        max = NoteAttachment::MAX_NUM_WORDS
916    )]
917    NoteAttachmentContentTooManyWords(usize),
918    #[error(
919        "note attachments contain a total of {0} words, but the maximum allowed is {max} words",
920        max = NoteAttachments::MAX_NUM_WORDS
921    )]
922    NoteAttachmentsTooManyWords(usize),
923    #[error(
924        "attachment size {0} exceeds maximum {max}",
925        max = NoteAttachment::MAX_NUM_WORDS
926    )]
927    NoteAttachmentHeaderSizeExceeded(u8),
928    #[error("{0} attachments were provided but maximum is {max}", max = NoteAttachments::MAX_COUNT)]
929    TooManyAttachments(usize),
930    #[error("attachment scheme {0} exceeds maximum value of {max}", max = NoteAttachmentScheme::MAX)]
931    NoteAttachmentSchemeExceeded(u32),
932    #[error("attachment scheme value 0 is reserved")]
933    NoteAttachmentSchemeZeroReserved,
934    #[error("{error_msg}")]
935    Other {
936        error_msg: Box<str>,
937        // thiserror will return this when calling Error::source on NoteError.
938        source: Option<Box<dyn Error + Send + Sync + 'static>>,
939    },
940}
941
942impl NoteError {
943    /// Creates a custom error using the [`NoteError::Other`] variant from an error message.
944    pub fn other(message: impl Into<String>) -> Self {
945        let message: String = message.into();
946        Self::Other { error_msg: message.into(), source: None }
947    }
948
949    /// Creates a custom error using the [`NoteError::Other`] variant from an error message and
950    /// a source error.
951    pub fn other_with_source(
952        message: impl Into<String>,
953        source: impl Error + Send + Sync + 'static,
954    ) -> Self {
955        let message: String = message.into();
956        Self::Other {
957            error_msg: message.into(),
958            source: Some(Box::new(source)),
959        }
960    }
961}
962
963// PARTIAL BLOCKCHAIN ERROR
964// ================================================================================================
965
966#[derive(Debug, Error)]
967pub enum PartialBlockchainError {
968    #[error(
969        "block num {block_num} exceeds chain length {chain_length} implied by the partial blockchain"
970    )]
971    BlockNumTooBig {
972        chain_length: usize,
973        block_num: BlockNumber,
974    },
975
976    #[error("duplicate block {block_num} in partial blockchain")]
977    DuplicateBlock { block_num: BlockNumber },
978
979    #[error("partial blockchain does not track authentication paths for block {block_num}")]
980    UntrackedBlock { block_num: BlockNumber },
981
982    #[error(
983        "provided block header with number {block_num} and commitment {block_commitment} is not tracked by partial MMR"
984    )]
985    BlockHeaderCommitmentMismatch {
986        block_num: BlockNumber,
987        block_commitment: Word,
988        source: MmrError,
989    },
990}
991
992impl PartialBlockchainError {
993    pub fn block_num_too_big(chain_length: usize, block_num: BlockNumber) -> Self {
994        Self::BlockNumTooBig { chain_length, block_num }
995    }
996
997    pub fn duplicate_block(block_num: BlockNumber) -> Self {
998        Self::DuplicateBlock { block_num }
999    }
1000
1001    pub fn untracked_block(block_num: BlockNumber) -> Self {
1002        Self::UntrackedBlock { block_num }
1003    }
1004}
1005
1006// TRANSACTION INPUT ERROR
1007// ================================================================================================
1008
1009#[derive(Debug, Error)]
1010pub enum TransactionInputError {
1011    #[error("transaction input note with nullifier {0} is a duplicate")]
1012    DuplicateInputNote(Nullifier),
1013    #[error("partial blockchain has length {actual} which does not match block number {expected}")]
1014    InconsistentChainLength {
1015        expected: BlockNumber,
1016        actual: BlockNumber,
1017    },
1018    #[error(
1019        "partial blockchain has commitment {actual} which does not match the block header's chain commitment {expected}"
1020    )]
1021    InconsistentChainCommitment { expected: Word, actual: Word },
1022    #[error(
1023        "protocol config has commitment {actual} which does not match the block header's protocol config commitment {expected}"
1024    )]
1025    InconsistentProtocolConfig { expected: Word, actual: Word },
1026    #[error("block in which input note with id {0} was created is not in partial blockchain")]
1027    InputNoteBlockNotInPartialBlockchain(NoteId),
1028    #[error("input note with id {0} was not created in block {1}")]
1029    InputNoteNotInBlock(NoteId, BlockNumber),
1030    #[error(
1031        "total number of input notes is {0} which exceeds the maximum of {MAX_INPUT_NOTES_PER_TX}"
1032    )]
1033    TooManyInputNotes(usize),
1034}
1035
1036// TRANSACTION INPUTS EXTRACTION ERROR
1037// ===============================================================================================
1038
1039#[derive(Debug, Error)]
1040pub enum TransactionInputsExtractionError {
1041    #[error("specified foreign account id matches the transaction input's account id")]
1042    AccountNotForeign,
1043    #[error("foreign account data not found in advice map for account {0}")]
1044    ForeignAccountNotFound(AccountId),
1045    #[error("foreign account code not found for account {0}")]
1046    ForeignAccountCodeNotFound(AccountId),
1047    #[error("storage header data not found in advice map for account {0}")]
1048    StorageHeaderNotFound(AccountId),
1049    #[error("failed to handle account data")]
1050    AccountError(#[from] AccountError),
1051    #[error("failed to handle merkle data")]
1052    MerkleError(#[from] MerkleError),
1053    #[error("failed to handle account tree data")]
1054    AccountTreeError(#[from] AccountTreeError),
1055    #[error("missing vault root from Merkle store")]
1056    MissingVaultRoot,
1057    #[error("missing storage map root from Merkle store")]
1058    MissingMapRoot,
1059    #[error("failed to construct SMT proof")]
1060    SmtProofError(#[from] SmtProofError),
1061    #[error("failed to construct an asset")]
1062    AssetError(#[from] AssetError),
1063    #[error("failed to handle storage map data")]
1064    StorageMapError(#[from] StorageMapError),
1065    #[error("failed to convert elements to leaf index: {0}")]
1066    LeafConversionError(String),
1067    #[error("failed to construct SMT leaf")]
1068    SmtLeafError(#[from] SmtLeafError),
1069}
1070
1071// TRANSACTION OUTPUT ERROR
1072// ===============================================================================================
1073
1074#[derive(Debug, Error)]
1075pub enum TransactionOutputError {
1076    #[error("transaction output note with id {0} is a duplicate")]
1077    DuplicateOutputNote(NoteId),
1078    #[error("final account commitment is not in the advice map")]
1079    FinalAccountCommitmentMissingInAdviceMap,
1080    #[error("failed to parse final account header")]
1081    FinalAccountHeaderParseFailure(#[source] AccountError),
1082    #[error(
1083        "output notes commitment {expected} from kernel does not match computed commitment {actual}"
1084    )]
1085    OutputNotesCommitmentInconsistent { expected: Word, actual: Word },
1086    #[error("transaction kernel output stack is invalid: {0}")]
1087    OutputStackInvalid(String),
1088    #[error(
1089        "total number of output notes is {0} which exceeds the maximum of {MAX_OUTPUT_NOTES_PER_TX}"
1090    )]
1091    TooManyOutputNotes(usize),
1092    #[error("failed to process account update commitment: {0}")]
1093    AccountUpdateCommitment(Box<str>),
1094}
1095
1096// OUTPUT NOTE ERROR
1097// ================================================================================================
1098
1099/// Errors that can occur when creating a
1100/// [`PublicOutputNote`](crate::transaction::PublicOutputNote) or
1101/// [`PrivateOutputNote`](crate::transaction::PrivateOutputNote).
1102#[derive(Debug, Error)]
1103pub enum OutputNoteError {
1104    #[error("attachment headers do not match attachments for private note with id {0}")]
1105    AttachmentHeadersMismatch(NoteId),
1106    #[error("attachments commitment does not match attachments for private note with id {0}")]
1107    AttachmentsCommitmentMismatch(NoteId),
1108    #[error("note with id {0} is private but expected a public note")]
1109    NoteIsPrivate(NoteId),
1110    #[error("note with id {0} is public but expected a private note")]
1111    NoteIsPublic(NoteId),
1112    #[error(
1113        "public note with id {note_id} has size {note_size} bytes which exceeds maximum note size of {NOTE_MAX_SIZE}"
1114    )]
1115    NoteSizeLimitExceeded { note_id: NoteId, note_size: usize },
1116}
1117
1118// TRANSACTION SUMMARY ERROR
1119// ================================================================================================
1120
1121#[derive(Debug, Error)]
1122pub enum TransactionSummaryError {
1123    #[error(
1124        "transaction summary preimage contains {actual} elements but expected {expected} elements"
1125    )]
1126    InvalidPreimageLength { actual: usize, expected: usize },
1127    #[error("transaction summary metadata element {0} sets bits above the packed fields")]
1128    MetadataOutOfRange(Felt),
1129    #[error(
1130        "transaction summary layout version is {actual} but only version {expected} is supported"
1131    )]
1132    UnsupportedVersion { actual: Felt, expected: u8 },
1133}
1134
1135// TRANSACTION EVENT PARSING ERROR
1136// ================================================================================================
1137
1138#[derive(Debug, Error)]
1139pub enum TransactionEventError {
1140    #[error("event id {0} is not a valid transaction event")]
1141    InvalidTransactionEvent(EventId),
1142}
1143
1144// TRANSACTION TRACE PARSING ERROR
1145// ================================================================================================
1146
1147#[derive(Debug, Error)]
1148pub enum TransactionTraceParsingError {
1149    #[error("trace id {0} is an unknown transaction kernel trace")]
1150    UnknownTransactionTrace(u32),
1151}
1152
1153// PROVEN TRANSACTION ERROR
1154// ================================================================================================
1155
1156#[derive(Debug, Error)]
1157pub enum ProvenTransactionError {
1158    #[error(
1159        "proven transaction's final account commitment {tx_final_commitment} and account details commitment {details_commitment} must match"
1160    )]
1161    AccountFinalCommitmentMismatch {
1162        tx_final_commitment: Word,
1163        details_commitment: Word,
1164    },
1165    #[error(
1166        "proven transaction's final account ID {tx_account_id} and account details id {details_account_id} must match"
1167    )]
1168    AccountIdMismatch {
1169        tx_account_id: AccountId,
1170        details_account_id: AccountId,
1171    },
1172    #[error("failed to construct input notes for proven transaction")]
1173    InputNotesError(TransactionInputError),
1174    #[error("private account {0} should not have account details")]
1175    PrivateAccountWithDetails(AccountId),
1176    #[error("account {0} with public state is missing its account details")]
1177    PublicStateAccountMissingDetails(AccountId),
1178    #[error("new account {id} with public state must be accompanied by a full state patch")]
1179    NewPublicStateAccountRequiresFullStatePatch { id: AccountId, source: AccountError },
1180    #[error(
1181        "existing account {0} with public state should only provide delta updates instead of full details"
1182    )]
1183    ExistingPublicStateAccountRequiresDeltaDetails(AccountId),
1184    #[error("failed to construct output notes for proven transaction")]
1185    OutputNotesError(#[source] TransactionOutputError),
1186    #[error(
1187        "account update of size {update_size} for account {account_id} exceeds maximum update size of {ACCOUNT_UPDATE_MAX_SIZE}"
1188    )]
1189    AccountUpdateSizeLimitExceeded {
1190        account_id: AccountId,
1191        update_size: usize,
1192    },
1193    #[error("proven transaction neither changed the account state, nor consumed any notes")]
1194    EmptyTransaction,
1195    #[error(
1196        "expected account patch commitment {expected_patch_commitment} but found {actual_patch_commitment}"
1197    )]
1198    AccountPatchCommitmentMismatch {
1199        expected_patch_commitment: Word,
1200        actual_patch_commitment: Word,
1201    },
1202    #[error("note with id {0} is both created and consumed by the transaction")]
1203    NoteCreatedAndConsumed(NoteId),
1204}
1205
1206// TRANSACTION HEADER ERROR
1207// ================================================================================================
1208
1209/// Error returned when constructing an invalid transaction header.
1210#[derive(Debug, Error)]
1211#[non_exhaustive]
1212pub enum TransactionHeaderError {
1213    #[error("input note with nullifier {0} appears twice in the transaction header")]
1214    DuplicateInputNote(Nullifier),
1215    #[error("output note {0} appears twice in the transaction header")]
1216    DuplicateOutputNote(NoteId),
1217    #[error("note with id {0} is both created and consumed by the transaction header")]
1218    NoteCreatedAndConsumed(NoteId),
1219}
1220
1221impl From<AccountUpdateDetailsValidationError> for ProvenTransactionError {
1222    fn from(error: AccountUpdateDetailsValidationError) -> Self {
1223        match error {
1224            AccountUpdateDetailsValidationError::PrivateAccountWithDetails(account_id) => {
1225                Self::PrivateAccountWithDetails(account_id)
1226            },
1227            AccountUpdateDetailsValidationError::PublicStateAccountMissingDetails(account_id) => {
1228                Self::PublicStateAccountMissingDetails(account_id)
1229            },
1230            AccountUpdateDetailsValidationError::AccountIdMismatch {
1231                account_id,
1232                patch_account_id,
1233            } => Self::AccountIdMismatch {
1234                tx_account_id: account_id,
1235                details_account_id: patch_account_id,
1236            },
1237        }
1238    }
1239}
1240
1241impl From<AccountUpdateSizeValidationError> for ProvenTransactionError {
1242    fn from(error: AccountUpdateSizeValidationError) -> Self {
1243        Self::AccountUpdateSizeLimitExceeded {
1244            account_id: error.account_id,
1245            update_size: error.update_size,
1246        }
1247    }
1248}
1249
1250impl From<AccountUpdateSizeValidationError> for BatchAccountUpdateError {
1251    fn from(error: AccountUpdateSizeValidationError) -> Self {
1252        Self::AccountUpdateSizeLimitExceeded {
1253            account_id: error.account_id,
1254            update_size: error.update_size,
1255        }
1256    }
1257}
1258
1259impl From<AccountUpdateDetailsValidationError> for BatchAccountUpdateError {
1260    fn from(error: AccountUpdateDetailsValidationError) -> Self {
1261        match error {
1262            AccountUpdateDetailsValidationError::PrivateAccountWithDetails(account_id) => {
1263                Self::PrivateAccountWithDetails(account_id)
1264            },
1265            AccountUpdateDetailsValidationError::PublicStateAccountMissingDetails(account_id) => {
1266                Self::PublicStateAccountMissingDetails(account_id)
1267            },
1268            AccountUpdateDetailsValidationError::AccountIdMismatch {
1269                account_id,
1270                patch_account_id,
1271            } => Self::AccountIdMismatch { account_id, patch_account_id },
1272        }
1273    }
1274}
1275
1276impl From<AccountUpdateDetailsValidationError> for BlockAccountUpdateError {
1277    fn from(error: AccountUpdateDetailsValidationError) -> Self {
1278        match error {
1279            AccountUpdateDetailsValidationError::PrivateAccountWithDetails(account_id) => {
1280                Self::PrivateAccountWithDetails(account_id)
1281            },
1282            AccountUpdateDetailsValidationError::PublicStateAccountMissingDetails(account_id) => {
1283                Self::PublicStateAccountMissingDetails(account_id)
1284            },
1285            AccountUpdateDetailsValidationError::AccountIdMismatch {
1286                account_id,
1287                patch_account_id,
1288            } => Self::AccountIdMismatch { account_id, patch_account_id },
1289        }
1290    }
1291}
1292
1293impl From<NewPublicAccountValidationError> for ProvenTransactionError {
1294    fn from(error: NewPublicAccountValidationError) -> Self {
1295        match error {
1296            NewPublicAccountValidationError::RequiresFullStatePatch { id, source } => {
1297                Self::NewPublicStateAccountRequiresFullStatePatch { id, source }
1298            },
1299            NewPublicAccountValidationError::FinalCommitmentMismatch {
1300                final_state_commitment,
1301                account_commitment,
1302            } => Self::AccountFinalCommitmentMismatch {
1303                tx_final_commitment: final_state_commitment,
1304                details_commitment: account_commitment,
1305            },
1306        }
1307    }
1308}
1309
1310impl From<NewPublicAccountValidationError> for BatchAccountUpdateError {
1311    fn from(error: NewPublicAccountValidationError) -> Self {
1312        match error {
1313            NewPublicAccountValidationError::RequiresFullStatePatch { id, source } => {
1314                Self::NewPublicStateAccountRequiresFullStatePatch { id, source }
1315            },
1316            NewPublicAccountValidationError::FinalCommitmentMismatch {
1317                final_state_commitment,
1318                account_commitment,
1319            } => Self::AccountFinalCommitmentMismatch {
1320                final_state_commitment,
1321                account_commitment,
1322            },
1323        }
1324    }
1325}
1326
1327impl From<NewPublicAccountValidationError> for BlockAccountUpdateError {
1328    fn from(error: NewPublicAccountValidationError) -> Self {
1329        match error {
1330            NewPublicAccountValidationError::RequiresFullStatePatch { id, source } => {
1331                Self::NewPublicStateAccountRequiresFullStatePatch { id, source }
1332            },
1333            NewPublicAccountValidationError::FinalCommitmentMismatch {
1334                final_state_commitment,
1335                account_commitment,
1336            } => Self::AccountFinalCommitmentMismatch {
1337                final_state_commitment,
1338                account_commitment,
1339            },
1340        }
1341    }
1342}
1343
1344// PROPOSED BATCH ERROR
1345// ================================================================================================
1346
1347#[derive(Debug, Error)]
1348pub enum ProposedBatchError {
1349    #[error("failed to verify transaction {transaction_id} in transaction batch")]
1350    TransactionVerificationFailed {
1351        transaction_id: TransactionId,
1352        source: TransactionVerifierError,
1353    },
1354
1355    #[error(
1356        "transaction batch has {0} input notes but at most {MAX_INPUT_NOTES_PER_BATCH} are allowed"
1357    )]
1358    TooManyInputNotes(usize),
1359
1360    #[error(
1361        "transaction batch has {0} output notes but at most {MAX_OUTPUT_NOTES_PER_BATCH} are allowed"
1362    )]
1363    TooManyOutputNotes(usize),
1364
1365    #[error(
1366        "transaction batch has {0} account updates but at most {MAX_ACCOUNTS_PER_BATCH} are allowed"
1367    )]
1368    TooManyAccountUpdates(usize),
1369
1370    #[error(
1371        "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}"
1372    )]
1373    ExpiredTransaction {
1374        transaction_id: TransactionId,
1375        transaction_expiration_num: BlockNumber,
1376        reference_block_num: BlockNumber,
1377    },
1378
1379    #[error("transaction batch must contain at least one transaction")]
1380    EmptyTransactionBatch,
1381
1382    #[error("transaction {transaction_id} appears twice in the proposed batch input")]
1383    DuplicateTransaction { transaction_id: TransactionId },
1384
1385    #[error(
1386        "transaction {second_transaction_id} consumes the note with nullifier {note_nullifier} that is also consumed by another transaction {first_transaction_id} in the batch"
1387    )]
1388    DuplicateInputNote {
1389        note_nullifier: Nullifier,
1390        first_transaction_id: TransactionId,
1391        second_transaction_id: TransactionId,
1392    },
1393
1394    #[error(
1395        "transaction {second_transaction_id} creates the note with id {note_id} that is also created by another transaction {first_transaction_id} in the batch"
1396    )]
1397    DuplicateOutputNote {
1398        note_id: NoteId,
1399        first_transaction_id: TransactionId,
1400        second_transaction_id: TransactionId,
1401    },
1402
1403    #[error(
1404        "transaction {consumed_by} that consumes the note with ID {note_id} must be ordered before transaction {created_by} that creates the note"
1405    )]
1406    NoteConsumedBeforeCreated {
1407        note_id: NoteId,
1408        consumed_by: TransactionId,
1409        created_by: TransactionId,
1410    },
1411
1412    #[error("failed to merge transaction patch into account {account_id}")]
1413    AccountUpdateError {
1414        account_id: AccountId,
1415        source: BatchAccountUpdateError,
1416    },
1417
1418    #[error(
1419        "unable to prove unauthenticated note inclusion because block {block_number} in which note with id {note_id} was created is not in partial blockchain"
1420    )]
1421    UnauthenticatedInputNoteBlockNotInPartialBlockchain {
1422        block_number: BlockNumber,
1423        note_id: NoteId,
1424    },
1425
1426    #[error(
1427        "unable to prove unauthenticated note inclusion of note {note_id} in block {block_num}"
1428    )]
1429    UnauthenticatedNoteAuthenticationFailed {
1430        note_id: NoteId,
1431        block_num: BlockNumber,
1432        source: MerkleError,
1433    },
1434
1435    #[error("partial blockchain has length {actual} which does not match block number {expected}")]
1436    InconsistentChainLength {
1437        expected: BlockNumber,
1438        actual: BlockNumber,
1439    },
1440
1441    #[error(
1442        "partial blockchain has root {actual} which does not match block header's root {expected}"
1443    )]
1444    InconsistentChainRoot { expected: Word, actual: Word },
1445
1446    #[error(
1447        "block {block_num} referenced by transaction {transaction_id} is not in the partial blockchain"
1448    )]
1449    MissingTransactionReferenceBlock {
1450        transaction_id: TransactionId,
1451        block_num: BlockNumber,
1452    },
1453
1454    #[error(
1455        "transaction {transaction_id} references block {block_num} with commitment {actual_block_commitment}, but the block in the chain with the same number has commitment {expected_block_commitment}"
1456    )]
1457    TransactionReferenceBlockCommitmentMismatch {
1458        transaction_id: TransactionId,
1459        block_num: BlockNumber,
1460        expected_block_commitment: Word,
1461        actual_block_commitment: Word,
1462    },
1463}
1464
1465// PROVEN BATCH ERROR
1466// ================================================================================================
1467
1468#[derive(Debug, Error)]
1469pub enum ProvenBatchError {
1470    #[error("transaction batch must contain at least one transaction")]
1471    EmptyTransactionBatch,
1472    #[error("transaction {0} appears twice in the proven batch")]
1473    DuplicateTransaction(TransactionId),
1474    #[error(
1475        "transaction batch has {0} input notes but at most {MAX_INPUT_NOTES_PER_BATCH} are allowed"
1476    )]
1477    TooManyInputNotes(usize),
1478    #[error("input note with nullifier {0} appears twice in the proven batch")]
1479    DuplicateInputNote(Nullifier),
1480    #[error(
1481        "transaction batch has {0} output notes but at most {MAX_OUTPUT_NOTES_PER_BATCH} are allowed"
1482    )]
1483    TooManyOutputNotes(usize),
1484    #[error(
1485        "transaction batch has at least {0} account updates but at most {MAX_ACCOUNTS_PER_BATCH} are allowed"
1486    )]
1487    TooManyAccountUpdates(usize),
1488    #[error("output note {0} appears twice in the proven batch")]
1489    DuplicateOutputNote(NoteId),
1490    #[error("note with id {0} is both created and consumed by the proven batch")]
1491    NoteCreatedAndConsumed(NoteId),
1492    #[error("account {0} is updated more than once in the proven batch")]
1493    DuplicateAccountUpdate(AccountId),
1494    #[error("account update for {0} is missing from the proven batch")]
1495    MissingAccountUpdate(AccountId),
1496    #[error("account update for {0} has no corresponding transaction in the proven batch")]
1497    UnexpectedAccountUpdate(AccountId),
1498    #[error(
1499        "transaction {transaction_id} for account {account_id} starts from state {actual_initial_state_commitment}, but the previous transaction ends at state {expected_initial_state_commitment}"
1500    )]
1501    TransactionAccountStateMismatch {
1502        account_id: AccountId,
1503        transaction_id: TransactionId,
1504        expected_initial_state_commitment: Word,
1505        actual_initial_state_commitment: Word,
1506    },
1507    #[error(
1508        "account update for {account_id} starts from state {actual}, but its first transaction starts from state {expected}"
1509    )]
1510    AccountUpdateInitialStateMismatch {
1511        account_id: AccountId,
1512        expected: Word,
1513        actual: Word,
1514    },
1515    #[error(
1516        "account update for {account_id} ends at state {actual}, but its last transaction ends at state {expected}"
1517    )]
1518    AccountUpdateFinalStateMismatch {
1519        account_id: AccountId,
1520        expected: Word,
1521        actual: Word,
1522    },
1523    #[error(
1524        "batch expiration block number {batch_expiration_block_num} is not greater than the reference block number {reference_block_num}"
1525    )]
1526    InvalidBatchExpirationBlockNum {
1527        batch_expiration_block_num: BlockNumber,
1528        reference_block_num: BlockNumber,
1529    },
1530    #[error("batch kernel execution failed")]
1531    BatchKernelExecutionFailed(#[source] ExecutionError),
1532    #[error("batch kernel proving failed")]
1533    BatchKernelProvingFailed(#[source] ExecutionError),
1534    #[error("precompile proving failed")]
1535    PrecompileProvingFailed(#[source] ExecutionError),
1536    #[error("batch proof contains precompiles")]
1537    BatchProofContainsPrecompiles,
1538    #[error("batch kernel produced an invalid output stack")]
1539    BatchKernelOutputInvalid(#[source] BatchOutputError),
1540}
1541
1542// BATCH OUTPUT ERROR
1543// ================================================================================================
1544
1545#[derive(Debug, Error)]
1546pub enum BatchOutputError {
1547    #[error("batch kernel output stack is invalid: {0}")]
1548    OutputStackInvalid(String),
1549    #[error("batch expiration block number {0} does not fit into a u32")]
1550    ExpirationBlockNumberTooLarge(Felt),
1551}
1552
1553// BLOCK OUTPUT ERROR
1554// ================================================================================================
1555
1556#[derive(Debug, Error)]
1557pub enum BlockOutputError {
1558    #[error(
1559        "block kernel output stack has a non-zero element at index {index}, but everything past the nullifier commitment must be zero padding"
1560    )]
1561    PaddingNotZero { index: usize },
1562}
1563
1564// PROPOSED BLOCK ERROR
1565// ================================================================================================
1566
1567#[derive(Debug, Error)]
1568pub enum ProposedBlockError {
1569    #[error("block must contain at least one transaction batch")]
1570    EmptyBlock,
1571
1572    #[error("block must contain at most {MAX_BATCHES_PER_BLOCK} transaction batches")]
1573    TooManyBatches,
1574
1575    #[error(
1576        "batch {batch_id} expired at block {batch_expiration_block_num} but the current block number is {current_block_num}"
1577    )]
1578    ExpiredBatch {
1579        batch_id: BatchId,
1580        batch_expiration_block_num: BlockNumber,
1581        current_block_num: BlockNumber,
1582    },
1583
1584    #[error("batch {batch_id} appears twice in the block inputs")]
1585    DuplicateBatch { batch_id: BatchId },
1586
1587    #[error(
1588        "batch {second_batch_id} consumes the note with nullifier {note_nullifier} that is also consumed by another batch {first_batch_id} in the block"
1589    )]
1590    DuplicateInputNote {
1591        note_nullifier: Nullifier,
1592        first_batch_id: BatchId,
1593        second_batch_id: BatchId,
1594    },
1595
1596    #[error(
1597        "batch {second_batch_id} creates the note with ID {note_id} that is also created by another batch {first_batch_id} in the block"
1598    )]
1599    DuplicateOutputNote {
1600        note_id: NoteId,
1601        first_batch_id: BatchId,
1602        second_batch_id: BatchId,
1603    },
1604
1605    #[error(
1606        "batch {consumed_by} that consumes the note with ID {note_id} must be ordered before batch {created_by} that creates the note"
1607    )]
1608    NoteConsumedBeforeCreated {
1609        note_id: NoteId,
1610        consumed_by: BatchId,
1611        created_by: BatchId,
1612    },
1613
1614    #[error(
1615        "timestamp {provided_timestamp} does not increase monotonically compared to timestamp {previous_timestamp} from the previous block header"
1616    )]
1617    TimestampDoesNotIncreaseMonotonically {
1618        provided_timestamp: u32,
1619        previous_timestamp: u32,
1620    },
1621
1622    #[error(
1623        "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}"
1624    )]
1625    ConflictingBatchesUpdateSameAccount {
1626        account_id: AccountId,
1627        initial_state_commitment: Word,
1628        first_batch_id: BatchId,
1629        second_batch_id: BatchId,
1630    },
1631
1632    #[error(
1633        "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"
1634    )]
1635    ChainLengthNotEqualToPreviousBlockNumber {
1636        chain_length: BlockNumber,
1637        prev_block_num: BlockNumber,
1638    },
1639
1640    #[error(
1641        "partial blockchain has commitment {chain_commitment} which does not match the chain commitment {prev_block_chain_commitment} of the previous block {prev_block_num}"
1642    )]
1643    ChainRootNotEqualToPreviousBlockChainCommitment {
1644        chain_commitment: Word,
1645        prev_block_chain_commitment: Word,
1646        prev_block_num: BlockNumber,
1647    },
1648
1649    #[error(
1650        "partial blockchain is missing block {reference_block_num} referenced by batch {batch_id} in the block"
1651    )]
1652    BatchReferenceBlockMissingFromChain {
1653        reference_block_num: BlockNumber,
1654        batch_id: BatchId,
1655    },
1656
1657    #[error(
1658        "failed to prove unauthenticated note inclusion because block {block_number} in which note with id {note_id} was created is not in partial blockchain"
1659    )]
1660    UnauthenticatedInputNoteBlockNotInPartialBlockchain {
1661        block_number: BlockNumber,
1662        note_id: NoteId,
1663    },
1664
1665    #[error(
1666        "failed to prove unauthenticated note inclusion of note {note_id} in block {block_num}"
1667    )]
1668    UnauthenticatedNoteAuthenticationFailed {
1669        note_id: NoteId,
1670        block_num: BlockNumber,
1671        source: MerkleError,
1672    },
1673
1674    #[error(
1675        "unauthenticated note with nullifier {nullifier} was not created in the same block and no inclusion proof to authenticate it was provided"
1676    )]
1677    UnauthenticatedNoteConsumed { nullifier: Nullifier },
1678
1679    #[error("block inputs do not contain a proof of inclusion for account {0}")]
1680    MissingAccountWitness(AccountId),
1681
1682    #[error(
1683        "account {account_id} with state {state_commitment} cannot transition to any of the remaining states {}",
1684        remaining_state_commitments.iter().map(Word::to_hex).collect::<Vec<_>>().join(", ")
1685    )]
1686    InconsistentAccountStateTransition {
1687        account_id: AccountId,
1688        state_commitment: Word,
1689        remaining_state_commitments: Vec<Word>,
1690    },
1691
1692    #[error("no proof for nullifier {0} was provided")]
1693    NullifierProofMissing(Nullifier),
1694
1695    #[error("note with nullifier {0} is already spent")]
1696    NullifierSpent(Nullifier),
1697
1698    #[error("failed to merge transaction patch into account {account_id}")]
1699    AccountUpdateError {
1700        account_id: AccountId,
1701        source: Box<AccountPatchError>,
1702    },
1703
1704    #[error("failed to track account witness")]
1705    AccountWitnessTracking { source: AccountTreeError },
1706
1707    #[error(
1708        "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"
1709    )]
1710    StaleAccountTreeRoot {
1711        prev_block_account_root: Word,
1712        stale_account_root: Word,
1713    },
1714
1715    #[error("account ID prefix already exists in the tree")]
1716    AccountIdPrefixDuplicate { source: AccountTreeError },
1717
1718    #[error(
1719        "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"
1720    )]
1721    StaleNullifierTreeRoot {
1722        prev_block_nullifier_root: Word,
1723        stale_nullifier_root: Word,
1724    },
1725
1726    #[error("nullifier witness has a different root than the current nullifier tree root")]
1727    NullifierWitnessRootMismatch(NullifierTreeError),
1728}
1729
1730// PROTOCOL CONFIG ERROR
1731// ================================================================================================
1732
1733/// Error returned when constructing an invalid protocol configuration.
1734#[derive(Debug, thiserror::Error)]
1735#[non_exhaustive]
1736pub enum ProtocolConfigError {
1737    #[error("fee asset composition {0:?} is not supported, it must be fungible")]
1738    FeeAssetMustBeFungible(AssetComposition),
1739    #[error("minimum proof security must be at least one bit")]
1740    MinimumSecurityBitsMustBeNonZero,
1741    #[error("next protocol config cannot become effective at the genesis block")]
1742    NextConfigEffectiveAtGenesis,
1743    #[error(
1744        "kernel config contains {count} procedures but must contain at most {max}",
1745        max = KernelConfig::MAX_NUM_KERNEL_PROCEDURES,
1746    )]
1747    TooManyKernelProcedures { count: usize },
1748}
1749
1750// VALIDATOR CONFIG ERROR
1751// ================================================================================================
1752
1753/// Error returned when constructing an invalid [`ValidatorConfig`].
1754#[derive(Debug, thiserror::Error)]
1755#[non_exhaustive]
1756pub enum ValidatorConfigError {
1757    #[error("validator set must contain at least one key")]
1758    EmptySet,
1759    #[error(
1760        "validator set contains {count} keys but must contain at most {max}",
1761        max = ValidatorConfig::MAX_VALIDATORS,
1762    )]
1763    TooManyKeys { count: usize },
1764    #[error("validator set contains duplicate public keys")]
1765    DuplicateKey,
1766    #[error("quorum is {quorum} but must equal the validator count of {count}")]
1767    QuorumMustEqualValidatorCount { quorum: u16, count: usize },
1768}
1769
1770// NULLIFIER TREE ERROR
1771// ================================================================================================
1772
1773#[derive(Debug, Error)]
1774pub enum NullifierTreeError {
1775    #[error(
1776        "entries passed to nullifier tree contain multiple block numbers for the same nullifier"
1777    )]
1778    DuplicateNullifierBlockNumbers(#[source] MerkleError),
1779
1780    #[error("attempt to mark nullifier {0} as spent but it is already spent")]
1781    NullifierAlreadySpent(Nullifier),
1782
1783    #[error("maximum number of nullifier tree leaves exceeded")]
1784    MaxLeafEntriesExceeded(#[source] MerkleError),
1785
1786    #[error("nullifier {nullifier} is not tracked by the partial nullifier tree")]
1787    UntrackedNullifier {
1788        nullifier: Nullifier,
1789        source: MerkleError,
1790    },
1791
1792    #[error("new tree root after nullifier witness insertion does not match previous tree root")]
1793    TreeRootConflict(#[source] MerkleError),
1794
1795    #[error("failed to compute nullifier tree mutations")]
1796    ComputeMutations(#[source] MerkleError),
1797
1798    #[error("invalid nullifier block number")]
1799    InvalidNullifierBlockNumber(Word),
1800}
1801
1802// AUTH SCHEME ERROR
1803// ================================================================================================
1804
1805#[derive(Debug, Error)]
1806pub enum AuthSchemeError {
1807    #[error("auth scheme identifier `{0}` is not valid")]
1808    InvalidAuthSchemeIdentifier(String),
1809}
1810
1811// TRANSACTION VERIFIER ERROR
1812// ================================================================================================
1813
1814#[derive(Debug, Error)]
1815pub enum TransactionVerifierError {
1816    #[error("failed to verify transaction")]
1817    TransactionVerificationFailed(#[source] VerificationError),
1818    #[error("transaction proof contains settled precompile work")]
1819    TransactionProofContainsPrecompiles,
1820    #[error("transaction proof security level is {actual} but must be at least {expected_minimum}")]
1821    InsufficientProofSecurityLevel { actual: u32, expected_minimum: u32 },
1822}