zebra_state/
error.rs

1//! Error types for Zebra's state.
2
3use std::sync::Arc;
4
5use chrono::{DateTime, Utc};
6use thiserror::Error;
7
8use zebra_chain::{
9    amount::{self, NegativeAllowed, NonNegative},
10    block,
11    history_tree::HistoryTreeError,
12    orchard, sapling, sprout, transaction, transparent,
13    value_balance::{ValueBalance, ValueBalanceError},
14    work::difficulty::CompactDifficulty,
15};
16
17use crate::constants::MIN_TRANSPARENT_COINBASE_MATURITY;
18
19/// A wrapper for type erased errors that is itself clonable and implements the
20/// Error trait
21#[derive(Debug, Error, Clone)]
22#[error(transparent)]
23pub struct CloneError {
24    source: Arc<dyn std::error::Error + Send + Sync + 'static>,
25}
26
27impl From<CommitSemanticallyVerifiedError> for CloneError {
28    fn from(source: CommitSemanticallyVerifiedError) -> Self {
29        let source = Arc::new(source);
30        Self { source }
31    }
32}
33
34impl From<BoxError> for CloneError {
35    fn from(source: BoxError) -> Self {
36        let source = Arc::from(source);
37        Self { source }
38    }
39}
40
41/// A boxed [`std::error::Error`].
42pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
43
44/// An error describing the reason a semantically verified block could not be committed to the state.
45#[derive(Debug, Error, PartialEq, Eq)]
46#[error("block is not contextually valid: {}", .0)]
47pub struct CommitSemanticallyVerifiedError(#[from] ValidateContextError);
48
49/// An error describing the reason a block or its descendants could not be reconsidered after
50/// potentially being invalidated from the chain_set.
51#[derive(Debug, Error)]
52pub enum ReconsiderError {
53    #[error("Block with hash {0} was not previously invalidated")]
54    MissingInvalidatedBlock(block::Hash),
55
56    #[error("Parent chain not found for block {0}")]
57    ParentChainNotFound(block::Hash),
58
59    #[error("Invalidated blocks list is empty when it should contain at least one block")]
60    InvalidatedBlocksEmpty,
61
62    #[error("{0}")]
63    ValidationError(#[from] ValidateContextError),
64}
65
66/// An error describing why a block failed contextual validation.
67#[derive(Debug, Error, Clone, PartialEq, Eq)]
68#[non_exhaustive]
69#[allow(missing_docs)]
70pub enum ValidateContextError {
71    #[error("block hash {block_hash} was previously invalidated")]
72    #[non_exhaustive]
73    BlockPreviouslyInvalidated { block_hash: block::Hash },
74
75    #[error("block parent not found in any chain, or not enough blocks in chain")]
76    #[non_exhaustive]
77    NotReadyToBeCommitted,
78
79    #[error("block height {candidate_height:?} is lower than the current finalized height {finalized_tip_height:?}")]
80    #[non_exhaustive]
81    OrphanedBlock {
82        candidate_height: block::Height,
83        finalized_tip_height: block::Height,
84    },
85
86    #[error("block height {candidate_height:?} is not one greater than its parent block's height {parent_height:?}")]
87    #[non_exhaustive]
88    NonSequentialBlock {
89        candidate_height: block::Height,
90        parent_height: block::Height,
91    },
92
93    #[error("block time {candidate_time:?} is less than or equal to the median-time-past for the block {median_time_past:?}")]
94    #[non_exhaustive]
95    TimeTooEarly {
96        candidate_time: DateTime<Utc>,
97        median_time_past: DateTime<Utc>,
98    },
99
100    #[error("block time {candidate_time:?} is greater than the median-time-past for the block plus 90 minutes {block_time_max:?}")]
101    #[non_exhaustive]
102    TimeTooLate {
103        candidate_time: DateTime<Utc>,
104        block_time_max: DateTime<Utc>,
105    },
106
107    #[error("block difficulty threshold {difficulty_threshold:?} is not equal to the expected difficulty for the block {expected_difficulty:?}")]
108    #[non_exhaustive]
109    InvalidDifficultyThreshold {
110        difficulty_threshold: CompactDifficulty,
111        expected_difficulty: CompactDifficulty,
112    },
113
114    #[error("transparent double-spend: {outpoint:?} is spent twice in {location:?}")]
115    #[non_exhaustive]
116    DuplicateTransparentSpend {
117        outpoint: transparent::OutPoint,
118        location: &'static str,
119    },
120
121    #[error("missing transparent output: possible double-spend of {outpoint:?} in {location:?}")]
122    #[non_exhaustive]
123    MissingTransparentOutput {
124        outpoint: transparent::OutPoint,
125        location: &'static str,
126    },
127
128    #[error("out-of-order transparent spend: {outpoint:?} is created by a later transaction in the same block")]
129    #[non_exhaustive]
130    EarlyTransparentSpend { outpoint: transparent::OutPoint },
131
132    #[error(
133        "unshielded transparent coinbase spend: {outpoint:?} \
134         must be spent in a transaction which only has shielded outputs"
135    )]
136    #[non_exhaustive]
137    UnshieldedTransparentCoinbaseSpend { outpoint: transparent::OutPoint },
138
139    #[error(
140        "immature transparent coinbase spend: \
141        attempt to spend {outpoint:?} at {spend_height:?}, \
142        but spends are invalid before {min_spend_height:?}, \
143        which is {MIN_TRANSPARENT_COINBASE_MATURITY:?} blocks \
144        after it was created at {created_height:?}"
145    )]
146    #[non_exhaustive]
147    ImmatureTransparentCoinbaseSpend {
148        outpoint: transparent::OutPoint,
149        spend_height: block::Height,
150        min_spend_height: block::Height,
151        created_height: block::Height,
152    },
153
154    #[error("sprout double-spend: duplicate nullifier: {nullifier:?}, in finalized state: {in_finalized_state:?}")]
155    #[non_exhaustive]
156    DuplicateSproutNullifier {
157        nullifier: sprout::Nullifier,
158        in_finalized_state: bool,
159    },
160
161    #[error("sapling double-spend: duplicate nullifier: {nullifier:?}, in finalized state: {in_finalized_state:?}")]
162    #[non_exhaustive]
163    DuplicateSaplingNullifier {
164        nullifier: sapling::Nullifier,
165        in_finalized_state: bool,
166    },
167
168    #[error("orchard double-spend: duplicate nullifier: {nullifier:?}, in finalized state: {in_finalized_state:?}")]
169    #[non_exhaustive]
170    DuplicateOrchardNullifier {
171        nullifier: orchard::Nullifier,
172        in_finalized_state: bool,
173    },
174
175    #[error(
176        "the remaining value in the transparent transaction value pool MUST be nonnegative:\n\
177         {amount_error:?},\n\
178         {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}"
179    )]
180    #[non_exhaustive]
181    NegativeRemainingTransactionValue {
182        amount_error: amount::Error,
183        height: block::Height,
184        tx_index_in_block: usize,
185        transaction_hash: transaction::Hash,
186    },
187
188    #[error(
189        "error calculating the remaining value in the transaction value pool:\n\
190         {amount_error:?},\n\
191         {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}"
192    )]
193    #[non_exhaustive]
194    CalculateRemainingTransactionValue {
195        amount_error: amount::Error,
196        height: block::Height,
197        tx_index_in_block: usize,
198        transaction_hash: transaction::Hash,
199    },
200
201    #[error(
202        "error calculating value balances for the remaining value in the transaction value pool:\n\
203         {value_balance_error:?},\n\
204         {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}"
205    )]
206    #[non_exhaustive]
207    CalculateTransactionValueBalances {
208        value_balance_error: ValueBalanceError,
209        height: block::Height,
210        tx_index_in_block: usize,
211        transaction_hash: transaction::Hash,
212    },
213
214    #[error(
215        "error calculating the block chain value pool change:\n\
216         {value_balance_error:?},\n\
217         {height:?}, {block_hash:?},\n\
218         transactions: {transaction_count:?}, spent UTXOs: {spent_utxo_count:?}"
219    )]
220    #[non_exhaustive]
221    CalculateBlockChainValueChange {
222        value_balance_error: ValueBalanceError,
223        height: block::Height,
224        block_hash: block::Hash,
225        transaction_count: usize,
226        spent_utxo_count: usize,
227    },
228
229    #[error(
230        "error adding value balances to the chain value pool:\n\
231         {value_balance_error:?},\n\
232         {chain_value_pools:?},\n\
233         {block_value_pool_change:?},\n\
234         {height:?}"
235    )]
236    #[non_exhaustive]
237    AddValuePool {
238        value_balance_error: ValueBalanceError,
239        chain_value_pools: ValueBalance<NonNegative>,
240        block_value_pool_change: ValueBalance<NegativeAllowed>,
241        height: Option<block::Height>,
242    },
243
244    #[error("error updating a note commitment tree: {0}")]
245    NoteCommitmentTreeError(#[from] zebra_chain::parallel::tree::NoteCommitmentTreeError),
246
247    #[error("error building the history tree: {0}")]
248    HistoryTreeError(#[from] Arc<HistoryTreeError>),
249
250    #[error("block contains an invalid commitment: {0}")]
251    InvalidBlockCommitment(#[from] block::CommitmentError),
252
253    #[error(
254        "unknown Sprout anchor: {anchor:?},\n\
255         {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}"
256    )]
257    #[non_exhaustive]
258    UnknownSproutAnchor {
259        anchor: sprout::tree::Root,
260        height: Option<block::Height>,
261        tx_index_in_block: Option<usize>,
262        transaction_hash: transaction::Hash,
263    },
264
265    #[error(
266        "unknown Sapling anchor: {anchor:?},\n\
267         {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}"
268    )]
269    #[non_exhaustive]
270    UnknownSaplingAnchor {
271        anchor: sapling::tree::Root,
272        height: Option<block::Height>,
273        tx_index_in_block: Option<usize>,
274        transaction_hash: transaction::Hash,
275    },
276
277    #[error(
278        "unknown Orchard anchor: {anchor:?},\n\
279         {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}"
280    )]
281    #[non_exhaustive]
282    UnknownOrchardAnchor {
283        anchor: orchard::tree::Root,
284        height: Option<block::Height>,
285        tx_index_in_block: Option<usize>,
286        transaction_hash: transaction::Hash,
287    },
288}
289
290/// Trait for creating the corresponding duplicate nullifier error from a nullifier.
291pub trait DuplicateNullifierError {
292    /// Returns the corresponding duplicate nullifier error for `self`.
293    fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError;
294}
295
296impl DuplicateNullifierError for sprout::Nullifier {
297    fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError {
298        ValidateContextError::DuplicateSproutNullifier {
299            nullifier: *self,
300            in_finalized_state,
301        }
302    }
303}
304
305impl DuplicateNullifierError for sapling::Nullifier {
306    fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError {
307        ValidateContextError::DuplicateSaplingNullifier {
308            nullifier: *self,
309            in_finalized_state,
310        }
311    }
312}
313
314impl DuplicateNullifierError for orchard::Nullifier {
315    fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError {
316        ValidateContextError::DuplicateOrchardNullifier {
317            nullifier: *self,
318            in_finalized_state,
319        }
320    }
321}