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