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::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},
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("faucet metadata decimals is {actual} which exceeds max value of {max}")]
99    FungibleFaucetTooManyDecimals { actual: u8, max: u8 },
100    #[error("faucet metadata max supply is {actual} which exceeds max value of {max}")]
101    FungibleFaucetMaxSupplyTooLarge { actual: u64, max: u64 },
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    #[error("failed to parse account ID from final account header")]
134    FinalAccountHeaderIdParsingFailed(#[source] AccountIdError),
135    /// This variant can be used by methods that are not inherent to the account but want to return
136    /// this error type.
137    #[error("assumption violated: {0}")]
138    AssumptionViolated(String),
139}
140
141// ACCOUNT ID ERROR
142// ================================================================================================
143
144#[derive(Debug, Error)]
145pub enum AccountIdError {
146    #[error("failed to convert bytes into account ID prefix field element")]
147    AccountIdInvalidPrefixFieldElement(#[source] DeserializationError),
148    #[error("failed to convert bytes into account ID suffix field element")]
149    AccountIdInvalidSuffixFieldElement(#[source] DeserializationError),
150    #[error("`{0}` is not a known account storage mode")]
151    UnknownAccountStorageMode(Box<str>),
152    #[error(r#"`{0}` is not a known account type, expected one of "FungibleFaucet", "NonFungibleFaucet", "RegularAccountImmutableCode" or "RegularAccountUpdatableCode""#)]
153    UnknownAccountType(Box<str>),
154    #[error("failed to parse hex string into account ID")]
155    AccountIdHexParseError(#[source] HexParseError),
156    #[error("`{0}` is not a known account ID version")]
157    UnknownAccountIdVersion(u8),
158    #[error("anchor epoch in account ID must not be u16::MAX ({})", u16::MAX)]
159    AnchorEpochMustNotBeU16Max,
160    #[error("least significant byte of account ID suffix must be zero")]
161    AccountIdSuffixLeastSignificantByteMustBeZero,
162    #[error(
163        "anchor block must be an epoch block, that is, its block number must be a multiple of 2^{}",
164        BlockNumber::EPOCH_LENGTH_EXPONENT
165    )]
166    AnchorBlockMustBeEpochBlock,
167    #[error("failed to decode bech32 string into account ID")]
168    Bech32DecodeError(#[source] Bech32Error),
169}
170
171// BECH32 ERROR
172// ================================================================================================
173
174#[derive(Debug, Error)]
175pub enum Bech32Error {
176    #[error("failed to decode bech32 string")]
177    DecodeError(#[source] Box<dyn Error + Send + Sync + 'static>),
178    #[error("found unknown address type {0} which is not the expected {account_addr} account ID address type",
179      account_addr = AddressType::AccountId as u8
180    )]
181    UnknownAddressType(u8),
182    #[error("expected bech32 data to be of length {expected} but it was of length {actual}")]
183    InvalidDataLength { expected: usize, actual: usize },
184}
185
186// NETWORK ID ERROR
187// ================================================================================================
188
189#[derive(Debug, Error)]
190pub enum NetworkIdError {
191    #[error("failed to parse string into a network ID")]
192    NetworkIdParseError(#[source] Box<dyn Error + Send + Sync + 'static>),
193}
194
195// ACCOUNT DELTA ERROR
196// ================================================================================================
197
198#[derive(Debug, Error)]
199pub enum AccountDeltaError {
200    #[error("storage slot {0} was updated as a value and as a map")]
201    StorageSlotUsedAsDifferentTypes(u8),
202    #[error("non fungible vault can neither be added nor removed twice")]
203    DuplicateNonFungibleVaultUpdate(NonFungibleAsset),
204    #[error(
205        "fungible asset issued by faucet {faucet_id} has delta {delta} which overflows when added to current value {current}"
206    )]
207    FungibleAssetDeltaOverflow {
208        faucet_id: AccountId,
209        current: i64,
210        delta: i64,
211    },
212    #[error(
213        "account update of type `{left_update_type}` cannot be merged with account update of type `{right_update_type}`"
214    )]
215    IncompatibleAccountUpdates {
216        left_update_type: &'static str,
217        right_update_type: &'static str,
218    },
219    #[error("account delta could not be applied to account {account_id}")]
220    AccountDeltaApplicationFailed {
221        account_id: AccountId,
222        source: AccountError,
223    },
224    #[error("inconsistent nonce update: {0}")]
225    InconsistentNonceUpdate(String),
226    #[error("account ID {0} in fungible asset delta is not of type fungible faucet")]
227    NotAFungibleFaucetId(AccountId),
228}
229
230// BATCH ACCOUNT UPDATE ERROR
231// ================================================================================================
232
233#[derive(Debug, Error)]
234pub enum BatchAccountUpdateError {
235    #[error(
236        "account update for account {expected_account_id} cannot be merged with update from transaction {transaction} which was executed against account {actual_account_id}"
237    )]
238    AccountUpdateIdMismatch {
239        transaction: TransactionId,
240        expected_account_id: AccountId,
241        actual_account_id: AccountId,
242    },
243    #[error(
244        "final state commitment in account update from transaction {0} does not match initial state of current update"
245    )]
246    AccountUpdateInitialStateMismatch(TransactionId),
247    #[error("failed to merge account delta from transaction {0}")]
248    TransactionUpdateMergeError(TransactionId, #[source] AccountDeltaError),
249}
250
251// ASSET ERROR
252// ================================================================================================
253
254#[derive(Debug, Error)]
255pub enum AssetError {
256    #[error(
257      "fungible asset amount {0} exceeds the max allowed amount of {max_amount}",
258      max_amount = FungibleAsset::MAX_AMOUNT
259    )]
260    FungibleAssetAmountTooBig(u64),
261    #[error("subtracting {subtrahend} from fungible asset amount {minuend} would overflow")]
262    FungibleAssetAmountNotSufficient { minuend: u64, subtrahend: u64 },
263    #[error("fungible asset word {hex} does not contain expected ZERO at word index 1",
264      hex = vm_core::utils::to_hex(Felt::elements_as_bytes(.0))
265    )]
266    FungibleAssetExpectedZero(Word),
267    #[error(
268        "cannot add fungible asset with issuer {other_issuer} to fungible asset with issuer {original_issuer}"
269    )]
270    FungibleAssetInconsistentFaucetIds {
271        original_issuer: AccountId,
272        other_issuer: AccountId,
273    },
274    #[error("faucet account ID in asset is invalid")]
275    InvalidFaucetAccountId(#[source] Box<dyn Error + Send + Sync + 'static>),
276    #[error(
277      "faucet id {0} of type {id_type} must be of type {expected_ty} for fungible assets",
278      id_type = .0.account_type(),
279      expected_ty = AccountType::FungibleFaucet
280    )]
281    FungibleFaucetIdTypeMismatch(AccountId),
282    #[error(
283      "faucet id {0} of type {id_type} must be of type {expected_ty} for non fungible assets",
284      id_type = .0.account_type(),
285      expected_ty = AccountType::NonFungibleFaucet
286    )]
287    NonFungibleFaucetIdTypeMismatch(AccountIdPrefix),
288    #[error("{0}")]
289    TokenSymbolError(String),
290}
291
292// ASSET VAULT ERROR
293// ================================================================================================
294
295#[derive(Debug, Error)]
296pub enum AssetVaultError {
297    #[error("adding fungible asset amounts would exceed maximum allowed amount")]
298    AddFungibleAssetBalanceError(#[source] AssetError),
299    #[error("provided assets contain duplicates")]
300    DuplicateAsset(#[source] MerkleError),
301    #[error("non fungible asset {0} already exists in the vault")]
302    DuplicateNonFungibleAsset(NonFungibleAsset),
303    #[error("fungible asset {0} does not exist in the vault")]
304    FungibleAssetNotFound(FungibleAsset),
305    #[error("faucet id {0} is not a fungible faucet id")]
306    NotAFungibleFaucetId(AccountId),
307    #[error("non fungible asset {0} does not exist in the vault")]
308    NonFungibleAssetNotFound(NonFungibleAsset),
309    #[error("subtracting fungible asset amounts would underflow")]
310    SubtractFungibleAssetBalanceError(#[source] AssetError),
311}
312
313// NOTE ERROR
314// ================================================================================================
315
316#[derive(Debug, Error)]
317pub enum NoteError {
318    #[error("duplicate fungible asset from issuer {0} in note")]
319    DuplicateFungibleAsset(AccountId),
320    #[error("duplicate non fungible asset {0} in note")]
321    DuplicateNonFungibleAsset(NonFungibleAsset),
322    #[error("note type {0:?} is inconsistent with note tag {1}")]
323    InconsistentNoteTag(NoteType, u64),
324    #[error("adding fungible asset amounts would exceed maximum allowed amount")]
325    AddFungibleAssetBalanceError(#[source] AssetError),
326    #[error("note sender is not a valid account ID")]
327    NoteSenderInvalidAccountId(#[source] AccountIdError),
328    #[error("note tag use case {0} must be less than 2^{exp}", exp = NoteTag::MAX_USE_CASE_ID_EXPONENT)]
329    NoteTagUseCaseTooLarge(u16),
330    #[error(
331        "note execution hint tag {0} must be in range {from}..={to}",
332        from = NoteExecutionHint::NONE_TAG,
333        to = NoteExecutionHint::ON_BLOCK_SLOT_TAG,
334    )]
335    NoteExecutionHintTagOutOfRange(u8),
336    #[error("note execution hint after block variant cannot contain u32::MAX")]
337    NoteExecutionHintAfterBlockCannotBeU32Max,
338    #[error("invalid note execution hint payload {1} for tag {0}")]
339    InvalidNoteExecutionHintPayload(u8, u32),
340    #[error("note type {0:b} does not match any of the valid note types {public}, {private} or {encrypted}",
341      public = NoteType::Public as u8,
342      private = NoteType::Private as u8,
343      encrypted = NoteType::Encrypted as u8,
344    )]
345    InvalidNoteType(u64),
346    #[error("note location index {node_index_in_block} is out of bounds 0..={highest_index}")]
347    NoteLocationIndexOutOfBounds {
348        node_index_in_block: u16,
349        highest_index: usize,
350    },
351    #[error("note network execution requires public accounts")]
352    NetworkExecutionRequiresPublicAccount,
353    #[error("note network execution requires a public note but note is of type {0:?}")]
354    NetworkExecutionRequiresPublicNote(NoteType),
355    #[error("failed to assemble note script:\n{}", PrintDiagnostic::new(.0))]
356    NoteScriptAssemblyError(Report),
357    #[error("failed to deserialize note script")]
358    NoteScriptDeserializationError(#[source] DeserializationError),
359    #[error("public use case requires a public note but note is of type {0:?}")]
360    PublicUseCaseRequiresPublicNote(NoteType),
361    #[error("note contains {0} assets which exceeds the maximum of {max}", max = NoteAssets::MAX_NUM_ASSETS)]
362    TooManyAssets(usize),
363    #[error("note contains {0} inputs which exceeds the maximum of {max}", max = MAX_INPUTS_PER_NOTE)]
364    TooManyInputs(usize),
365}
366
367// CHAIN MMR ERROR
368// ================================================================================================
369
370#[derive(Debug, Error)]
371pub enum ChainMmrError {
372    #[error("block num {block_num} exceeds chain length {chain_length} implied by the chain MMR")]
373    BlockNumTooBig {
374        chain_length: usize,
375        block_num: BlockNumber,
376    },
377    #[error("duplicate block {block_num} in chain MMR")]
378    DuplicateBlock { block_num: BlockNumber },
379    #[error("chain MMR does not track authentication paths for block {block_num}")]
380    UntrackedBlock { block_num: BlockNumber },
381}
382
383impl ChainMmrError {
384    pub fn block_num_too_big(chain_length: usize, block_num: BlockNumber) -> Self {
385        Self::BlockNumTooBig { chain_length, block_num }
386    }
387
388    pub fn duplicate_block(block_num: BlockNumber) -> Self {
389        Self::DuplicateBlock { block_num }
390    }
391
392    pub fn untracked_block(block_num: BlockNumber) -> Self {
393        Self::UntrackedBlock { block_num }
394    }
395}
396
397// TRANSACTION SCRIPT ERROR
398// ================================================================================================
399
400#[derive(Debug, Error)]
401pub enum TransactionScriptError {
402    #[error("failed to assemble transaction script:\n{}", PrintDiagnostic::new(.0))]
403    AssemblyError(Report),
404}
405
406// TRANSACTION INPUT ERROR
407// ================================================================================================
408
409#[derive(Debug, Error)]
410pub enum TransactionInputError {
411    #[error("account seed must be provided for new accounts")]
412    AccountSeedNotProvidedForNewAccount,
413    #[error("account seed must not be provided for existing accounts")]
414    AccountSeedProvidedForExistingAccount,
415    #[error(
416      "anchor block header for epoch {0} (block number = {block_number}) must be provided in the chain mmr for the new account",
417      block_number = BlockNumber::from_epoch(*.0),
418    )]
419    AnchorBlockHeaderNotProvidedForNewAccount(u16),
420    #[error("transaction input note with nullifier {0} is a duplicate")]
421    DuplicateInputNote(Nullifier),
422    #[error(
423        "ID {expected} of the new account does not match the ID {actual} computed from the provided seed"
424    )]
425    InconsistentAccountSeed { expected: AccountId, actual: AccountId },
426    #[error("chain mmr has length {actual} which does not match block number {expected}")]
427    InconsistentChainLength {
428        expected: BlockNumber,
429        actual: BlockNumber,
430    },
431    #[error(
432        "chain mmr has commitment {actual} which does not match the block header's chain commitment {expected}"
433    )]
434    InconsistentChainCommitment { expected: Digest, actual: Digest },
435    #[error("block in which input note with id {0} was created is not in chain mmr")]
436    InputNoteBlockNotInChainMmr(NoteId),
437    #[error("input note with id {0} was not created in block {1}")]
438    InputNoteNotInBlock(NoteId, BlockNumber),
439    #[error("account ID computed from seed is invalid")]
440    InvalidAccountIdSeed(#[source] AccountIdError),
441    #[error(
442        "total number of input notes is {0} which exceeds the maximum of {MAX_INPUT_NOTES_PER_TX}"
443    )]
444    TooManyInputNotes(usize),
445}
446
447// TRANSACTION OUTPUT ERROR
448// ===============================================================================================
449
450#[derive(Debug, Error)]
451pub enum TransactionOutputError {
452    #[error("transaction output note with id {0} is a duplicate")]
453    DuplicateOutputNote(NoteId),
454    #[error("final account commitment is not in the advice map")]
455    FinalAccountHashMissingInAdviceMap,
456    #[error("failed to parse final account header")]
457    FinalAccountHeaderParseFailure(#[source] AccountError),
458    #[error(
459        "output notes commitment {expected} from kernel does not match computed commitment {actual}"
460    )]
461    OutputNotesCommitmentInconsistent { expected: Digest, actual: Digest },
462    #[error("transaction kernel output stack is invalid: {0}")]
463    OutputStackInvalid(String),
464    #[error(
465        "total number of output notes is {0} which exceeds the maximum of {MAX_OUTPUT_NOTES_PER_TX}"
466    )]
467    TooManyOutputNotes(usize),
468}
469
470// PROVEN TRANSACTION ERROR
471// ================================================================================================
472
473#[derive(Debug, Error)]
474pub enum ProvenTransactionError {
475    #[error(
476        "proven transaction's final account commitment {tx_final_commitment} and account details commitment {details_commitment} must match"
477    )]
478    AccountFinalCommitmentMismatch {
479        tx_final_commitment: Digest,
480        details_commitment: Digest,
481    },
482    #[error(
483        "proven transaction's final account ID {tx_account_id} and account details id {details_account_id} must match"
484    )]
485    AccountIdMismatch {
486        tx_account_id: AccountId,
487        details_account_id: AccountId,
488    },
489    #[error("failed to construct input notes for proven transaction")]
490    InputNotesError(TransactionInputError),
491    #[error("private account {0} should not have account details")]
492    PrivateAccountWithDetails(AccountId),
493    #[error("public account {0} is missing its account details")]
494    PublicAccountMissingDetails(AccountId),
495    #[error("new public account {0} is missing its account details")]
496    NewPublicAccountRequiresFullDetails(AccountId),
497    #[error(
498        "existing public account {0} should only provide delta updates instead of full details"
499    )]
500    ExistingPublicAccountRequiresDeltaDetails(AccountId),
501    #[error("failed to construct output notes for proven transaction")]
502    OutputNotesError(TransactionOutputError),
503    #[error(
504        "account update of size {update_size} for account {account_id} exceeds maximum update size of {ACCOUNT_UPDATE_MAX_SIZE}"
505    )]
506    AccountUpdateSizeLimitExceeded {
507        account_id: AccountId,
508        update_size: usize,
509    },
510}
511
512// PROPOSED BATCH ERROR
513// ================================================================================================
514
515#[derive(Debug, Error)]
516pub enum ProposedBatchError {
517    #[error(
518        "transaction batch has {0} input notes but at most {MAX_INPUT_NOTES_PER_BATCH} are allowed"
519    )]
520    TooManyInputNotes(usize),
521
522    #[error(
523        "transaction batch has {0} output notes but at most {MAX_OUTPUT_NOTES_PER_BATCH} are allowed"
524    )]
525    TooManyOutputNotes(usize),
526
527    #[error(
528        "transaction batch has {0} account updates but at most {MAX_ACCOUNTS_PER_BATCH} are allowed"
529    )]
530    TooManyAccountUpdates(usize),
531
532    #[error(
533        "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}"
534    )]
535    ExpiredTransaction {
536        transaction_id: TransactionId,
537        transaction_expiration_num: BlockNumber,
538        reference_block_num: BlockNumber,
539    },
540
541    #[error("transaction batch must contain at least one transaction")]
542    EmptyTransactionBatch,
543
544    #[error("transaction {transaction_id} appears twice in the proposed batch input")]
545    DuplicateTransaction { transaction_id: TransactionId },
546
547    #[error(
548        "transaction {second_transaction_id} consumes the note with nullifier {note_nullifier} that is also consumed by another transaction {first_transaction_id} in the batch"
549    )]
550    DuplicateInputNote {
551        note_nullifier: Nullifier,
552        first_transaction_id: TransactionId,
553        second_transaction_id: TransactionId,
554    },
555
556    #[error(
557        "transaction {second_transaction_id} creates the note with id {note_id} that is also created by another transaction {first_transaction_id} in the batch"
558    )]
559    DuplicateOutputNote {
560        note_id: NoteId,
561        first_transaction_id: TransactionId,
562        second_transaction_id: TransactionId,
563    },
564
565    #[error(
566        "note commitment mismatch for note {id}: (input: {input_commitment}, output: {output_commitment})"
567    )]
568    NoteCommitmentMismatch {
569        id: NoteId,
570        input_commitment: Digest,
571        output_commitment: Digest,
572    },
573
574    #[error("failed to merge transaction delta into account {account_id}")]
575    AccountUpdateError {
576        account_id: AccountId,
577        source: BatchAccountUpdateError,
578    },
579
580    #[error(
581        "unable to prove unauthenticated note inclusion because block {block_number} in which note with id {note_id} was created is not in chain mmr"
582    )]
583    UnauthenticatedInputNoteBlockNotInChainMmr {
584        block_number: BlockNumber,
585        note_id: NoteId,
586    },
587
588    #[error(
589        "unable to prove unauthenticated note inclusion of note {note_id} in block {block_num}"
590    )]
591    UnauthenticatedNoteAuthenticationFailed {
592        note_id: NoteId,
593        block_num: BlockNumber,
594        source: MerkleError,
595    },
596
597    #[error("chain mmr has length {actual} which does not match block number {expected}")]
598    InconsistentChainLength {
599        expected: BlockNumber,
600        actual: BlockNumber,
601    },
602
603    #[error("chain mmr has root {actual} which does not match block header's root {expected}")]
604    InconsistentChainRoot { expected: Digest, actual: Digest },
605
606    #[error(
607        "block {block_reference} referenced by transaction {transaction_id} is not in the chain mmr"
608    )]
609    MissingTransactionBlockReference {
610        block_reference: Digest,
611        transaction_id: TransactionId,
612    },
613}
614
615// PROPOSED BLOCK ERROR
616// ================================================================================================
617
618#[derive(Debug, Error)]
619pub enum ProposedBlockError {
620    #[error("block must contain at least one transaction batch")]
621    EmptyBlock,
622
623    #[error("block must contain at most {MAX_BATCHES_PER_BLOCK} transaction batches")]
624    TooManyBatches,
625
626    #[error(
627        "batch {batch_id} expired at block {batch_expiration_block_num} but the current block number is {current_block_num}"
628    )]
629    ExpiredBatch {
630        batch_id: BatchId,
631        batch_expiration_block_num: BlockNumber,
632        current_block_num: BlockNumber,
633    },
634
635    #[error("batch {batch_id} appears twice in the block inputs")]
636    DuplicateBatch { batch_id: BatchId },
637
638    #[error(
639        "batch {second_batch_id} consumes the note with nullifier {note_nullifier} that is also consumed by another batch {first_batch_id} in the block"
640    )]
641    DuplicateInputNote {
642        note_nullifier: Nullifier,
643        first_batch_id: BatchId,
644        second_batch_id: BatchId,
645    },
646
647    #[error(
648        "batch {second_batch_id} creates the note with ID {note_id} that is also created by another batch {first_batch_id} in the block"
649    )]
650    DuplicateOutputNote {
651        note_id: NoteId,
652        first_batch_id: BatchId,
653        second_batch_id: BatchId,
654    },
655
656    #[error(
657        "timestamp {provided_timestamp} does not increase monotonically compared to timestamp {previous_timestamp} from the previous block header"
658    )]
659    TimestampDoesNotIncreaseMonotonically {
660        provided_timestamp: u32,
661        previous_timestamp: u32,
662    },
663
664    #[error(
665        "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}"
666    )]
667    ConflictingBatchesUpdateSameAccount {
668        account_id: AccountId,
669        initial_state_commitment: Digest,
670        first_batch_id: BatchId,
671        second_batch_id: BatchId,
672    },
673
674    #[error(
675        "chain mmr 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"
676    )]
677    ChainLengthNotEqualToPreviousBlockNumber {
678        chain_length: BlockNumber,
679        prev_block_num: BlockNumber,
680    },
681
682    #[error(
683        "chain mmr has commitment {chain_commitment} which does not match the chain commitment {prev_block_chain_commitment} of the previous block {prev_block_num}"
684    )]
685    ChainRootNotEqualToPreviousBlockChainCommitment {
686        chain_commitment: Digest,
687        prev_block_chain_commitment: Digest,
688        prev_block_num: BlockNumber,
689    },
690
691    #[error(
692        "chain mmr is missing block {reference_block_num} referenced by batch {batch_id} in the block"
693    )]
694    BatchReferenceBlockMissingFromChain {
695        reference_block_num: BlockNumber,
696        batch_id: BatchId,
697    },
698
699    #[error(
700        "note commitment mismatch for note {id}: (input: {input_commitment}, output: {output_commitment})"
701    )]
702    NoteCommitmentMismatch {
703        id: NoteId,
704        input_commitment: Digest,
705        output_commitment: Digest,
706    },
707
708    #[error(
709        "failed to prove unauthenticated note inclusion because block {block_number} in which note with id {note_id} was created is not in chain mmr"
710    )]
711    UnauthenticatedInputNoteBlockNotInChainMmr {
712        block_number: BlockNumber,
713        note_id: NoteId,
714    },
715
716    #[error(
717        "failed to prove unauthenticated note inclusion of note {note_id} in block {block_num}"
718    )]
719    UnauthenticatedNoteAuthenticationFailed {
720        note_id: NoteId,
721        block_num: BlockNumber,
722        source: MerkleError,
723    },
724
725    #[error(
726        "unauthenticated note with nullifier {nullifier} was not created in the same block and no inclusion proof to authenticate it was provided"
727    )]
728    UnauthenticatedNoteConsumed { nullifier: Nullifier },
729
730    #[error("block inputs do not contain a proof of inclusion for account {0}")]
731    MissingAccountWitness(AccountId),
732
733    #[error(
734        "account {account_id} with state {state_commitment} cannot transition to any of the remaining states {remaining_state_commitments:?}"
735    )]
736    InconsistentAccountStateTransition {
737        account_id: AccountId,
738        state_commitment: Digest,
739        remaining_state_commitments: Vec<Digest>,
740    },
741
742    #[error("no proof for nullifier {0} was provided")]
743    NullifierProofMissing(Nullifier),
744
745    #[error("note with nullifier {0} is already spent")]
746    NullifierSpent(Nullifier),
747
748    #[error("failed to merge transaction delta into account {account_id}")]
749    AccountUpdateError {
750        account_id: AccountId,
751        source: AccountDeltaError,
752    },
753}
754
755// NULLIFIER TREE ERROR
756// ================================================================================================
757
758#[derive(Debug, Error)]
759pub enum NullifierTreeError {
760    #[error("attempt to mark nullifier {0} as spent but it is already spent")]
761    NullifierAlreadySpent(Nullifier),
762    #[error("nullifier {nullifier} is not tracked by the partial nullifier tree")]
763    UntrackedNullifier {
764        nullifier: Nullifier,
765        source: MerkleError,
766    },
767    #[error("new tree root after nullifier witness insertion does not match previous tree root")]
768    TreeRootConflict(#[source] MerkleError),
769}