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, HashOrHeight, KnownBlock};
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 CommitBlockError {
48 #[error("block hash is a duplicate: already in {location}")]
49 Duplicate {
51 hash_or_height: Option<HashOrHeight>,
53 location: KnownBlock,
55 },
56
57 #[error("could not contextually validate semantically verified block")]
59 ValidateContextError(#[from] Box<ValidateContextError>),
60
61 #[error("block commit task exited. Is Zebra shutting down?")]
63 #[non_exhaustive]
64 WriteTaskExited,
65}
66
67impl CommitBlockError {
68 pub fn is_duplicate_request(&self) -> bool {
71 matches!(self, CommitBlockError::Duplicate { .. })
72 }
73}
74
75#[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#[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#[derive(Debug, Error)]
116#[non_exhaustive]
117pub enum InvalidateError {
118 #[error("cannot invalidate blocks while still committing checkpointed blocks")]
120 ProcessingCheckpointedBlocks,
121
122 #[error("failed to send invalidate block request to block write task")]
124 SendInvalidateRequestFailed,
125
126 #[error("invalidate block request was unexpectedly dropped")]
128 InvalidateRequestDropped,
129
130 #[error("block hash {0} not found in any non-finalized chain")]
132 BlockNotFound(block::Hash),
133}
134
135#[derive(Debug, Error)]
137#[non_exhaustive]
138pub enum ReconsiderError {
139 #[error("Block with hash {0} was not previously invalidated")]
141 MissingInvalidatedBlock(block::Hash),
142
143 #[error("Parent chain not found for block {0}")]
145 ParentChainNotFound(block::Hash),
146
147 #[error("Invalidated blocks list is empty when it should contain at least one block")]
149 InvalidatedBlocksEmpty,
150
151 #[error("cannot reconsider blocks while still committing checkpointed blocks")]
153 CheckpointCommitInProgress,
154
155 #[error("failed to send reconsider block request to block write task")]
157 ReconsiderSendFailed,
158
159 #[error("reconsider block request was unexpectedly dropped")]
161 ReconsiderResponseDropped,
162}
163
164#[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
394pub trait DuplicateNullifierError {
396 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}