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