miden_objects/
errors.rs

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