1use alloc::boxed::Box;
2use alloc::string::String;
3use alloc::vec::Vec;
4use core::error::Error;
5
6use miden_assembly::Report;
7use miden_assembly::diagnostics::reporting::PrintDiagnostic;
8use miden_core::Felt;
9use miden_core::mast::MastForestError;
10use miden_crypto::merkle::MmrError;
11use miden_crypto::utils::HexParseError;
12use miden_processor::DeserializationError;
13use thiserror::Error;
14
15use super::account::AccountId;
16use super::asset::{FungibleAsset, NonFungibleAsset, TokenSymbol};
17use super::crypto::merkle::MerkleError;
18use super::note::NoteId;
19use super::{MAX_BATCHES_PER_BLOCK, MAX_OUTPUT_NOTES_PER_BATCH, Word};
20use crate::account::{
21 AccountCode,
22 AccountIdPrefix,
23 AccountStorage,
24 AccountType,
25 SlotName,
26 StorageValueName,
27 StorageValueNameError,
28 TemplateTypeError,
29};
30use crate::address::AddressType;
31use crate::asset::AssetVaultKey;
32use crate::batch::BatchId;
33use crate::block::BlockNumber;
34use crate::note::{NoteAssets, NoteExecutionHint, NoteTag, NoteType, Nullifier};
35use crate::transaction::TransactionId;
36use crate::{
37 ACCOUNT_UPDATE_MAX_SIZE,
38 MAX_ACCOUNTS_PER_BATCH,
39 MAX_INPUT_NOTES_PER_BATCH,
40 MAX_INPUT_NOTES_PER_TX,
41 MAX_INPUTS_PER_NOTE,
42 MAX_OUTPUT_NOTES_PER_TX,
43};
44
45#[derive(Debug, Error)]
49pub enum AccountComponentTemplateError {
50 #[error("storage slot name `{0}` is duplicate")]
51 DuplicateEntryNames(StorageValueName),
52 #[error("storage placeholder name `{0}` is duplicate")]
53 DuplicatePlaceholderName(StorageValueName),
54 #[error("slot {0} is defined multiple times")]
55 DuplicateSlot(u8),
56 #[error("storage value name is incorrect: {0}")]
57 IncorrectStorageValueName(#[source] StorageValueNameError),
58 #[error("type `{0}` is not valid for `{1}` slots")]
59 InvalidType(String, String),
60 #[error("error deserializing component metadata: {0}")]
61 MetadataDeserializationError(String),
62 #[error("multi-slot entry should contain as many values as storage slot indices")]
63 MultiSlotArityMismatch,
64 #[error("multi-slot entry slot range should occupy more than one storage slot")]
65 MultiSlotSpansOneSlot,
66 #[error("component storage slots are not contiguous ({0} is followed by {1})")]
67 NonContiguousSlots(u8, u8),
68 #[error("storage value for placeholder `{0}` was not provided in the init storage data")]
69 PlaceholderValueNotProvided(StorageValueName),
70 #[error("error converting value into expected type: ")]
71 StorageValueParsingError(#[source] TemplateTypeError),
72 #[error("storage map contains duplicate keys")]
73 StorageMapHasDuplicateKeys(#[source] Box<dyn Error + Send + Sync + 'static>),
74 #[error("component storage slots have to start at 0, but they start at {0}")]
75 StorageSlotsDoNotStartAtZero(u8),
76 #[cfg(feature = "std")]
77 #[error("error trying to deserialize from toml")]
78 TomlDeserializationError(#[source] toml::de::Error),
79 #[cfg(feature = "std")]
80 #[error("error trying to deserialize from toml")]
81 TomlSerializationError(#[source] toml::ser::Error),
82}
83
84#[derive(Debug, Error)]
88pub enum AccountError {
89 #[error("failed to deserialize account code")]
90 AccountCodeDeserializationError(#[source] DeserializationError),
91 #[error("account code does not contain an auth component")]
92 AccountCodeNoAuthComponent,
93 #[error("account code contains multiple auth components")]
94 AccountCodeMultipleAuthComponents,
95 #[error("account code must contain at least one non-auth procedure")]
96 AccountCodeNoProcedures,
97 #[error("account code contains {0} procedures but it may contain at most {max} procedures", max = AccountCode::MAX_NUM_PROCEDURES)]
98 AccountCodeTooManyProcedures(usize),
99 #[error("account procedure {0}'s storage offset {1} does not fit into u8")]
100 AccountCodeProcedureStorageOffsetTooLarge(Word, Felt),
101 #[error("account procedure {0}'s storage size {1} does not fit into u8")]
102 AccountCodeProcedureStorageSizeTooLarge(Word, Felt),
103 #[error("account procedure {0}'s final two elements must be Felt::ZERO")]
104 AccountCodeProcedureInvalidPadding(Word),
105 #[error("failed to assemble account component:\n{}", PrintDiagnostic::new(.0))]
106 AccountComponentAssemblyError(Report),
107 #[error("failed to merge components into one account code mast forest")]
108 AccountComponentMastForestMergeError(#[source] MastForestError),
109 #[error("procedure with MAST root {0} is present in multiple account components")]
110 AccountComponentDuplicateProcedureRoot(Word),
111 #[error("failed to create account component")]
112 AccountComponentTemplateInstantiationError(#[source] AccountComponentTemplateError),
113 #[error("account component contains multiple authentication procedures")]
114 AccountComponentMultipleAuthProcedures,
115 #[error("failed to update asset vault")]
116 AssetVaultUpdateError(#[source] AssetVaultError),
117 #[error("account build error: {0}")]
118 BuildError(String, #[source] Option<Box<AccountError>>),
119 #[error("failed to parse account ID from final account header")]
120 FinalAccountHeaderIdParsingFailed(#[source] AccountIdError),
121 #[error("account header data has length {actual} but it must be of length {expected}")]
122 HeaderDataIncorrectLength { actual: usize, expected: usize },
123 #[error("active account nonce {current} plus increment {increment} overflows a felt to {new}")]
124 NonceOverflow {
125 current: Felt,
126 increment: Felt,
127 new: Felt,
128 },
129 #[error(
130 "digest of the seed has {actual} trailing zeroes but must have at least {expected} trailing zeroes"
131 )]
132 SeedDigestTooFewTrailingZeros { expected: u32, actual: u32 },
133 #[error("account ID {actual} computed from seed does not match ID {expected} on account")]
134 AccountIdSeedMismatch { actual: AccountId, expected: AccountId },
135 #[error("account ID seed was provided for an existing account")]
136 ExistingAccountWithSeed,
137 #[error("account ID seed was not provided for a new account")]
138 NewAccountMissingSeed,
139 #[error(
140 "an account with a seed cannot be converted into a delta since it represents an unregistered account"
141 )]
142 DeltaFromAccountWithSeed,
143 #[error("seed converts to an invalid account ID")]
144 SeedConvertsToInvalidAccountId(#[source] AccountIdError),
145 #[error("storage map root {0} not found in the account storage")]
146 StorageMapRootNotFound(Word),
147 #[error("storage slot at index {0} is not of type map")]
148 StorageSlotNotMap(u8),
149 #[error("storage slot at index {0} is not of type value")]
150 StorageSlotNotValue(u8),
151 #[error("storage slot index is {index} but the slots length is {slots_len}")]
152 StorageIndexOutOfBounds { slots_len: u8, index: u8 },
153 #[error("number of storage slots is {0} but max possible number is {max}", max = AccountStorage::MAX_NUM_STORAGE_SLOTS)]
154 StorageTooManySlots(u64),
155 #[error("procedure storage offset + size is {0} which exceeds the maximum value of {max}",
156 max = AccountStorage::MAX_NUM_STORAGE_SLOTS
157 )]
158 StorageOffsetPlusSizeOutOfBounds(u16),
159 #[error(
160 "procedure which does not access storage (storage size = 0) has non-zero storage offset"
161 )]
162 PureProcedureWithStorageOffset,
163 #[error(
164 "account component at index {component_index} is incompatible with account of type {account_type}"
165 )]
166 UnsupportedComponentForAccountType {
167 account_type: AccountType,
168 component_index: usize,
169 },
170 #[error(
171 "failed to apply full state delta to existing account; full state deltas can be converted to accounts directly"
172 )]
173 ApplyFullStateDeltaToAccount,
174 #[error("only account deltas representing a full account can be converted to a full account")]
175 PartialStateDeltaToAccount,
176 #[error("maximum number of storage map leaves exceeded")]
177 MaxNumStorageMapLeavesExceeded(#[source] MerkleError),
178 #[error("{error_msg}")]
181 Other {
182 error_msg: Box<str>,
183 source: Option<Box<dyn Error + Send + Sync + 'static>>,
185 },
186}
187
188impl AccountError {
189 pub fn other(message: impl Into<String>) -> Self {
191 let message: String = message.into();
192 Self::Other { error_msg: message.into(), source: None }
193 }
194
195 pub fn other_with_source(
198 message: impl Into<String>,
199 source: impl Error + Send + Sync + 'static,
200 ) -> Self {
201 let message: String = message.into();
202 Self::Other {
203 error_msg: message.into(),
204 source: Some(Box::new(source)),
205 }
206 }
207}
208
209#[derive(Debug, Error)]
213pub enum AccountIdError {
214 #[error("failed to convert bytes into account ID prefix field element")]
215 AccountIdInvalidPrefixFieldElement(#[source] DeserializationError),
216 #[error("failed to convert bytes into account ID suffix field element")]
217 AccountIdInvalidSuffixFieldElement(#[source] DeserializationError),
218 #[error("`{0}` is not a known account storage mode")]
219 UnknownAccountStorageMode(Box<str>),
220 #[error(r#"`{0}` is not a known account type, expected one of "FungibleFaucet", "NonFungibleFaucet", "RegularAccountImmutableCode" or "RegularAccountUpdatableCode""#)]
221 UnknownAccountType(Box<str>),
222 #[error("failed to parse hex string into account ID")]
223 AccountIdHexParseError(#[source] HexParseError),
224 #[error("`{0}` is not a known account ID version")]
225 UnknownAccountIdVersion(u8),
226 #[error("most significant bit of account ID suffix must be zero")]
227 AccountIdSuffixMostSignificantBitMustBeZero,
228 #[error("least significant byte of account ID suffix must be zero")]
229 AccountIdSuffixLeastSignificantByteMustBeZero,
230 #[error("failed to decode bech32 string into account ID")]
231 Bech32DecodeError(#[source] Bech32Error),
232}
233
234#[derive(Debug, Error)]
238pub enum SlotNameError {
239 #[error("slot names must only contain characters a..z, A..Z, 0..9 or underscore")]
240 InvalidCharacter,
241 #[error("slot names must be separated by double colons")]
242 UnexpectedColon,
243 #[error("slot name components must not start with an underscore")]
244 UnexpectedUnderscore,
245 #[error(
246 "slot names must contain at least {} components separated by double colons",
247 SlotName::MIN_NUM_COMPONENTS
248 )]
249 TooShort,
250}
251
252#[derive(Debug, Error)]
256pub enum AccountTreeError {
257 #[error(
258 "account tree contains multiple account IDs that share the same prefix {duplicate_prefix}"
259 )]
260 DuplicateIdPrefix { duplicate_prefix: AccountIdPrefix },
261 #[error(
262 "entries passed to account tree contain multiple state commitments for the same account ID prefix {prefix}"
263 )]
264 DuplicateStateCommitments { prefix: AccountIdPrefix },
265 #[error("untracked account ID {id} used in partial account tree")]
266 UntrackedAccountId { id: AccountId, source: MerkleError },
267 #[error("new tree root after account witness insertion does not match previous tree root")]
268 TreeRootConflict(#[source] MerkleError),
269 #[error("failed to apply mutations to account tree")]
270 ApplyMutations(#[source] MerkleError),
271 #[error("failed to compute account tree mutations")]
272 ComputeMutations(#[source] MerkleError),
273 #[error("smt leaf's index is not a valid account ID prefix")]
274 InvalidAccountIdPrefix(#[source] AccountIdError),
275 #[error("account witness merkle path depth {0} does not match AccountTree::DEPTH")]
276 WitnessMerklePathDepthDoesNotMatchAccountTreeDepth(usize),
277}
278
279#[derive(Debug, Error)]
283pub enum AddressError {
284 #[error("tag length {0} should be {expected} bits for network accounts",
285 expected = NoteTag::DEFAULT_NETWORK_TAG_LENGTH
286 )]
287 CustomTagLengthNotAllowedForNetworkAccounts(u8),
288 #[error("tag length {0} is too large, must be less than or equal to {max}",
289 max = NoteTag::MAX_LOCAL_TAG_LENGTH
290 )]
291 TagLengthTooLarge(u8),
292 #[error("unknown address interface `{0}`")]
293 UnknownAddressInterface(u16),
294 #[error("failed to decode account ID")]
295 AccountIdDecodeError(#[source] AccountIdError),
296 #[error("address separator must not be included without routing parameters")]
297 TrailingSeparator,
298 #[error("failed to decode bech32 string into an address")]
299 Bech32DecodeError(#[source] Bech32Error),
300 #[error("{error_msg}")]
301 DecodeError {
302 error_msg: Box<str>,
303 source: Option<Box<dyn Error + Send + Sync + 'static>>,
305 },
306 #[error("found unknown routing parameter key {0}")]
307 UnknownRoutingParameterKey(u8),
308}
309
310impl AddressError {
311 pub fn decode_error(message: impl Into<String>) -> Self {
313 let message: String = message.into();
314 Self::DecodeError { error_msg: message.into(), source: None }
315 }
316
317 pub fn decode_error_with_source(
320 message: impl Into<String>,
321 source: impl Error + Send + Sync + 'static,
322 ) -> Self {
323 let message: String = message.into();
324 Self::DecodeError {
325 error_msg: message.into(),
326 source: Some(Box::new(source)),
327 }
328 }
329}
330
331#[derive(Debug, Error)]
335pub enum Bech32Error {
336 #[error(transparent)]
337 DecodeError(Box<dyn Error + Send + Sync + 'static>),
338 #[error("found unknown address type {0} which is not the expected {account_addr} account ID address type",
339 account_addr = AddressType::AccountId as u8
340 )]
341 UnknownAddressType(u8),
342 #[error("expected bech32 data to be of length {expected} but it was of length {actual}")]
343 InvalidDataLength { expected: usize, actual: usize },
344}
345
346#[derive(Debug, Error)]
350pub enum NetworkIdError {
351 #[error("failed to parse string into a network ID")]
352 NetworkIdParseError(#[source] Box<dyn Error + Send + Sync + 'static>),
353}
354
355#[derive(Debug, Error)]
359pub enum AccountDeltaError {
360 #[error(
361 "storage slot index {slot_index} is greater than or equal to the number of slots {num_slots}"
362 )]
363 StorageSlotIndexOutOfBounds { slot_index: u8, num_slots: u8 },
364 #[error("storage slot {0} was updated as a value and as a map")]
365 StorageSlotUsedAsDifferentTypes(u8),
366 #[error("non fungible vault can neither be added nor removed twice")]
367 DuplicateNonFungibleVaultUpdate(NonFungibleAsset),
368 #[error(
369 "fungible asset issued by faucet {faucet_id} has delta {delta} which overflows when added to current value {current}"
370 )]
371 FungibleAssetDeltaOverflow {
372 faucet_id: AccountId,
373 current: i64,
374 delta: i64,
375 },
376 #[error(
377 "account update of type `{left_update_type}` cannot be merged with account update of type `{right_update_type}`"
378 )]
379 IncompatibleAccountUpdates {
380 left_update_type: &'static str,
381 right_update_type: &'static str,
382 },
383 #[error("account delta could not be applied to account {account_id}")]
384 AccountDeltaApplicationFailed {
385 account_id: AccountId,
386 source: AccountError,
387 },
388 #[error("non-empty account storage or vault delta with zero nonce delta is not allowed")]
389 NonEmptyStorageOrVaultDeltaWithZeroNonceDelta,
390 #[error(
391 "account nonce increment {current} plus the other nonce increment {increment} overflows a felt to {new}"
392 )]
393 NonceIncrementOverflow {
394 current: Felt,
395 increment: Felt,
396 new: Felt,
397 },
398 #[error("account ID {0} in fungible asset delta is not of type fungible faucet")]
399 NotAFungibleFaucetId(AccountId),
400 #[error("cannot merge two full state deltas")]
401 MergingFullStateDeltas,
402}
403
404#[derive(Debug, Error)]
408pub enum StorageMapError {
409 #[error("map entries contain key {key} twice with values {value0} and {value1}")]
410 DuplicateKey { key: Word, value0: Word, value1: Word },
411 #[error("map key {raw_key} is not present in provided SMT proof")]
412 MissingKey { raw_key: Word },
413}
414
415#[derive(Debug, Error)]
419pub enum BatchAccountUpdateError {
420 #[error(
421 "account update for account {expected_account_id} cannot be merged with update from transaction {transaction} which was executed against account {actual_account_id}"
422 )]
423 AccountUpdateIdMismatch {
424 transaction: TransactionId,
425 expected_account_id: AccountId,
426 actual_account_id: AccountId,
427 },
428 #[error(
429 "final state commitment in account update from transaction {0} does not match initial state of current update"
430 )]
431 AccountUpdateInitialStateMismatch(TransactionId),
432 #[error("failed to merge account delta from transaction {0}")]
433 TransactionUpdateMergeError(TransactionId, #[source] Box<AccountDeltaError>),
434}
435
436#[derive(Debug, Error)]
440pub enum AssetError {
441 #[error(
442 "fungible asset amount {0} exceeds the max allowed amount of {max_amount}",
443 max_amount = FungibleAsset::MAX_AMOUNT
444 )]
445 FungibleAssetAmountTooBig(u64),
446 #[error("subtracting {subtrahend} from fungible asset amount {minuend} would underflow")]
447 FungibleAssetAmountNotSufficient { minuend: u64, subtrahend: u64 },
448 #[error("fungible asset word {0} does not contain expected ZERO at word index 1")]
449 FungibleAssetExpectedZero(Word),
450 #[error(
451 "cannot add fungible asset with issuer {other_issuer} to fungible asset with issuer {original_issuer}"
452 )]
453 FungibleAssetInconsistentFaucetIds {
454 original_issuer: AccountId,
455 other_issuer: AccountId,
456 },
457 #[error("faucet account ID in asset is invalid")]
458 InvalidFaucetAccountId(#[source] Box<dyn Error + Send + Sync + 'static>),
459 #[error("faucet account ID in asset has a non-faucet prefix: {}", .0)]
460 InvalidFaucetAccountIdPrefix(AccountIdPrefix),
461 #[error(
462 "faucet id {0} of type {id_type} must be of type {expected_ty} for fungible assets",
463 id_type = .0.account_type(),
464 expected_ty = AccountType::FungibleFaucet
465 )]
466 FungibleFaucetIdTypeMismatch(AccountId),
467 #[error(
468 "faucet id {0} of type {id_type} must be of type {expected_ty} for non fungible assets",
469 id_type = .0.account_type(),
470 expected_ty = AccountType::NonFungibleFaucet
471 )]
472 NonFungibleFaucetIdTypeMismatch(AccountIdPrefix),
473 #[error("asset vault key {actual} does not match expected asset vault key {expected}")]
474 AssetVaultKeyMismatch { actual: Word, expected: Word },
475}
476
477#[derive(Debug, Error)]
481pub enum TokenSymbolError {
482 #[error("token symbol value {0} cannot exceed {max}", max = TokenSymbol::MAX_ENCODED_VALUE)]
483 ValueTooLarge(u64),
484 #[error("token symbol should have length between 1 and 6 characters, but {0} was provided")]
485 InvalidLength(usize),
486 #[error("token symbol `{0}` contains characters that are not uppercase ASCII")]
487 InvalidCharacter(String),
488 #[error("token symbol data left after decoding the specified number of characters")]
489 DataNotFullyDecoded,
490}
491
492#[derive(Debug, Error)]
496pub enum AssetVaultError {
497 #[error("adding fungible asset amounts would exceed maximum allowed amount")]
498 AddFungibleAssetBalanceError(#[source] AssetError),
499 #[error("provided assets contain duplicates")]
500 DuplicateAsset(#[source] MerkleError),
501 #[error("non fungible asset {0} already exists in the vault")]
502 DuplicateNonFungibleAsset(NonFungibleAsset),
503 #[error("fungible asset {0} does not exist in the vault")]
504 FungibleAssetNotFound(FungibleAsset),
505 #[error("faucet id {0} is not a fungible faucet id")]
506 NotAFungibleFaucetId(AccountId),
507 #[error("non fungible asset {0} does not exist in the vault")]
508 NonFungibleAssetNotFound(NonFungibleAsset),
509 #[error("subtracting fungible asset amounts would underflow")]
510 SubtractFungibleAssetBalanceError(#[source] AssetError),
511 #[error("maximum number of asset vault leaves exceeded")]
512 MaxLeafEntriesExceeded(#[source] MerkleError),
513}
514
515#[derive(Debug, Error)]
519pub enum PartialAssetVaultError {
520 #[error("provided SMT entry {entry} is not a valid asset")]
521 InvalidAssetInSmt { entry: Word, source: AssetError },
522 #[error("expected asset vault key to be {expected} but it was {actual}")]
523 AssetVaultKeyMismatch { expected: AssetVaultKey, actual: Word },
524 #[error("failed to add asset proof")]
525 FailedToAddProof(#[source] MerkleError),
526 #[error("asset is not tracked in the partial vault")]
527 UntrackedAsset(#[source] MerkleError),
528}
529
530#[derive(Debug, Error)]
534pub enum NoteError {
535 #[error("note tag length {0} exceeds the maximum of {max}", max = NoteTag::MAX_LOCAL_TAG_LENGTH)]
536 NoteTagLengthTooLarge(u8),
537 #[error("duplicate fungible asset from issuer {0} in note")]
538 DuplicateFungibleAsset(AccountId),
539 #[error("duplicate non fungible asset {0} in note")]
540 DuplicateNonFungibleAsset(NonFungibleAsset),
541 #[error("note type {0} is inconsistent with note tag {1}")]
542 InconsistentNoteTag(NoteType, u64),
543 #[error("adding fungible asset amounts would exceed maximum allowed amount")]
544 AddFungibleAssetBalanceError(#[source] AssetError),
545 #[error("note sender is not a valid account ID")]
546 NoteSenderInvalidAccountId(#[source] AccountIdError),
547 #[error("note tag use case {0} must be less than 2^{exp}", exp = NoteTag::MAX_USE_CASE_ID_EXPONENT)]
548 NoteTagUseCaseTooLarge(u16),
549 #[error(
550 "note execution hint tag {0} must be in range {from}..={to}",
551 from = NoteExecutionHint::NONE_TAG,
552 to = NoteExecutionHint::ON_BLOCK_SLOT_TAG,
553 )]
554 NoteExecutionHintTagOutOfRange(u8),
555 #[error("note execution hint after block variant cannot contain u32::MAX")]
556 NoteExecutionHintAfterBlockCannotBeU32Max,
557 #[error("invalid note execution hint payload {1} for tag {0}")]
558 InvalidNoteExecutionHintPayload(u8, u32),
559 #[error("note type {0} does not match any of the valid note types {public}, {private} or {encrypted}",
560 public = NoteType::Public,
561 private = NoteType::Private,
562 encrypted = NoteType::Encrypted,
563 )]
564 UnknownNoteType(Box<str>),
565 #[error("note location index {node_index_in_block} is out of bounds 0..={highest_index}")]
566 NoteLocationIndexOutOfBounds {
567 node_index_in_block: u16,
568 highest_index: usize,
569 },
570 #[error("note network execution requires a public note but note is of type {0}")]
571 NetworkExecutionRequiresPublicNote(NoteType),
572 #[error("failed to assemble note script:\n{}", PrintDiagnostic::new(.0))]
573 NoteScriptAssemblyError(Report),
574 #[error("failed to deserialize note script")]
575 NoteScriptDeserializationError(#[source] DeserializationError),
576 #[error("note contains {0} assets which exceeds the maximum of {max}", max = NoteAssets::MAX_NUM_ASSETS)]
577 TooManyAssets(usize),
578 #[error("note contains {0} inputs which exceeds the maximum of {max}", max = MAX_INPUTS_PER_NOTE)]
579 TooManyInputs(usize),
580 #[error("note tag requires a public note but the note is of type {0}")]
581 PublicNoteRequired(NoteType),
582 #[error("{error_msg}")]
583 Other {
584 error_msg: Box<str>,
585 source: Option<Box<dyn Error + Send + Sync + 'static>>,
587 },
588}
589
590impl NoteError {
591 pub fn other(message: impl Into<String>) -> Self {
593 let message: String = message.into();
594 Self::Other { error_msg: message.into(), source: None }
595 }
596
597 pub fn other_with_source(
600 message: impl Into<String>,
601 source: impl Error + Send + Sync + 'static,
602 ) -> Self {
603 let message: String = message.into();
604 Self::Other {
605 error_msg: message.into(),
606 source: Some(Box::new(source)),
607 }
608 }
609}
610
611#[derive(Debug, Error)]
615pub enum PartialBlockchainError {
616 #[error(
617 "block num {block_num} exceeds chain length {chain_length} implied by the partial blockchain"
618 )]
619 BlockNumTooBig {
620 chain_length: usize,
621 block_num: BlockNumber,
622 },
623
624 #[error("duplicate block {block_num} in partial blockchain")]
625 DuplicateBlock { block_num: BlockNumber },
626
627 #[error("partial blockchain does not track authentication paths for block {block_num}")]
628 UntrackedBlock { block_num: BlockNumber },
629
630 #[error(
631 "provided block header with number {block_num} and commitment {block_commitment} is not tracked by partial MMR"
632 )]
633 BlockHeaderCommitmentMismatch {
634 block_num: BlockNumber,
635 block_commitment: Word,
636 source: MmrError,
637 },
638}
639
640impl PartialBlockchainError {
641 pub fn block_num_too_big(chain_length: usize, block_num: BlockNumber) -> Self {
642 Self::BlockNumTooBig { chain_length, block_num }
643 }
644
645 pub fn duplicate_block(block_num: BlockNumber) -> Self {
646 Self::DuplicateBlock { block_num }
647 }
648
649 pub fn untracked_block(block_num: BlockNumber) -> Self {
650 Self::UntrackedBlock { block_num }
651 }
652}
653
654#[derive(Debug, Error)]
658pub enum TransactionScriptError {
659 #[error("failed to assemble transaction script:\n{}", PrintDiagnostic::new(.0))]
660 AssemblyError(Report),
661}
662
663#[derive(Debug, Error)]
667pub enum TransactionInputError {
668 #[error("transaction input note with nullifier {0} is a duplicate")]
669 DuplicateInputNote(Nullifier),
670 #[error("partial blockchain has length {actual} which does not match block number {expected}")]
671 InconsistentChainLength {
672 expected: BlockNumber,
673 actual: BlockNumber,
674 },
675 #[error(
676 "partial blockchain has commitment {actual} which does not match the block header's chain commitment {expected}"
677 )]
678 InconsistentChainCommitment { expected: Word, actual: Word },
679 #[error("block in which input note with id {0} was created is not in partial blockchain")]
680 InputNoteBlockNotInPartialBlockchain(NoteId),
681 #[error("input note with id {0} was not created in block {1}")]
682 InputNoteNotInBlock(NoteId, BlockNumber),
683 #[error(
684 "total number of input notes is {0} which exceeds the maximum of {MAX_INPUT_NOTES_PER_TX}"
685 )]
686 TooManyInputNotes(usize),
687}
688
689#[derive(Debug, Error)]
693pub enum TransactionOutputError {
694 #[error("transaction output note with id {0} is a duplicate")]
695 DuplicateOutputNote(NoteId),
696 #[error("final account commitment is not in the advice map")]
697 FinalAccountCommitmentMissingInAdviceMap,
698 #[error("fee asset is not a fungible asset")]
699 FeeAssetNotFungibleAsset(#[source] AssetError),
700 #[error("failed to parse final account header")]
701 FinalAccountHeaderParseFailure(#[source] AccountError),
702 #[error(
703 "output notes commitment {expected} from kernel does not match computed commitment {actual}"
704 )]
705 OutputNotesCommitmentInconsistent { expected: Word, actual: Word },
706 #[error("transaction kernel output stack is invalid: {0}")]
707 OutputStackInvalid(String),
708 #[error(
709 "total number of output notes is {0} which exceeds the maximum of {MAX_OUTPUT_NOTES_PER_TX}"
710 )]
711 TooManyOutputNotes(usize),
712 #[error("failed to process account update commitment: {0}")]
713 AccountUpdateCommitment(Box<str>),
714}
715
716#[derive(Debug, Error)]
720pub enum ProvenTransactionError {
721 #[error(
722 "proven transaction's final account commitment {tx_final_commitment} and account details commitment {details_commitment} must match"
723 )]
724 AccountFinalCommitmentMismatch {
725 tx_final_commitment: Word,
726 details_commitment: Word,
727 },
728 #[error(
729 "proven transaction's final account ID {tx_account_id} and account details id {details_account_id} must match"
730 )]
731 AccountIdMismatch {
732 tx_account_id: AccountId,
733 details_account_id: AccountId,
734 },
735 #[error("failed to construct input notes for proven transaction")]
736 InputNotesError(TransactionInputError),
737 #[error("private account {0} should not have account details")]
738 PrivateAccountWithDetails(AccountId),
739 #[error("account {0} with public state is missing its account details")]
740 PublicStateAccountMissingDetails(AccountId),
741 #[error("new account {id} with public state must be accompanied by a full state delta")]
742 NewPublicStateAccountRequiresFullStateDelta { id: AccountId, source: AccountError },
743 #[error(
744 "existing account {0} with public state should only provide delta updates instead of full details"
745 )]
746 ExistingPublicStateAccountRequiresDeltaDetails(AccountId),
747 #[error("failed to construct output notes for proven transaction")]
748 OutputNotesError(TransactionOutputError),
749 #[error(
750 "account update of size {update_size} for account {account_id} exceeds maximum update size of {ACCOUNT_UPDATE_MAX_SIZE}"
751 )]
752 AccountUpdateSizeLimitExceeded {
753 account_id: AccountId,
754 update_size: usize,
755 },
756 #[error("proven transaction neither changed the account state, nor consumed any notes")]
757 EmptyTransaction,
758 #[error("failed to validate account delta in transaction account update")]
759 AccountDeltaCommitmentMismatch(#[source] Box<dyn Error + Send + Sync + 'static>),
760}
761
762#[derive(Debug, Error)]
766pub enum ProposedBatchError {
767 #[error(
768 "transaction batch has {0} input notes but at most {MAX_INPUT_NOTES_PER_BATCH} are allowed"
769 )]
770 TooManyInputNotes(usize),
771
772 #[error(
773 "transaction batch has {0} output notes but at most {MAX_OUTPUT_NOTES_PER_BATCH} are allowed"
774 )]
775 TooManyOutputNotes(usize),
776
777 #[error(
778 "transaction batch has {0} account updates but at most {MAX_ACCOUNTS_PER_BATCH} are allowed"
779 )]
780 TooManyAccountUpdates(usize),
781
782 #[error(
783 "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}"
784 )]
785 ExpiredTransaction {
786 transaction_id: TransactionId,
787 transaction_expiration_num: BlockNumber,
788 reference_block_num: BlockNumber,
789 },
790
791 #[error("transaction batch must contain at least one transaction")]
792 EmptyTransactionBatch,
793
794 #[error("transaction {transaction_id} appears twice in the proposed batch input")]
795 DuplicateTransaction { transaction_id: TransactionId },
796
797 #[error(
798 "transaction {second_transaction_id} consumes the note with nullifier {note_nullifier} that is also consumed by another transaction {first_transaction_id} in the batch"
799 )]
800 DuplicateInputNote {
801 note_nullifier: Nullifier,
802 first_transaction_id: TransactionId,
803 second_transaction_id: TransactionId,
804 },
805
806 #[error(
807 "transaction {second_transaction_id} creates the note with id {note_id} that is also created by another transaction {first_transaction_id} in the batch"
808 )]
809 DuplicateOutputNote {
810 note_id: NoteId,
811 first_transaction_id: TransactionId,
812 second_transaction_id: TransactionId,
813 },
814
815 #[error(
816 "note commitment mismatch for note {id}: (input: {input_commitment}, output: {output_commitment})"
817 )]
818 NoteCommitmentMismatch {
819 id: NoteId,
820 input_commitment: Word,
821 output_commitment: Word,
822 },
823
824 #[error("failed to merge transaction delta into account {account_id}")]
825 AccountUpdateError {
826 account_id: AccountId,
827 source: BatchAccountUpdateError,
828 },
829
830 #[error(
831 "unable to prove unauthenticated note inclusion because block {block_number} in which note with id {note_id} was created is not in partial blockchain"
832 )]
833 UnauthenticatedInputNoteBlockNotInPartialBlockchain {
834 block_number: BlockNumber,
835 note_id: NoteId,
836 },
837
838 #[error(
839 "unable to prove unauthenticated note inclusion of note {note_id} in block {block_num}"
840 )]
841 UnauthenticatedNoteAuthenticationFailed {
842 note_id: NoteId,
843 block_num: BlockNumber,
844 source: MerkleError,
845 },
846
847 #[error("partial blockchain has length {actual} which does not match block number {expected}")]
848 InconsistentChainLength {
849 expected: BlockNumber,
850 actual: BlockNumber,
851 },
852
853 #[error(
854 "partial blockchain has root {actual} which does not match block header's root {expected}"
855 )]
856 InconsistentChainRoot { expected: Word, actual: Word },
857
858 #[error(
859 "block {block_reference} referenced by transaction {transaction_id} is not in the partial blockchain"
860 )]
861 MissingTransactionBlockReference {
862 block_reference: Word,
863 transaction_id: TransactionId,
864 },
865}
866
867#[derive(Debug, Error)]
871pub enum ProvenBatchError {
872 #[error("failed to verify transaction {transaction_id} in transaction batch")]
873 TransactionVerificationFailed {
874 transaction_id: TransactionId,
875 source: Box<dyn Error + Send + Sync + 'static>,
876 },
877 #[error(
878 "batch expiration block number {batch_expiration_block_num} is not greater than the reference block number {reference_block_num}"
879 )]
880 InvalidBatchExpirationBlockNum {
881 batch_expiration_block_num: BlockNumber,
882 reference_block_num: BlockNumber,
883 },
884}
885
886#[derive(Debug, Error)]
890pub enum ProposedBlockError {
891 #[error("block must contain at least one transaction batch")]
892 EmptyBlock,
893
894 #[error("block must contain at most {MAX_BATCHES_PER_BLOCK} transaction batches")]
895 TooManyBatches,
896
897 #[error(
898 "batch {batch_id} expired at block {batch_expiration_block_num} but the current block number is {current_block_num}"
899 )]
900 ExpiredBatch {
901 batch_id: BatchId,
902 batch_expiration_block_num: BlockNumber,
903 current_block_num: BlockNumber,
904 },
905
906 #[error("batch {batch_id} appears twice in the block inputs")]
907 DuplicateBatch { batch_id: BatchId },
908
909 #[error(
910 "batch {second_batch_id} consumes the note with nullifier {note_nullifier} that is also consumed by another batch {first_batch_id} in the block"
911 )]
912 DuplicateInputNote {
913 note_nullifier: Nullifier,
914 first_batch_id: BatchId,
915 second_batch_id: BatchId,
916 },
917
918 #[error(
919 "batch {second_batch_id} creates the note with ID {note_id} that is also created by another batch {first_batch_id} in the block"
920 )]
921 DuplicateOutputNote {
922 note_id: NoteId,
923 first_batch_id: BatchId,
924 second_batch_id: BatchId,
925 },
926
927 #[error(
928 "timestamp {provided_timestamp} does not increase monotonically compared to timestamp {previous_timestamp} from the previous block header"
929 )]
930 TimestampDoesNotIncreaseMonotonically {
931 provided_timestamp: u32,
932 previous_timestamp: u32,
933 },
934
935 #[error(
936 "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}"
937 )]
938 ConflictingBatchesUpdateSameAccount {
939 account_id: AccountId,
940 initial_state_commitment: Word,
941 first_batch_id: BatchId,
942 second_batch_id: BatchId,
943 },
944
945 #[error(
946 "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"
947 )]
948 ChainLengthNotEqualToPreviousBlockNumber {
949 chain_length: BlockNumber,
950 prev_block_num: BlockNumber,
951 },
952
953 #[error(
954 "partial blockchain has commitment {chain_commitment} which does not match the chain commitment {prev_block_chain_commitment} of the previous block {prev_block_num}"
955 )]
956 ChainRootNotEqualToPreviousBlockChainCommitment {
957 chain_commitment: Word,
958 prev_block_chain_commitment: Word,
959 prev_block_num: BlockNumber,
960 },
961
962 #[error(
963 "partial blockchain is missing block {reference_block_num} referenced by batch {batch_id} in the block"
964 )]
965 BatchReferenceBlockMissingFromChain {
966 reference_block_num: BlockNumber,
967 batch_id: BatchId,
968 },
969
970 #[error(
971 "note commitment mismatch for note {id}: (input: {input_commitment}, output: {output_commitment})"
972 )]
973 NoteCommitmentMismatch {
974 id: NoteId,
975 input_commitment: Word,
976 output_commitment: Word,
977 },
978
979 #[error(
980 "failed to prove unauthenticated note inclusion because block {block_number} in which note with id {note_id} was created is not in partial blockchain"
981 )]
982 UnauthenticatedInputNoteBlockNotInPartialBlockchain {
983 block_number: BlockNumber,
984 note_id: NoteId,
985 },
986
987 #[error(
988 "failed to prove unauthenticated note inclusion of note {note_id} in block {block_num}"
989 )]
990 UnauthenticatedNoteAuthenticationFailed {
991 note_id: NoteId,
992 block_num: BlockNumber,
993 source: MerkleError,
994 },
995
996 #[error(
997 "unauthenticated note with nullifier {nullifier} was not created in the same block and no inclusion proof to authenticate it was provided"
998 )]
999 UnauthenticatedNoteConsumed { nullifier: Nullifier },
1000
1001 #[error("block inputs do not contain a proof of inclusion for account {0}")]
1002 MissingAccountWitness(AccountId),
1003
1004 #[error(
1005 "account {account_id} with state {state_commitment} cannot transition to any of the remaining states {}",
1006 remaining_state_commitments.iter().map(Word::to_hex).collect::<Vec<_>>().join(", ")
1007 )]
1008 InconsistentAccountStateTransition {
1009 account_id: AccountId,
1010 state_commitment: Word,
1011 remaining_state_commitments: Vec<Word>,
1012 },
1013
1014 #[error("no proof for nullifier {0} was provided")]
1015 NullifierProofMissing(Nullifier),
1016
1017 #[error("note with nullifier {0} is already spent")]
1018 NullifierSpent(Nullifier),
1019
1020 #[error("failed to merge transaction delta into account {account_id}")]
1021 AccountUpdateError {
1022 account_id: AccountId,
1023 source: Box<AccountDeltaError>,
1024 },
1025}
1026
1027#[derive(Debug, Error)]
1031pub enum FeeError {
1032 #[error("native asset of the chain must be a fungible faucet but was of type {account_type}")]
1033 NativeAssetIdNotFungible { account_type: AccountType },
1034}
1035
1036#[derive(Debug, Error)]
1040pub enum NullifierTreeError {
1041 #[error(
1042 "entries passed to nullifier tree contain multiple block numbers for the same nullifier"
1043 )]
1044 DuplicateNullifierBlockNumbers(#[source] MerkleError),
1045
1046 #[error("attempt to mark nullifier {0} as spent but it is already spent")]
1047 NullifierAlreadySpent(Nullifier),
1048
1049 #[error("maximum number of nullifier tree leaves exceeded")]
1050 MaxLeafEntriesExceeded(#[source] MerkleError),
1051
1052 #[error("nullifier {nullifier} is not tracked by the partial nullifier tree")]
1053 UntrackedNullifier {
1054 nullifier: Nullifier,
1055 source: MerkleError,
1056 },
1057
1058 #[error("new tree root after nullifier witness insertion does not match previous tree root")]
1059 TreeRootConflict(#[source] MerkleError),
1060
1061 #[error("failed to compute nulifier tree mutations")]
1062 ComputeMutations(#[source] MerkleError),
1063}
1064
1065#[derive(Debug, Error)]
1069pub enum AuthSchemeError {
1070 #[error("auth scheme identifier `{0}` is not valid")]
1071 InvalidAuthSchemeIdentifier(u8),
1072}