1use std::{path::PathBuf, 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 ironwood, 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)]
50#[non_exhaustive]
51pub enum StateInitError {
52 #[error(
59 "cannot open read-only state: cache directory {path:?} is missing or unreadable. \
60 Hint: a read-only state requires an existing Zebra cache directory; check that the \
61 state cache_dir in the Zebra config points at a running Zebra node's cache directory"
62 )]
63 ReadOnlyCacheDirUnreadable {
64 path: PathBuf,
66 source: std::io::Error,
68 },
69
70 #[error(
76 "cannot open read-only state: no database found at {path:?}. \
77 Hint: a read-only state requires an existing finalized database created by a running \
78 Zebra node; check that the state cache_dir in the Zebra config points at that node's \
79 cache directory"
80 )]
81 ReadOnlyDatabaseNotFound {
82 path: PathBuf,
84 },
85
86 #[error(
92 "cannot open read-only state: an ephemeral database was also requested. \
93 Hint: a read-only state follows an existing Zebra node's database and must not \
94 delete it; set `ephemeral = false`, or do not request a read-only state"
95 )]
96 ReadOnlyEphemeralConflict,
97}
98
99#[derive(Debug, Error, Clone, PartialEq, Eq, new)]
101pub enum CommitBlockError {
102 #[error("block hash is a duplicate: already in {location}")]
103 Duplicate {
105 hash_or_height: Option<HashOrHeight>,
107 location: KnownBlock,
109 },
110
111 #[error("could not contextually validate semantically verified block")]
113 ValidateContextError(#[from] Box<ValidateContextError>),
114
115 #[error("block commit task exited. Is Zebra shutting down?")]
117 #[non_exhaustive]
118 WriteTaskExited,
119}
120
121impl CommitBlockError {
122 pub fn is_duplicate_request(&self) -> bool {
125 matches!(self, CommitBlockError::Duplicate { .. })
126 }
127
128 pub fn misbehavior_score(&self) -> u32 {
130 0
131 }
132}
133
134#[derive(Debug, Error, Clone, PartialEq, Eq)]
136#[error("could not commit semantically-verified block")]
137pub struct CommitSemanticallyVerifiedError(#[from] CommitBlockError);
138
139impl CommitSemanticallyVerifiedError {
140 pub fn inner(&self) -> &CommitBlockError {
142 &self.0
143 }
144}
145
146impl From<ValidateContextError> for CommitSemanticallyVerifiedError {
147 fn from(value: ValidateContextError) -> Self {
148 Self(CommitBlockError::ValidateContextError(Box::new(value)))
149 }
150}
151
152#[derive(Debug, Error)]
153pub enum LayeredStateError<E: std::error::Error + std::fmt::Display> {
154 #[error("{0}")]
155 State(E),
156 #[error("{0}")]
157 Layer(BoxError),
158}
159
160impl<E: std::error::Error + 'static> From<BoxError> for LayeredStateError<E> {
161 fn from(err: BoxError) -> Self {
162 match err.downcast::<E>() {
163 Ok(state_err) => Self::State(*state_err),
164 Err(layer_error) => Self::Layer(layer_error),
165 }
166 }
167}
168
169#[derive(Debug, Error, Clone)]
171#[error("could not commit checkpoint-verified block")]
172pub struct CommitCheckpointVerifiedError(#[from] CommitBlockError);
173
174impl CommitCheckpointVerifiedError {
175 pub fn inner(&self) -> &CommitBlockError {
177 &self.0
178 }
179}
180
181impl From<ValidateContextError> for CommitCheckpointVerifiedError {
182 fn from(value: ValidateContextError) -> Self {
183 Self(CommitBlockError::ValidateContextError(Box::new(value)))
184 }
185}
186
187#[derive(Debug, Error)]
189#[non_exhaustive]
190pub enum InvalidateError {
191 #[error("cannot invalidate blocks while still committing checkpointed blocks")]
193 ProcessingCheckpointedBlocks,
194
195 #[error("failed to send invalidate block request to block write task")]
197 SendInvalidateRequestFailed,
198
199 #[error("invalidate block request was unexpectedly dropped")]
201 InvalidateRequestDropped,
202
203 #[error("block hash {0} not found in any non-finalized chain")]
205 BlockNotFound(block::Hash),
206}
207
208#[derive(Debug, Error)]
210#[non_exhaustive]
211pub enum ReconsiderError {
212 #[error("Block with hash {0} was not previously invalidated")]
214 MissingInvalidatedBlock(block::Hash),
215
216 #[error("Parent chain not found for block {0}")]
218 ParentChainNotFound(block::Hash),
219
220 #[error("Invalidated blocks list is empty when it should contain at least one block")]
222 InvalidatedBlocksEmpty,
223
224 #[error("cannot reconsider blocks while still committing checkpointed blocks")]
226 CheckpointCommitInProgress,
227
228 #[error("failed to send reconsider block request to block write task")]
230 ReconsiderSendFailed,
231
232 #[error("reconsider block request was unexpectedly dropped")]
234 ReconsiderResponseDropped,
235
236 #[error("replaying a previously invalidated block failed contextual validation: {0}")]
239 ReplayFailed(#[source] ValidateContextError),
240}
241
242#[derive(Debug, Error, Clone, PartialEq, Eq)]
244#[non_exhaustive]
245#[allow(missing_docs)]
246pub enum ValidateContextError {
247 #[error("block hash {block_hash} was previously invalidated")]
248 #[non_exhaustive]
249 BlockPreviouslyInvalidated { block_hash: block::Hash },
250
251 #[error("block parent not found in any chain, or not enough blocks in chain")]
252 #[non_exhaustive]
253 NotReadyToBeCommitted,
254
255 #[error("block height {candidate_height:?} is lower than the current finalized height {finalized_tip_height:?}")]
256 #[non_exhaustive]
257 OrphanedBlock {
258 candidate_height: block::Height,
259 finalized_tip_height: block::Height,
260 },
261
262 #[error("block height {candidate_height:?} is not one greater than its parent block's height {parent_height:?}")]
263 #[non_exhaustive]
264 NonSequentialBlock {
265 candidate_height: block::Height,
266 parent_height: block::Height,
267 },
268
269 #[error("block time {candidate_time:?} is less than or equal to the median-time-past for the block {median_time_past:?}")]
270 #[non_exhaustive]
271 TimeTooEarly {
272 candidate_time: DateTime<Utc>,
273 median_time_past: DateTime<Utc>,
274 },
275
276 #[error("block time {candidate_time:?} is greater than the median-time-past for the block plus 90 minutes {block_time_max:?}")]
277 #[non_exhaustive]
278 TimeTooLate {
279 candidate_time: DateTime<Utc>,
280 block_time_max: DateTime<Utc>,
281 },
282
283 #[error("block difficulty threshold {difficulty_threshold:?} is not equal to the expected difficulty for the block {expected_difficulty:?}")]
284 #[non_exhaustive]
285 InvalidDifficultyThreshold {
286 difficulty_threshold: CompactDifficulty,
287 expected_difficulty: CompactDifficulty,
288 },
289
290 #[error("transparent double-spend: {outpoint:?} is spent twice in {location:?}")]
291 #[non_exhaustive]
292 DuplicateTransparentSpend {
293 outpoint: transparent::OutPoint,
294 location: &'static str,
295 },
296
297 #[error("missing transparent output: possible double-spend of {outpoint:?} in {location:?}")]
298 #[non_exhaustive]
299 MissingTransparentOutput {
300 outpoint: transparent::OutPoint,
301 location: &'static str,
302 },
303
304 #[error("out-of-order transparent spend: {outpoint:?} is created by a later transaction in the same block")]
305 #[non_exhaustive]
306 EarlyTransparentSpend { outpoint: transparent::OutPoint },
307
308 #[error(
309 "unshielded transparent coinbase spend: {outpoint:?} \
310 must be spent in a transaction which only has shielded outputs"
311 )]
312 #[non_exhaustive]
313 UnshieldedTransparentCoinbaseSpend { outpoint: transparent::OutPoint },
314
315 #[error(
316 "immature transparent coinbase spend: \
317 attempt to spend {outpoint:?} at {spend_height:?}, \
318 but spends are invalid before {min_spend_height:?}, \
319 which is {MIN_TRANSPARENT_COINBASE_MATURITY:?} blocks \
320 after it was created at {created_height:?}"
321 )]
322 #[non_exhaustive]
323 ImmatureTransparentCoinbaseSpend {
324 outpoint: transparent::OutPoint,
325 spend_height: block::Height,
326 min_spend_height: block::Height,
327 created_height: block::Height,
328 },
329
330 #[error("sprout double-spend: duplicate nullifier: {nullifier:?}, in finalized state: {in_finalized_state:?}")]
331 #[non_exhaustive]
332 DuplicateSproutNullifier {
333 nullifier: sprout::Nullifier,
334 in_finalized_state: bool,
335 },
336
337 #[error("sapling double-spend: duplicate nullifier: {nullifier:?}, in finalized state: {in_finalized_state:?}")]
338 #[non_exhaustive]
339 DuplicateSaplingNullifier {
340 nullifier: sapling::Nullifier,
341 in_finalized_state: bool,
342 },
343
344 #[error("orchard double-spend: duplicate nullifier: {nullifier:?}, in finalized state: {in_finalized_state:?}")]
345 #[non_exhaustive]
346 DuplicateOrchardNullifier {
347 nullifier: orchard::Nullifier,
348 in_finalized_state: bool,
349 },
350
351 #[error("ironwood double-spend: duplicate nullifier: {nullifier:?}, in finalized state: {in_finalized_state:?}")]
352 #[non_exhaustive]
353 DuplicateIronwoodNullifier {
354 nullifier: ironwood::Nullifier,
355 in_finalized_state: bool,
356 },
357
358 #[error(
359 "the remaining value in the transparent transaction value pool MUST be nonnegative:\n\
360 {amount_error:?},\n\
361 {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}"
362 )]
363 #[non_exhaustive]
364 NegativeRemainingTransactionValue {
365 amount_error: amount::Error,
366 height: block::Height,
367 tx_index_in_block: usize,
368 transaction_hash: transaction::Hash,
369 },
370
371 #[error(
372 "error calculating the remaining value in the transaction value pool:\n\
373 {amount_error:?},\n\
374 {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}"
375 )]
376 #[non_exhaustive]
377 CalculateRemainingTransactionValue {
378 amount_error: amount::Error,
379 height: block::Height,
380 tx_index_in_block: usize,
381 transaction_hash: transaction::Hash,
382 },
383
384 #[error(
385 "error calculating value balances for the remaining value in the transaction value pool:\n\
386 {value_balance_error:?},\n\
387 {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}"
388 )]
389 #[non_exhaustive]
390 CalculateTransactionValueBalances {
391 value_balance_error: ValueBalanceError,
392 height: block::Height,
393 tx_index_in_block: usize,
394 transaction_hash: transaction::Hash,
395 },
396
397 #[error(
398 "error calculating the block chain value pool change:\n\
399 {value_balance_error:?},\n\
400 {height:?}, {block_hash:?},\n\
401 transactions: {transaction_count:?}, spent UTXOs: {spent_utxo_count:?}"
402 )]
403 #[non_exhaustive]
404 CalculateBlockChainValueChange {
405 value_balance_error: ValueBalanceError,
406 height: block::Height,
407 block_hash: block::Hash,
408 transaction_count: usize,
409 spent_utxo_count: usize,
410 },
411
412 #[error(
413 "error adding value balances to the chain value pool:\n\
414 {value_balance_error:?},\n\
415 {chain_value_pools:?},\n\
416 {block_value_pool_change:?},\n\
417 {height:?}"
418 )]
419 #[non_exhaustive]
420 AddValuePool {
421 value_balance_error: ValueBalanceError,
422 chain_value_pools: Box<ValueBalance<NonNegative>>,
423 block_value_pool_change: Box<ValueBalance<NegativeAllowed>>,
424 height: Option<block::Height>,
425 },
426
427 #[error("error updating a note commitment tree: {0}")]
428 NoteCommitmentTreeError(#[from] zebra_chain::parallel::tree::NoteCommitmentTreeError),
429
430 #[error("error building the history tree: {0}")]
431 HistoryTreeError(#[from] Arc<HistoryTreeError>),
432
433 #[error("block contains an invalid commitment: {0}")]
434 InvalidBlockCommitment(#[from] block::CommitmentError),
435
436 #[error(
437 "unknown Sprout anchor: {anchor:?},\n\
438 {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}"
439 )]
440 #[non_exhaustive]
441 UnknownSproutAnchor {
442 anchor: sprout::tree::Root,
443 height: Option<block::Height>,
444 tx_index_in_block: Option<usize>,
445 transaction_hash: transaction::Hash,
446 },
447
448 #[error(
449 "unknown Sapling anchor: {anchor:?},\n\
450 {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}"
451 )]
452 #[non_exhaustive]
453 UnknownSaplingAnchor {
454 anchor: sapling::tree::Root,
455 height: Option<block::Height>,
456 tx_index_in_block: Option<usize>,
457 transaction_hash: transaction::Hash,
458 },
459
460 #[error(
461 "unknown Orchard anchor: {anchor:?},\n\
462 {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}"
463 )]
464 #[non_exhaustive]
465 UnknownOrchardAnchor {
466 anchor: orchard::tree::Root,
467 height: Option<block::Height>,
468 tx_index_in_block: Option<usize>,
469 transaction_hash: transaction::Hash,
470 },
471
472 #[error(
473 "unknown Ironwood anchor: {anchor:?},\n\
474 {height:?}, index in block: {tx_index_in_block:?}, {transaction_hash:?}"
475 )]
476 #[non_exhaustive]
477 UnknownIronwoodAnchor {
478 anchor: orchard::tree::Root,
480 height: Option<block::Height>,
481 tx_index_in_block: Option<usize>,
482 transaction_hash: transaction::Hash,
483 },
484}
485
486impl From<sprout::tree::NoteCommitmentTreeError> for ValidateContextError {
487 fn from(value: sprout::tree::NoteCommitmentTreeError) -> Self {
488 ValidateContextError::NoteCommitmentTreeError(value.into())
489 }
490}
491
492pub trait DuplicateNullifierError {
494 fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError;
496}
497
498impl DuplicateNullifierError for sprout::Nullifier {
499 fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError {
500 ValidateContextError::DuplicateSproutNullifier {
501 nullifier: *self,
502 in_finalized_state,
503 }
504 }
505}
506
507impl DuplicateNullifierError for sapling::Nullifier {
508 fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError {
509 ValidateContextError::DuplicateSaplingNullifier {
510 nullifier: *self,
511 in_finalized_state,
512 }
513 }
514}
515
516impl DuplicateNullifierError for orchard::Nullifier {
517 fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError {
518 ValidateContextError::DuplicateOrchardNullifier {
519 nullifier: *self,
520 in_finalized_state,
521 }
522 }
523}
524
525impl DuplicateNullifierError for ironwood::Nullifier {
526 fn duplicate_nullifier_error(&self, in_finalized_state: bool) -> ValidateContextError {
527 ValidateContextError::DuplicateIronwoodNullifier {
528 nullifier: *self,
529 in_finalized_state,
530 }
531 }
532}
533
534#[cfg(test)]
535mod tests {
536 use super::*;
537 use zebra_chain::block::Height;
538
539 #[test]
540 fn commit_block_error_misbehavior_scores() {
541 let context_err = CommitBlockError::ValidateContextError(Box::new(
542 ValidateContextError::NonSequentialBlock {
543 candidate_height: Height(5),
544 parent_height: Height(3),
545 },
546 ));
547 assert_eq!(context_err.misbehavior_score(), 0);
548
549 let dup_err = CommitBlockError::Duplicate {
550 hash_or_height: None,
551 location: KnownBlock::BestChain,
552 };
553 assert_eq!(dup_err.misbehavior_score(), 0);
554 }
555}