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#[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#[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 #[error("assumption violated: {0}")]
144 AssumptionViolated(String),
145}
146
147#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[derive(Debug, Error)]
475pub enum TransactionScriptError {
476 #[error("failed to assemble transaction script:\n{}", PrintDiagnostic::new(.0))]
477 AssemblyError(Report),
478}
479
480#[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#[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#[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#[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#[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#[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#[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}