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