1use 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#[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
42pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
44
45#[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#[derive(Debug, Error, Clone, PartialEq, Eq)]
79#[non_exhaustive]
80pub enum CommitSemanticallyVerifiedError {
81 #[error("could not queue and commit semantically verified block")]
83 QueueAndCommitError(#[from] QueueAndCommitError),
84 #[error("could not contextually validate semantically verified block")]
86 ValidateContextError(#[from] ValidateContextError),
87 #[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#[derive(Debug, Error)]
111#[non_exhaustive]
112pub enum InvalidateError {
113 #[error("cannot invalidate blocks while still committing checkpointed blocks")]
115 ProcessingCheckpointedBlocks,
116
117 #[error("failed to send invalidate block request to block write task")]
119 SendInvalidateRequestFailed,
120
121 #[error("invalidate block request was unexpectedly dropped")]
123 InvalidateRequestDropped,
124
125 #[error("block hash {0} not found in any non-finalized chain")]
127 BlockNotFound(block::Hash),
128}
129
130#[derive(Debug, Error)]
132#[non_exhaustive]
133pub enum ReconsiderError {
134 #[error("Block with hash {0} was not previously invalidated")]
136 MissingInvalidatedBlock(block::Hash),
137
138 #[error("Parent chain not found for block {0}")]
140 ParentChainNotFound(block::Hash),
141
142 #[error("Invalidated blocks list is empty when it should contain at least one block")]
144 InvalidatedBlocksEmpty,
145
146 #[error("cannot reconsider blocks while still committing checkpointed blocks")]
148 CheckpointCommitInProgress,
149
150 #[error("failed to send reconsider block request to block write task")]
152 ReconsiderSendFailed,
153
154 #[error("reconsider block request was unexpectedly dropped")]
156 ReconsiderResponseDropped,
157}
158
159#[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
389pub trait DuplicateNullifierError {
391 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}