use std::sync::Arc; use chrono::{DateTime, Utc}; use thiserror::Error; use zebra_chain::{ block, orchard, sapling, sprout, transparent, 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: CommitBlockError) -> 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 block could not be committed to the state. #[derive(Debug, Error, PartialEq, Eq)] #[error("block is not contextually valid")] pub struct CommitBlockError(#[from] ValidateContextError); /// An error describing why a block failed contextual validation. #[derive(Debug, Error, PartialEq, Eq)] #[non_exhaustive] #[allow(missing_docs)] pub enum ValidateContextError { #[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("remaining value in the transparent transaction value pool MUST be nonnegative: {transaction_hash:?}, in finalized state: {in_finalized_state:?}")] #[non_exhaustive] InvalidRemainingTransparentValue { transaction_hash: zebra_chain::transaction::Hash, in_finalized_state: bool, }, #[error("error in Sapling note commitment tree")] SaplingNoteCommitmentTreeError(#[from] zebra_chain::sapling::tree::NoteCommitmentTreeError), #[error("error in Orchard note commitment tree")] OrchardNoteCommitmentTreeError(#[from] zebra_chain::orchard::tree::NoteCommitmentTreeError), } /// Trait for creating the corresponding duplicate nullifier error from a nullifier. pub(crate) 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, } } }