//! Error types for Zebra's state. use std::sync::Arc; use chrono::{DateTime, Utc}; use thiserror::Error; use zebra_chain::{ amount::{self, NegativeAllowed, NonNegative}, block, history_tree::HistoryTreeError, orchard, sapling, sprout, transaction, transparent, value_balance::{ValueBalance, ValueBalanceError}, work::difficulty::CompactDifficulty, }; use crate::constants::MIN_TRANSPARENT_COINBASE_MATURITY; /// A wrapper for type erased errors that is itself clonable and implements the /// Error trait #[derive(Debug, Error, Clone)] #[error(transparent)] pub struct CloneError { source: Arc, } impl From for CloneError { fn from(source: CommitSemanticallyVerifiedError) -> Self { let source = Arc::new(source); Self { source } } } impl From for CloneError { fn from(source: BoxError) -> Self { let source = Arc::from(source); Self { source } } } /// A boxed [`std::error::Error`]. pub type BoxError = Box; /// An error describing the reason a semantically verified block could not be committed to the state. #[derive(Debug, Error, PartialEq, Eq)] #[error("block is not contextually valid: {}", .0)] pub struct CommitSemanticallyVerifiedError(#[from] ValidateContextError); /// An error describing why a block failed contextual validation. #[derive(Debug, Error, Clone, PartialEq, Eq)] #[non_exhaustive] #[allow(missing_docs)] pub enum ValidateContextError { #[error("block parent not found in any chain")] #[non_exhaustive] NotReadyToBeCommitted, #[error("block height {candidate_height:?} is lower than the current finalized height {finalized_tip_height:?}")] #[non_exhaustive] OrphanedBlock { candidate_height: block::Height, finalized_tip_height: block::Height, }, #[error("block height {candidate_height:?} is not one greater than its parent block's height {parent_height:?}")] #[non_exhaustive] NonSequentialBlock { candidate_height: block::Height, parent_height: block::Height, }, #[error("block time {candidate_time:?} is less than or equal to the median-time-past for the block {median_time_past:?}")] #[non_exhaustive] TimeTooEarly { candidate_time: DateTime, median_time_past: DateTime, }, #[error("block time {candidate_time:?} is greater than the median-time-past for the block plus 90 minutes {block_time_max:?}")] #[non_exhaustive] TimeTooLate { candidate_time: DateTime, block_time_max: DateTime, }, #[error("block difficulty threshold {difficulty_threshold:?} is not equal to the expected difficulty for the block {expected_difficulty:?}")] #[non_exhaustive] InvalidDifficultyThreshold { difficulty_threshold: CompactDifficulty, expected_difficulty: CompactDifficulty, }, #[error("transparent double-spend: {outpoint:?} is spent twice in {location:?}")] #[non_exhaustive] DuplicateTransparentSpend { outpoint: transparent::OutPoint, location: &'static str, }, #[error("missing transparent output: possible double-spend of {outpoint:?} in {location:?}")] #[non_exhaustive] MissingTransparentOutput { outpoint: transparent::OutPoint, location: &'static str, }, #[error("out-of-order transparent spend: {outpoint:?} is created by a later transaction in the same block")] #[non_exhaustive] EarlyTransparentSpend { outpoint: transparent::OutPoint }, #[error( "unshielded transparent coinbase spend: {outpoint:?} \ must be spent in a transaction which only has shielded outputs" )] #[non_exhaustive] UnshieldedTransparentCoinbaseSpend { outpoint: transparent::OutPoint }, #[error( "immature transparent coinbase spend: \ attempt to spend {outpoint:?} at {spend_height:?}, \ but spends are invalid before {min_spend_height:?}, \ which is {MIN_TRANSPARENT_COINBASE_MATURITY:?} blocks \ after it was created at {created_height:?}" )] #[non_exhaustive] ImmatureTransparentCoinbaseSpend { outpoint: transparent::OutPoint, spend_height: block::Height, min_spend_height: block::Height, created_height: block::Height, }, #[error("sprout double-spend: duplicate nullifier: {nullifier:?}, in finalized state: {in_finalized_state:?}")] #[non_exhaustive] DuplicateSproutNullifier { nullifier: sprout::Nullifier, in_finalized_state: bool, }, #[error("sapling double-spend: duplicate nullifier: {nullifier:?}, in finalized state: {in_finalized_state:?}")] #[non_exhaustive] DuplicateSaplingNullifier { nullifier: sapling::Nullifier, in_finalized_state: bool, }, #[error("orchard double-spend: duplicate nullifier: {nullifier:?}, in finalized state: {in_finalized_state:?}")] #[non_exhaustive] DuplicateOrchardNullifier { nullifier: orchard::Nullifier, in_finalized_state: bool, }, #[error( "the remaining value in the transparent transaction value pool MUST be nonnegative:\n\ {amount_error:?},\n\ {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}" )] #[non_exhaustive] NegativeRemainingTransactionValue { amount_error: amount::Error, height: block::Height, tx_index_in_block: usize, transaction_hash: transaction::Hash, }, #[error( "error calculating the remaining value in the transaction value pool:\n\ {amount_error:?},\n\ {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}" )] #[non_exhaustive] CalculateRemainingTransactionValue { amount_error: amount::Error, height: block::Height, tx_index_in_block: usize, transaction_hash: transaction::Hash, }, #[error( "error calculating value balances for the remaining value in the transaction value pool:\n\ {value_balance_error:?},\n\ {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}" )] #[non_exhaustive] CalculateTransactionValueBalances { value_balance_error: ValueBalanceError, height: block::Height, tx_index_in_block: usize, transaction_hash: transaction::Hash, }, #[error( "error calculating the block chain value pool change:\n\ {value_balance_error:?},\n\ {height:?}, {block_hash:?},\n\ transactions: {transaction_count:?}, spent UTXOs: {spent_utxo_count:?}" )] #[non_exhaustive] CalculateBlockChainValueChange { value_balance_error: ValueBalanceError, height: block::Height, block_hash: block::Hash, transaction_count: usize, spent_utxo_count: usize, }, #[error( "error adding value balances to the chain value pool:\n\ {value_balance_error:?},\n\ {chain_value_pools:?},\n\ {block_value_pool_change:?},\n\ {height:?}" )] #[non_exhaustive] AddValuePool { value_balance_error: ValueBalanceError, chain_value_pools: ValueBalance, block_value_pool_change: ValueBalance, height: Option, }, #[error("error updating a note commitment tree")] NoteCommitmentTreeError(#[from] zebra_chain::parallel::tree::NoteCommitmentTreeError), #[error("error building the history tree")] HistoryTreeError(#[from] Arc), #[error("block contains an invalid commitment")] InvalidBlockCommitment(#[from] block::CommitmentError), #[error( "unknown Sprout anchor: {anchor:?},\n\ {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}" )] #[non_exhaustive] UnknownSproutAnchor { anchor: sprout::tree::Root, height: Option, tx_index_in_block: Option, transaction_hash: transaction::Hash, }, #[error( "unknown Sapling anchor: {anchor:?},\n\ {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}" )] #[non_exhaustive] UnknownSaplingAnchor { anchor: sapling::tree::Root, height: Option, tx_index_in_block: Option, transaction_hash: transaction::Hash, }, #[error( "unknown Orchard anchor: {anchor:?},\n\ {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}" )] #[non_exhaustive] UnknownOrchardAnchor { anchor: orchard::tree::Root, height: Option, tx_index_in_block: Option, transaction_hash: transaction::Hash, }, } /// Trait for creating the corresponding duplicate nullifier error from a nullifier. pub trait DuplicateNullifierError { /// Returns the corresponding duplicate nullifier error for `self`. fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError; } impl DuplicateNullifierError for sprout::Nullifier { fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError { ValidateContextError::DuplicateSproutNullifier { nullifier: *self, in_finalized_state, } } } impl DuplicateNullifierError for sapling::Nullifier { fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError { ValidateContextError::DuplicateSaplingNullifier { nullifier: *self, in_finalized_state, } } } impl DuplicateNullifierError for orchard::Nullifier { fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError { ValidateContextError::DuplicateOrchardNullifier { nullifier: *self, in_finalized_state, } } }