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 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 #[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[derive(Debug, Error)]
478pub enum TransactionScriptError {
479 #[error("failed to assemble transaction script:\n{}", PrintDiagnostic::new(.0))]
480 AssemblyError(Report),
481}
482
483#[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#[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#[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#[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#[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#[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#[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}