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 pub fn misbehavior_score(&self) -> u32 {
76 0
77 }
78}
79
80#[derive(Debug, Error, Clone, PartialEq, Eq)]
82#[error("could not commit semantically-verified block")]
83pub struct CommitSemanticallyVerifiedError(#[from] CommitBlockError);
84
85impl From<ValidateContextError> for CommitSemanticallyVerifiedError {
86 fn from(value: ValidateContextError) -> Self {
87 Self(CommitBlockError::ValidateContextError(Box::new(value)))
88 }
89}
90
91#[derive(Debug, Error)]
92pub enum LayeredStateError<E: std::error::Error + std::fmt::Display> {
93 #[error("{0}")]
94 State(E),
95 #[error("{0}")]
96 Layer(BoxError),
97}
98
99impl<E: std::error::Error + 'static> From<BoxError> for LayeredStateError<E> {
100 fn from(err: BoxError) -> Self {
101 match err.downcast::<E>() {
102 Ok(state_err) => Self::State(*state_err),
103 Err(layer_error) => Self::Layer(layer_error),
104 }
105 }
106}
107
108#[derive(Debug, Error, Clone)]
110#[error("could not commit checkpoint-verified block")]
111pub struct CommitCheckpointVerifiedError(#[from] CommitBlockError);
112
113impl From<ValidateContextError> for CommitCheckpointVerifiedError {
114 fn from(value: ValidateContextError) -> Self {
115 Self(CommitBlockError::ValidateContextError(Box::new(value)))
116 }
117}
118
119#[derive(Debug, Error)]
121#[non_exhaustive]
122pub enum InvalidateError {
123 #[error("cannot invalidate blocks while still committing checkpointed blocks")]
125 ProcessingCheckpointedBlocks,
126
127 #[error("failed to send invalidate block request to block write task")]
129 SendInvalidateRequestFailed,
130
131 #[error("invalidate block request was unexpectedly dropped")]
133 InvalidateRequestDropped,
134
135 #[error("block hash {0} not found in any non-finalized chain")]
137 BlockNotFound(block::Hash),
138}
139
140#[derive(Debug, Error)]
142#[non_exhaustive]
143pub enum ReconsiderError {
144 #[error("Block with hash {0} was not previously invalidated")]
146 MissingInvalidatedBlock(block::Hash),
147
148 #[error("Parent chain not found for block {0}")]
150 ParentChainNotFound(block::Hash),
151
152 #[error("Invalidated blocks list is empty when it should contain at least one block")]
154 InvalidatedBlocksEmpty,
155
156 #[error("cannot reconsider blocks while still committing checkpointed blocks")]
158 CheckpointCommitInProgress,
159
160 #[error("failed to send reconsider block request to block write task")]
162 ReconsiderSendFailed,
163
164 #[error("reconsider block request was unexpectedly dropped")]
166 ReconsiderResponseDropped,
167}
168
169#[derive(Debug, Error, Clone, PartialEq, Eq)]
171#[non_exhaustive]
172#[allow(missing_docs)]
173pub enum ValidateContextError {
174 #[error("block hash {block_hash} was previously invalidated")]
175 #[non_exhaustive]
176 BlockPreviouslyInvalidated { block_hash: block::Hash },
177
178 #[error("block parent not found in any chain, or not enough blocks in chain")]
179 #[non_exhaustive]
180 NotReadyToBeCommitted,
181
182 #[error("block height {candidate_height:?} is lower than the current finalized height {finalized_tip_height:?}")]
183 #[non_exhaustive]
184 OrphanedBlock {
185 candidate_height: block::Height,
186 finalized_tip_height: block::Height,
187 },
188
189 #[error("block height {candidate_height:?} is not one greater than its parent block's height {parent_height:?}")]
190 #[non_exhaustive]
191 NonSequentialBlock {
192 candidate_height: block::Height,
193 parent_height: block::Height,
194 },
195
196 #[error("block time {candidate_time:?} is less than or equal to the median-time-past for the block {median_time_past:?}")]
197 #[non_exhaustive]
198 TimeTooEarly {
199 candidate_time: DateTime<Utc>,
200 median_time_past: DateTime<Utc>,
201 },
202
203 #[error("block time {candidate_time:?} is greater than the median-time-past for the block plus 90 minutes {block_time_max:?}")]
204 #[non_exhaustive]
205 TimeTooLate {
206 candidate_time: DateTime<Utc>,
207 block_time_max: DateTime<Utc>,
208 },
209
210 #[error("block difficulty threshold {difficulty_threshold:?} is not equal to the expected difficulty for the block {expected_difficulty:?}")]
211 #[non_exhaustive]
212 InvalidDifficultyThreshold {
213 difficulty_threshold: CompactDifficulty,
214 expected_difficulty: CompactDifficulty,
215 },
216
217 #[error("transparent double-spend: {outpoint:?} is spent twice in {location:?}")]
218 #[non_exhaustive]
219 DuplicateTransparentSpend {
220 outpoint: transparent::OutPoint,
221 location: &'static str,
222 },
223
224 #[error("missing transparent output: possible double-spend of {outpoint:?} in {location:?}")]
225 #[non_exhaustive]
226 MissingTransparentOutput {
227 outpoint: transparent::OutPoint,
228 location: &'static str,
229 },
230
231 #[error("out-of-order transparent spend: {outpoint:?} is created by a later transaction in the same block")]
232 #[non_exhaustive]
233 EarlyTransparentSpend { outpoint: transparent::OutPoint },
234
235 #[error(
236 "unshielded transparent coinbase spend: {outpoint:?} \
237 must be spent in a transaction which only has shielded outputs"
238 )]
239 #[non_exhaustive]
240 UnshieldedTransparentCoinbaseSpend { outpoint: transparent::OutPoint },
241
242 #[error(
243 "immature transparent coinbase spend: \
244 attempt to spend {outpoint:?} at {spend_height:?}, \
245 but spends are invalid before {min_spend_height:?}, \
246 which is {MIN_TRANSPARENT_COINBASE_MATURITY:?} blocks \
247 after it was created at {created_height:?}"
248 )]
249 #[non_exhaustive]
250 ImmatureTransparentCoinbaseSpend {
251 outpoint: transparent::OutPoint,
252 spend_height: block::Height,
253 min_spend_height: block::Height,
254 created_height: block::Height,
255 },
256
257 #[error("sprout double-spend: duplicate nullifier: {nullifier:?}, in finalized state: {in_finalized_state:?}")]
258 #[non_exhaustive]
259 DuplicateSproutNullifier {
260 nullifier: sprout::Nullifier,
261 in_finalized_state: bool,
262 },
263
264 #[error("sapling double-spend: duplicate nullifier: {nullifier:?}, in finalized state: {in_finalized_state:?}")]
265 #[non_exhaustive]
266 DuplicateSaplingNullifier {
267 nullifier: sapling::Nullifier,
268 in_finalized_state: bool,
269 },
270
271 #[error("orchard double-spend: duplicate nullifier: {nullifier:?}, in finalized state: {in_finalized_state:?}")]
272 #[non_exhaustive]
273 DuplicateOrchardNullifier {
274 nullifier: orchard::Nullifier,
275 in_finalized_state: bool,
276 },
277
278 #[error(
279 "the remaining value in the transparent transaction value pool MUST be nonnegative:\n\
280 {amount_error:?},\n\
281 {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}"
282 )]
283 #[non_exhaustive]
284 NegativeRemainingTransactionValue {
285 amount_error: amount::Error,
286 height: block::Height,
287 tx_index_in_block: usize,
288 transaction_hash: transaction::Hash,
289 },
290
291 #[error(
292 "error calculating the remaining value in the transaction value pool:\n\
293 {amount_error:?},\n\
294 {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}"
295 )]
296 #[non_exhaustive]
297 CalculateRemainingTransactionValue {
298 amount_error: amount::Error,
299 height: block::Height,
300 tx_index_in_block: usize,
301 transaction_hash: transaction::Hash,
302 },
303
304 #[error(
305 "error calculating value balances for the remaining value in the transaction value pool:\n\
306 {value_balance_error:?},\n\
307 {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}"
308 )]
309 #[non_exhaustive]
310 CalculateTransactionValueBalances {
311 value_balance_error: ValueBalanceError,
312 height: block::Height,
313 tx_index_in_block: usize,
314 transaction_hash: transaction::Hash,
315 },
316
317 #[error(
318 "error calculating the block chain value pool change:\n\
319 {value_balance_error:?},\n\
320 {height:?}, {block_hash:?},\n\
321 transactions: {transaction_count:?}, spent UTXOs: {spent_utxo_count:?}"
322 )]
323 #[non_exhaustive]
324 CalculateBlockChainValueChange {
325 value_balance_error: ValueBalanceError,
326 height: block::Height,
327 block_hash: block::Hash,
328 transaction_count: usize,
329 spent_utxo_count: usize,
330 },
331
332 #[error(
333 "error adding value balances to the chain value pool:\n\
334 {value_balance_error:?},\n\
335 {chain_value_pools:?},\n\
336 {block_value_pool_change:?},\n\
337 {height:?}"
338 )]
339 #[non_exhaustive]
340 AddValuePool {
341 value_balance_error: ValueBalanceError,
342 chain_value_pools: Box<ValueBalance<NonNegative>>,
343 block_value_pool_change: Box<ValueBalance<NegativeAllowed>>,
344 height: Option<block::Height>,
345 },
346
347 #[error("error updating a note commitment tree: {0}")]
348 NoteCommitmentTreeError(#[from] zebra_chain::parallel::tree::NoteCommitmentTreeError),
349
350 #[error("error building the history tree: {0}")]
351 HistoryTreeError(#[from] Arc<HistoryTreeError>),
352
353 #[error("block contains an invalid commitment: {0}")]
354 InvalidBlockCommitment(#[from] block::CommitmentError),
355
356 #[error(
357 "unknown Sprout anchor: {anchor:?},\n\
358 {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}"
359 )]
360 #[non_exhaustive]
361 UnknownSproutAnchor {
362 anchor: sprout::tree::Root,
363 height: Option<block::Height>,
364 tx_index_in_block: Option<usize>,
365 transaction_hash: transaction::Hash,
366 },
367
368 #[error(
369 "unknown Sapling anchor: {anchor:?},\n\
370 {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}"
371 )]
372 #[non_exhaustive]
373 UnknownSaplingAnchor {
374 anchor: sapling::tree::Root,
375 height: Option<block::Height>,
376 tx_index_in_block: Option<usize>,
377 transaction_hash: transaction::Hash,
378 },
379
380 #[error(
381 "unknown Orchard anchor: {anchor:?},\n\
382 {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}"
383 )]
384 #[non_exhaustive]
385 UnknownOrchardAnchor {
386 anchor: orchard::tree::Root,
387 height: Option<block::Height>,
388 tx_index_in_block: Option<usize>,
389 transaction_hash: transaction::Hash,
390 },
391}
392
393impl From<sprout::tree::NoteCommitmentTreeError> for ValidateContextError {
394 fn from(value: sprout::tree::NoteCommitmentTreeError) -> Self {
395 ValidateContextError::NoteCommitmentTreeError(value.into())
396 }
397}
398
399pub trait DuplicateNullifierError {
401 fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError;
403}
404
405impl DuplicateNullifierError for sprout::Nullifier {
406 fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError {
407 ValidateContextError::DuplicateSproutNullifier {
408 nullifier: *self,
409 in_finalized_state,
410 }
411 }
412}
413
414impl DuplicateNullifierError for sapling::Nullifier {
415 fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError {
416 ValidateContextError::DuplicateSaplingNullifier {
417 nullifier: *self,
418 in_finalized_state,
419 }
420 }
421}
422
423impl DuplicateNullifierError for orchard::Nullifier {
424 fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError {
425 ValidateContextError::DuplicateOrchardNullifier {
426 nullifier: *self,
427 in_finalized_state,
428 }
429 }
430}
431
432#[cfg(test)]
433mod tests {
434 use super::*;
435 use zebra_chain::block::Height;
436
437 #[test]
438 fn commit_block_error_misbehavior_scores() {
439 let context_err = CommitBlockError::ValidateContextError(Box::new(
440 ValidateContextError::NonSequentialBlock {
441 candidate_height: Height(5),
442 parent_height: Height(3),
443 },
444 ));
445 assert_eq!(context_err.misbehavior_score(), 0);
446
447 let dup_err = CommitBlockError::Duplicate {
448 hash_or_height: None,
449 location: KnownBlock::BestChain,
450 };
451 assert_eq!(dup_err.misbehavior_score(), 0);
452 }
453}