Skip to main content

zebra_state/
error.rs

1//! Error types for Zebra's state.
2
3use std::sync::Arc;
4
5use chrono::{DateTime, Utc};
6use derive_new::new;
7use thiserror::Error;
8
9use zebra_chain::{
10    amount::{self, NegativeAllowed, NonNegative},
11    block,
12    history_tree::HistoryTreeError,
13    orchard, sapling, sprout, transaction, transparent,
14    value_balance::{ValueBalance, ValueBalanceError},
15    work::difficulty::CompactDifficulty,
16};
17
18use crate::{constants::MIN_TRANSPARENT_COINBASE_MATURITY, HashOrHeight, KnownBlock};
19
20/// A wrapper for type erased errors that is itself clonable and implements the
21/// Error trait
22#[derive(Debug, Error, Clone)]
23#[error(transparent)]
24pub struct CloneError {
25    source: Arc<dyn std::error::Error + Send + Sync + 'static>,
26}
27
28impl From<CommitSemanticallyVerifiedError> for CloneError {
29    fn from(source: CommitSemanticallyVerifiedError) -> Self {
30        let source = Arc::new(source);
31        Self { source }
32    }
33}
34
35impl From<BoxError> for CloneError {
36    fn from(source: BoxError) -> Self {
37        let source = Arc::from(source);
38        Self { source }
39    }
40}
41
42/// A boxed [`std::error::Error`].
43pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
44
45/// An error describing why a block could not be queued to be committed to the state.
46#[derive(Debug, Error, Clone, PartialEq, Eq, new)]
47pub enum CommitBlockError {
48    #[error("block hash is a duplicate: already in {location}")]
49    /// The block is a duplicate: it is already queued or committed in the state.
50    Duplicate {
51        /// Hash or height of the duplicated block.
52        hash_or_height: Option<HashOrHeight>,
53        /// Location in the state where the block can be found.
54        location: KnownBlock,
55    },
56
57    /// Contextual validation failed.
58    #[error("could not contextually validate semantically verified block")]
59    ValidateContextError(#[from] Box<ValidateContextError>),
60
61    /// The write task exited (likely during shutdown).
62    #[error("block commit task exited. Is Zebra shutting down?")]
63    #[non_exhaustive]
64    WriteTaskExited,
65}
66
67impl CommitBlockError {
68    /// Returns `true` if this is definitely a duplicate commit request.
69    /// Some duplicate requests might not be detected, and therefore return `false`.
70    pub fn is_duplicate_request(&self) -> bool {
71        matches!(self, CommitBlockError::Duplicate { .. })
72    }
73}
74
75/// An error describing why a `CommitSemanticallyVerified` request failed.
76#[derive(Debug, Error, Clone, PartialEq, Eq)]
77#[error("could not commit semantically-verified block")]
78pub struct CommitSemanticallyVerifiedError(#[from] CommitBlockError);
79
80impl From<ValidateContextError> for CommitSemanticallyVerifiedError {
81    fn from(value: ValidateContextError) -> Self {
82        Self(CommitBlockError::ValidateContextError(Box::new(value)))
83    }
84}
85
86#[derive(Debug, Error)]
87pub enum LayeredStateError<E: std::error::Error + std::fmt::Display> {
88    #[error("{0}")]
89    State(E),
90    #[error("{0}")]
91    Layer(BoxError),
92}
93
94impl<E: std::error::Error + 'static> From<BoxError> for LayeredStateError<E> {
95    fn from(err: BoxError) -> Self {
96        match err.downcast::<E>() {
97            Ok(state_err) => Self::State(*state_err),
98            Err(layer_error) => Self::Layer(layer_error),
99        }
100    }
101}
102
103/// An error describing why a `CommitCheckpointVerifiedBlock` request failed.
104#[derive(Debug, Error, Clone)]
105#[error("could not commit checkpoint-verified block")]
106pub struct CommitCheckpointVerifiedError(#[from] CommitBlockError);
107
108impl From<ValidateContextError> for CommitCheckpointVerifiedError {
109    fn from(value: ValidateContextError) -> Self {
110        Self(CommitBlockError::ValidateContextError(Box::new(value)))
111    }
112}
113
114/// An error describing why a `InvalidateBlock` request failed.
115#[derive(Debug, Error)]
116#[non_exhaustive]
117pub enum InvalidateError {
118    /// The state is currently checkpointing blocks and cannot accept invalidation requests.
119    #[error("cannot invalidate blocks while still committing checkpointed blocks")]
120    ProcessingCheckpointedBlocks,
121
122    /// Sending the invalidate request to the block write task failed.
123    #[error("failed to send invalidate block request to block write task")]
124    SendInvalidateRequestFailed,
125
126    /// The invalidate request was dropped before processing.
127    #[error("invalidate block request was unexpectedly dropped")]
128    InvalidateRequestDropped,
129
130    /// The block hash was not found in any non-finalized chain.
131    #[error("block hash {0} not found in any non-finalized chain")]
132    BlockNotFound(block::Hash),
133}
134
135/// An error describing why a `ReconsiderBlock` request failed.
136#[derive(Debug, Error)]
137#[non_exhaustive]
138pub enum ReconsiderError {
139    /// The block is not found in the list of invalidated blocks.
140    #[error("Block with hash {0} was not previously invalidated")]
141    MissingInvalidatedBlock(block::Hash),
142
143    /// The block's parent is missing from the non-finalized state.
144    #[error("Parent chain not found for block {0}")]
145    ParentChainNotFound(block::Hash),
146
147    /// There were no invalidated blocks when at least one was expected.
148    #[error("Invalidated blocks list is empty when it should contain at least one block")]
149    InvalidatedBlocksEmpty,
150
151    /// The state is currently checkpointing blocks and cannot accept reconsider requests.
152    #[error("cannot reconsider blocks while still committing checkpointed blocks")]
153    CheckpointCommitInProgress,
154
155    /// Sending the reconsider request to the block write task failed.
156    #[error("failed to send reconsider block request to block write task")]
157    ReconsiderSendFailed,
158
159    /// The reconsider request was dropped before processing.
160    #[error("reconsider block request was unexpectedly dropped")]
161    ReconsiderResponseDropped,
162}
163
164/// An error describing why a block failed contextual validation.
165#[derive(Debug, Error, Clone, PartialEq, Eq)]
166#[non_exhaustive]
167#[allow(missing_docs)]
168pub enum ValidateContextError {
169    #[error("block hash {block_hash} was previously invalidated")]
170    #[non_exhaustive]
171    BlockPreviouslyInvalidated { block_hash: block::Hash },
172
173    #[error("block parent not found in any chain, or not enough blocks in chain")]
174    #[non_exhaustive]
175    NotReadyToBeCommitted,
176
177    #[error("block height {candidate_height:?} is lower than the current finalized height {finalized_tip_height:?}")]
178    #[non_exhaustive]
179    OrphanedBlock {
180        candidate_height: block::Height,
181        finalized_tip_height: block::Height,
182    },
183
184    #[error("block height {candidate_height:?} is not one greater than its parent block's height {parent_height:?}")]
185    #[non_exhaustive]
186    NonSequentialBlock {
187        candidate_height: block::Height,
188        parent_height: block::Height,
189    },
190
191    #[error("block time {candidate_time:?} is less than or equal to the median-time-past for the block {median_time_past:?}")]
192    #[non_exhaustive]
193    TimeTooEarly {
194        candidate_time: DateTime<Utc>,
195        median_time_past: DateTime<Utc>,
196    },
197
198    #[error("block time {candidate_time:?} is greater than the median-time-past for the block plus 90 minutes {block_time_max:?}")]
199    #[non_exhaustive]
200    TimeTooLate {
201        candidate_time: DateTime<Utc>,
202        block_time_max: DateTime<Utc>,
203    },
204
205    #[error("block difficulty threshold {difficulty_threshold:?} is not equal to the expected difficulty for the block {expected_difficulty:?}")]
206    #[non_exhaustive]
207    InvalidDifficultyThreshold {
208        difficulty_threshold: CompactDifficulty,
209        expected_difficulty: CompactDifficulty,
210    },
211
212    #[error("transparent double-spend: {outpoint:?} is spent twice in {location:?}")]
213    #[non_exhaustive]
214    DuplicateTransparentSpend {
215        outpoint: transparent::OutPoint,
216        location: &'static str,
217    },
218
219    #[error("missing transparent output: possible double-spend of {outpoint:?} in {location:?}")]
220    #[non_exhaustive]
221    MissingTransparentOutput {
222        outpoint: transparent::OutPoint,
223        location: &'static str,
224    },
225
226    #[error("out-of-order transparent spend: {outpoint:?} is created by a later transaction in the same block")]
227    #[non_exhaustive]
228    EarlyTransparentSpend { outpoint: transparent::OutPoint },
229
230    #[error(
231        "unshielded transparent coinbase spend: {outpoint:?} \
232         must be spent in a transaction which only has shielded outputs"
233    )]
234    #[non_exhaustive]
235    UnshieldedTransparentCoinbaseSpend { outpoint: transparent::OutPoint },
236
237    #[error(
238        "immature transparent coinbase spend: \
239        attempt to spend {outpoint:?} at {spend_height:?}, \
240        but spends are invalid before {min_spend_height:?}, \
241        which is {MIN_TRANSPARENT_COINBASE_MATURITY:?} blocks \
242        after it was created at {created_height:?}"
243    )]
244    #[non_exhaustive]
245    ImmatureTransparentCoinbaseSpend {
246        outpoint: transparent::OutPoint,
247        spend_height: block::Height,
248        min_spend_height: block::Height,
249        created_height: block::Height,
250    },
251
252    #[error("sprout double-spend: duplicate nullifier: {nullifier:?}, in finalized state: {in_finalized_state:?}")]
253    #[non_exhaustive]
254    DuplicateSproutNullifier {
255        nullifier: sprout::Nullifier,
256        in_finalized_state: bool,
257    },
258
259    #[error("sapling double-spend: duplicate nullifier: {nullifier:?}, in finalized state: {in_finalized_state:?}")]
260    #[non_exhaustive]
261    DuplicateSaplingNullifier {
262        nullifier: sapling::Nullifier,
263        in_finalized_state: bool,
264    },
265
266    #[error("orchard double-spend: duplicate nullifier: {nullifier:?}, in finalized state: {in_finalized_state:?}")]
267    #[non_exhaustive]
268    DuplicateOrchardNullifier {
269        nullifier: orchard::Nullifier,
270        in_finalized_state: bool,
271    },
272
273    #[error(
274        "the remaining value in the transparent transaction value pool MUST be nonnegative:\n\
275         {amount_error:?},\n\
276         {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}"
277    )]
278    #[non_exhaustive]
279    NegativeRemainingTransactionValue {
280        amount_error: amount::Error,
281        height: block::Height,
282        tx_index_in_block: usize,
283        transaction_hash: transaction::Hash,
284    },
285
286    #[error(
287        "error calculating the remaining value in the transaction value pool:\n\
288         {amount_error:?},\n\
289         {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}"
290    )]
291    #[non_exhaustive]
292    CalculateRemainingTransactionValue {
293        amount_error: amount::Error,
294        height: block::Height,
295        tx_index_in_block: usize,
296        transaction_hash: transaction::Hash,
297    },
298
299    #[error(
300        "error calculating value balances for the remaining value in the transaction value pool:\n\
301         {value_balance_error:?},\n\
302         {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}"
303    )]
304    #[non_exhaustive]
305    CalculateTransactionValueBalances {
306        value_balance_error: ValueBalanceError,
307        height: block::Height,
308        tx_index_in_block: usize,
309        transaction_hash: transaction::Hash,
310    },
311
312    #[error(
313        "error calculating the block chain value pool change:\n\
314         {value_balance_error:?},\n\
315         {height:?}, {block_hash:?},\n\
316         transactions: {transaction_count:?}, spent UTXOs: {spent_utxo_count:?}"
317    )]
318    #[non_exhaustive]
319    CalculateBlockChainValueChange {
320        value_balance_error: ValueBalanceError,
321        height: block::Height,
322        block_hash: block::Hash,
323        transaction_count: usize,
324        spent_utxo_count: usize,
325    },
326
327    #[error(
328        "error adding value balances to the chain value pool:\n\
329         {value_balance_error:?},\n\
330         {chain_value_pools:?},\n\
331         {block_value_pool_change:?},\n\
332         {height:?}"
333    )]
334    #[non_exhaustive]
335    AddValuePool {
336        value_balance_error: ValueBalanceError,
337        chain_value_pools: Box<ValueBalance<NonNegative>>,
338        block_value_pool_change: Box<ValueBalance<NegativeAllowed>>,
339        height: Option<block::Height>,
340    },
341
342    #[error("error updating a note commitment tree: {0}")]
343    NoteCommitmentTreeError(#[from] zebra_chain::parallel::tree::NoteCommitmentTreeError),
344
345    #[error("error building the history tree: {0}")]
346    HistoryTreeError(#[from] Arc<HistoryTreeError>),
347
348    #[error("block contains an invalid commitment: {0}")]
349    InvalidBlockCommitment(#[from] block::CommitmentError),
350
351    #[error(
352        "unknown Sprout anchor: {anchor:?},\n\
353         {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}"
354    )]
355    #[non_exhaustive]
356    UnknownSproutAnchor {
357        anchor: sprout::tree::Root,
358        height: Option<block::Height>,
359        tx_index_in_block: Option<usize>,
360        transaction_hash: transaction::Hash,
361    },
362
363    #[error(
364        "unknown Sapling anchor: {anchor:?},\n\
365         {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}"
366    )]
367    #[non_exhaustive]
368    UnknownSaplingAnchor {
369        anchor: sapling::tree::Root,
370        height: Option<block::Height>,
371        tx_index_in_block: Option<usize>,
372        transaction_hash: transaction::Hash,
373    },
374
375    #[error(
376        "unknown Orchard anchor: {anchor:?},\n\
377         {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}"
378    )]
379    #[non_exhaustive]
380    UnknownOrchardAnchor {
381        anchor: orchard::tree::Root,
382        height: Option<block::Height>,
383        tx_index_in_block: Option<usize>,
384        transaction_hash: transaction::Hash,
385    },
386}
387
388impl From<sprout::tree::NoteCommitmentTreeError> for ValidateContextError {
389    fn from(value: sprout::tree::NoteCommitmentTreeError) -> Self {
390        ValidateContextError::NoteCommitmentTreeError(value.into())
391    }
392}
393
394/// Trait for creating the corresponding duplicate nullifier error from a nullifier.
395pub trait DuplicateNullifierError {
396    /// Returns the corresponding duplicate nullifier error for `self`.
397    fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError;
398}
399
400impl DuplicateNullifierError for sprout::Nullifier {
401    fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError {
402        ValidateContextError::DuplicateSproutNullifier {
403            nullifier: *self,
404            in_finalized_state,
405        }
406    }
407}
408
409impl DuplicateNullifierError for sapling::Nullifier {
410    fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError {
411        ValidateContextError::DuplicateSaplingNullifier {
412            nullifier: *self,
413            in_finalized_state,
414        }
415    }
416}
417
418impl DuplicateNullifierError for orchard::Nullifier {
419    fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError {
420        ValidateContextError::DuplicateOrchardNullifier {
421            nullifier: *self,
422            in_finalized_state,
423        }
424    }
425}