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