1#![allow(deprecated)]
2use crate::BlockHash;
7
8use std::{any::type_name, fmt::Display};
9
10use zaino_fetch::jsonrpsee::connector::RpcRequestError;
11use zaino_proto::proto::utils::GetBlockRangeError;
12
13#[derive(Debug, thiserror::Error)]
16#[allow(clippy::result_large_err)]
17pub enum NodeBackedIndexerServiceError {
18 #[error("Critical error: {0}")]
20 Critical(String),
21
22 #[error("unhandled fallible RPC call {0}")]
24 UnhandledRpcError(String),
25 #[error("Custom error: {0}")]
27 Custom(String),
28
29 #[error("Join error: {0}")]
31 JoinError(#[from] tokio::task::JoinError),
32
33 #[error("JsonRpcConnector error: {0}")]
35 JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
36
37 #[error("RPC error: {0:?}")]
39 RpcError(#[from] zaino_fetch::jsonrpsee::connector::RpcError),
40
41 #[error("Chain index error: {0}")]
43 ChainIndexError(#[from] ChainIndexError),
44
45 #[error("Mempool error: {0}")]
47 BlockCacheError(#[from] BlockCacheError),
48
49 #[error("Mempool error: {0}")]
51 MempoolError(#[from] MempoolError),
52
53 #[error("Tonic status error: {0}")]
55 TonicStatusError(#[from] tonic::Status),
56
57 #[error("Serialization error: {0}")]
59 SerializationError(#[from] zebra_chain::serialization::SerializationError),
60
61 #[error("Integer conversion error: {0}")]
63 TryFromIntError(#[from] std::num::TryFromIntError),
64
65 #[error("IO error: {0}")]
67 IoError(#[from] std::io::Error),
68
69 #[error("Generic error: {0}")]
71 Generic(#[from] Box<dyn std::error::Error + Send + Sync>),
72
73 #[error(
75 "zebrad version mismatch. this build of zaino requires a \
76 version of {expected_zebrad_version}, but the connected zebrad \
77 is version {connected_zebrad_version}"
78 )]
79 ZebradVersionMismatch {
80 expected_zebrad_version: String,
82 connected_zebrad_version: String,
85 },
86 #[error("zaino not yet synced")]
87 UnavailableNotSyncedEnough,
89}
90
91impl From<GetBlockRangeError> for NodeBackedIndexerServiceError {
92 fn from(value: GetBlockRangeError) -> Self {
93 match value {
94 GetBlockRangeError::StartHeightOutOfRange => {
95 Self::TonicStatusError(tonic::Status::out_of_range(
96 "Error: Start height out of range. Failed to convert to u32.",
97 ))
98 }
99 GetBlockRangeError::NoStartHeightProvided => {
100 Self::TonicStatusError(tonic::Status::out_of_range("Error: No start height given"))
101 }
102 GetBlockRangeError::EndHeightOutOfRange => {
103 Self::TonicStatusError(tonic::Status::out_of_range(
104 "Error: End height out of range. Failed to convert to u32.",
105 ))
106 }
107 GetBlockRangeError::NoEndHeightProvided => {
108 Self::TonicStatusError(tonic::Status::out_of_range("Error: No end height given."))
109 }
110 GetBlockRangeError::PoolTypeArgumentError(_) => {
111 Self::TonicStatusError(tonic::Status::invalid_argument("Error: invalid pool type"))
112 }
113 }
114 }
115}
116
117#[allow(deprecated)]
118impl From<NodeBackedIndexerServiceError> for tonic::Status {
119 fn from(error: NodeBackedIndexerServiceError) -> Self {
120 match error {
121 NodeBackedIndexerServiceError::Critical(message) => tonic::Status::internal(message),
122 NodeBackedIndexerServiceError::Custom(message) => tonic::Status::internal(message),
123 NodeBackedIndexerServiceError::JoinError(err) => {
124 tonic::Status::internal(format!("Join error: {err}"))
125 }
126 NodeBackedIndexerServiceError::JsonRpcConnectorError(err) => {
127 tonic::Status::internal(format!("JsonRpcConnector error: {err}"))
128 }
129 NodeBackedIndexerServiceError::RpcError(err) => {
130 tonic::Status::internal(format!("RPC error: {err:?}"))
131 }
132 NodeBackedIndexerServiceError::ChainIndexError(err) => match err.kind {
133 ChainIndexErrorKind::InternalServerError => tonic::Status::internal(err.message),
134 ChainIndexErrorKind::InvalidSnapshot => {
135 tonic::Status::failed_precondition(err.message)
136 }
137 },
138 NodeBackedIndexerServiceError::BlockCacheError(err) => {
139 tonic::Status::internal(format!("BlockCache error: {err:?}"))
140 }
141 NodeBackedIndexerServiceError::MempoolError(err) => {
142 tonic::Status::internal(format!("Mempool error: {err:?}"))
143 }
144 NodeBackedIndexerServiceError::TonicStatusError(err) => err,
145 NodeBackedIndexerServiceError::SerializationError(err) => {
146 tonic::Status::internal(format!("Serialization error: {err}"))
147 }
148 NodeBackedIndexerServiceError::TryFromIntError(err) => {
149 tonic::Status::internal(format!("Integer conversion error: {err}"))
150 }
151 NodeBackedIndexerServiceError::IoError(err) => {
152 tonic::Status::internal(format!("IO error: {err}"))
153 }
154 NodeBackedIndexerServiceError::Generic(err) => {
155 tonic::Status::internal(format!("Generic error: {err}"))
156 }
157 ref err @ NodeBackedIndexerServiceError::ZebradVersionMismatch { .. } => {
158 tonic::Status::internal(err.to_string())
159 }
160 NodeBackedIndexerServiceError::UnhandledRpcError(e) => {
161 tonic::Status::internal(e.to_string())
162 }
163 NodeBackedIndexerServiceError::UnavailableNotSyncedEnough => {
164 tonic::Status::failed_precondition("zaino not yet synced".to_string())
165 }
166 }
167 }
168}
169
170impl<T: ToString> From<RpcRequestError<T>> for NodeBackedIndexerServiceError {
171 fn from(value: RpcRequestError<T>) -> Self {
172 match value {
173 RpcRequestError::Transport(transport_error) => {
174 Self::JsonRpcConnectorError(transport_error)
175 }
176 RpcRequestError::Method(e) => Self::UnhandledRpcError(format!(
177 "{}: {}",
178 std::any::type_name::<T>(),
179 e.to_string()
180 )),
181 RpcRequestError::JsonRpc(error) => Self::Custom(format!("bad argument: {error}")),
182 RpcRequestError::InternalUnrecoverable(e) => Self::Custom(e.to_string()),
183 RpcRequestError::ServerWorkQueueFull => {
184 Self::Custom("Server queue full. Handling for this not yet implemented".to_string())
185 }
186 RpcRequestError::UnexpectedErrorResponse(error) => Self::Custom(format!("{error}")),
187 }
188 }
189}
190
191impl<T: ToString> From<RpcRequestError<T>> for MempoolError {
194 fn from(value: RpcRequestError<T>) -> Self {
195 match value {
196 RpcRequestError::Transport(transport_error) => {
197 MempoolError::JsonRpcConnectorError(transport_error)
198 }
199 RpcRequestError::JsonRpc(error) => {
200 MempoolError::Critical(format!("argument failed to serialze: {error}"))
201 }
202 RpcRequestError::InternalUnrecoverable(e) => {
203 MempoolError::Critical(format!("Internal unrecoverable error: {e}"))
204 }
205 RpcRequestError::ServerWorkQueueFull => MempoolError::Critical(
206 "Server queue full. Handling for this not yet implemented".to_string(),
207 ),
208 RpcRequestError::Method(e) => MempoolError::Critical(format!(
209 "unhandled rpc-specific {} error: {}",
210 type_name::<T>(),
211 e.to_string()
212 )),
213 RpcRequestError::UnexpectedErrorResponse(error) => MempoolError::Critical(format!(
214 "unhandled rpc-specific {} error: {}",
215 type_name::<T>(),
216 error
217 )),
218 }
219 }
220}
221
222#[derive(Debug, thiserror::Error)]
224pub enum MempoolError {
225 #[error("Critical error: {0}")]
227 Critical(String),
228
229 #[error(
231 "Incorrect chain tip (expected {expected_chain_tip:?}, current {current_chain_tip:?})"
232 )]
233 IncorrectChainTip {
234 expected_chain_tip: BlockHash,
235 current_chain_tip: BlockHash,
236 },
237
238 #[error("JsonRpcConnector error: {0}")]
240 JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
241
242 #[error("blockchain source error: {0}")]
244 BlockchainSourceError(#[from] crate::chain_index::source::BlockchainSourceError),
245
246 #[error("Join error: {0}")]
248 WatchRecvError(#[from] tokio::sync::watch::error::RecvError),
249
250 #[error("Status error: {0:?}")]
252 StatusError(StatusError),
253}
254
255#[derive(Debug, thiserror::Error)]
257pub enum BlockCacheError {
258 #[error("Custom error: {0}")]
260 Custom(String),
261
262 #[error("Critical error: {0}")]
264 Critical(String),
265
266 #[error("NonFinalisedState Error: {0}")]
268 NonFinalisedStateError(#[from] NonFinalisedStateError),
269
270 #[error("FinalisedState Error: {0}")]
272 FinalisedStateError(#[from] FinalisedStateError),
273
274 #[error("JsonRpcConnector error: {0}")]
276 JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
277
278 #[error("Chain parse error: {0}")]
280 ChainParseError(#[from] zaino_fetch::chain::error::ParseError),
281
282 #[error("Serialization error: {0}")]
284 SerializationError(#[from] zebra_chain::serialization::SerializationError),
285
286 #[error("UTF-8 conversion error: {0}")]
288 Utf8Error(#[from] std::str::Utf8Error),
289
290 #[error("Integer parsing error: {0}")]
292 ParseIntError(#[from] std::num::ParseIntError),
293
294 #[error("Integer conversion error: {0}")]
296 TryFromIntError(#[from] std::num::TryFromIntError),
297}
298
299#[derive(Debug, thiserror::Error)]
301pub enum NonFinalisedStateError {
302 #[error("Custom error: {0}")]
304 Custom(String),
305
306 #[error("Missing data: {0}")]
308 MissingData(String),
309
310 #[error("Critical error: {0}")]
312 Critical(String),
313
314 #[error("JsonRpcConnector error: {0}")]
316 JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
317
318 #[error("Status error: {0:?}")]
320 StatusError(StatusError),
321}
322
323impl<T: ToString> From<RpcRequestError<T>> for NonFinalisedStateError {
326 fn from(value: RpcRequestError<T>) -> Self {
327 match value {
328 RpcRequestError::Transport(transport_error) => {
329 NonFinalisedStateError::JsonRpcConnectorError(transport_error)
330 }
331 RpcRequestError::JsonRpc(error) => {
332 NonFinalisedStateError::Custom(format!("argument failed to serialze: {error}"))
333 }
334 RpcRequestError::InternalUnrecoverable(e) => {
335 NonFinalisedStateError::Custom(format!("Internal unrecoverable error: {e}"))
336 }
337 RpcRequestError::ServerWorkQueueFull => NonFinalisedStateError::Custom(
338 "Server queue full. Handling for this not yet implemented".to_string(),
339 ),
340 RpcRequestError::Method(e) => NonFinalisedStateError::Custom(format!(
341 "unhandled rpc-specific {} error: {}",
342 type_name::<T>(),
343 e.to_string()
344 )),
345 RpcRequestError::UnexpectedErrorResponse(error) => {
346 NonFinalisedStateError::Custom(format!(
347 "unhandled rpc-specific {} error: {}",
348 type_name::<T>(),
349 error
350 ))
351 }
352 }
353 }
354}
355
356#[derive(Debug, thiserror::Error)]
359pub enum FinalisedStateError {
360 #[error("Custom error: {0}")]
363 Custom(String),
364
365 #[error("Missing data: {0}")]
371 DataUnavailable(String),
372
373 #[error("invalid block @ height {height} (hash {hash}): {reason}")]
379 InvalidBlock {
380 height: u32,
381 hash: BlockHash,
382 reason: String,
383 },
384
385 #[error("feature unavailable: {0}")]
388 FeatureUnavailable(&'static str),
389
390 #[error("blockchain source error: {0}")]
392 BlockchainSourceError(#[from] crate::chain_index::source::BlockchainSourceError),
393
394 #[error("Critical error: {0}")]
396 Critical(String),
397
398 #[error("LMDB database error: {0}")]
401 LmdbError(#[from] lmdb::Error),
402
403 #[error("LMDB database error: {0}")]
406 SerdeJsonError(#[from] serde_json::Error),
407
408 #[error("Status error: {0:?}")]
410 StatusError(StatusError),
411
412 #[error("JsonRpcConnector error: {0}")]
415 JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
416
417 #[error("IO error: {0}")]
419 IoError(#[from] std::io::Error),
420}
421
422impl<T: ToString> From<RpcRequestError<T>> for FinalisedStateError {
425 fn from(value: RpcRequestError<T>) -> Self {
426 match value {
427 RpcRequestError::Transport(transport_error) => {
428 FinalisedStateError::JsonRpcConnectorError(transport_error)
429 }
430 RpcRequestError::JsonRpc(error) => {
431 FinalisedStateError::Custom(format!("argument failed to serialze: {error}"))
432 }
433 RpcRequestError::InternalUnrecoverable(e) => {
434 FinalisedStateError::Custom(format!("Internal unrecoverable error: {e}"))
435 }
436 RpcRequestError::ServerWorkQueueFull => FinalisedStateError::Custom(
437 "Server queue full. Handling for this not yet implemented".to_string(),
438 ),
439 RpcRequestError::Method(e) => FinalisedStateError::Custom(format!(
440 "unhandled rpc-specific {} error: {}",
441 type_name::<T>(),
442 e.to_string()
443 )),
444 RpcRequestError::UnexpectedErrorResponse(error) => {
445 FinalisedStateError::Custom(format!(
446 "unhandled rpc-specific {} error: {}",
447 type_name::<T>(),
448 error
449 ))
450 }
451 }
452 }
453}
454
455#[derive(Debug, Clone, thiserror::Error)]
457#[error("Unexpected status error: {server_status:?}")]
458pub struct StatusError {
459 pub server_status: crate::status::StatusType,
460}
461
462#[derive(Debug, thiserror::Error)]
463#[error("{kind}: {message}")]
464pub struct ChainIndexError {
467 pub(crate) kind: ChainIndexErrorKind,
468 pub(crate) message: String,
469 pub(crate) source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
470}
471
472#[derive(Debug, Copy, Clone)]
473#[non_exhaustive]
474pub enum ChainIndexErrorKind {
476 InternalServerError,
478 #[allow(dead_code)]
484 InvalidSnapshot,
485}
486
487impl Display for ChainIndexErrorKind {
488 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
489 f.write_str(match self {
490 ChainIndexErrorKind::InternalServerError => "internal server error",
491 ChainIndexErrorKind::InvalidSnapshot => "invalid snapshot",
492 })
493 }
494}
495
496impl ChainIndexError {
497 pub fn kind(&self) -> ChainIndexErrorKind {
499 self.kind
500 }
501 pub(crate) fn internal(message: impl Into<String>) -> Self {
506 Self {
507 kind: ChainIndexErrorKind::InternalServerError,
508 message: message.into(),
509 source: None,
510 }
511 }
512
513 pub(crate) fn internal_from(error: impl std::error::Error + Send + Sync + 'static) -> Self {
518 Self {
519 kind: ChainIndexErrorKind::InternalServerError,
520 message: error.to_string(),
521 source: Some(Box::new(error)),
522 }
523 }
524
525 pub(crate) fn backing_validator(value: impl std::error::Error + Send + Sync + 'static) -> Self {
526 Self {
527 kind: ChainIndexErrorKind::InternalServerError,
528 message: "InternalServerError: error receiving data from backing node".to_string(),
529 source: Some(Box::new(value)),
530 }
531 }
532
533 pub(crate) fn database_hole(
534 missing_block: impl Display,
535 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
536 ) -> Self {
537 Self {
538 kind: ChainIndexErrorKind::InternalServerError,
539 message: format!(
540 "InternalServerError: hole in validator database, missing block {missing_block}"
541 ),
542 source,
543 }
544 }
545
546 pub(crate) fn validator_data_error_block_coinbase_height_missing() -> Self {
547 Self {
548 kind: ChainIndexErrorKind::InternalServerError,
549 message: "validator error: data error: block.coinbase_height() returned None"
550 .to_string(),
551 source: None,
552 }
553 }
554
555 pub(crate) fn child_process_status_error(process: &str, status_err: StatusError) -> Self {
556 use crate::status::StatusType;
557
558 let message = match status_err.server_status {
559 StatusType::Spawning => format!("{process} status: Spawning (not ready yet)"),
560 StatusType::Syncing => format!("{process} status: Syncing (not ready yet)"),
561 StatusType::Ready => format!("{process} status: Ready (unexpected error path)"),
562 StatusType::Busy => format!("{process} status: Busy (temporarily unavailable)"),
563 StatusType::Closing => format!("{process} status: Closing (shutting down)"),
564 StatusType::Offline => format!("{process} status: Offline (not available)"),
565 StatusType::RecoverableError => {
566 format!("{process} status: RecoverableError (retry may succeed)")
567 }
568 StatusType::CriticalError => {
569 format!("{process} status: CriticalError (requires operator action)")
570 }
571 };
572
573 ChainIndexError {
574 kind: ChainIndexErrorKind::InternalServerError,
575 message,
576 source: Some(Box::new(status_err)),
577 }
578 }
579}
580
581impl From<FinalisedStateError> for ChainIndexError {
582 fn from(value: FinalisedStateError) -> Self {
583 let message = match &value {
584 FinalisedStateError::DataUnavailable(err) => format!("unhandled missing data: {err}"),
585 FinalisedStateError::FeatureUnavailable(err) => {
586 format!("unhandled missing feature: {err}")
587 }
588 FinalisedStateError::InvalidBlock {
589 height,
590 hash: _,
591 reason,
592 } => format!("invalid block at height {height}: {reason}"),
593 FinalisedStateError::Custom(err) | FinalisedStateError::Critical(err) => err.clone(),
594 FinalisedStateError::LmdbError(error) => error.to_string(),
595 FinalisedStateError::SerdeJsonError(error) => error.to_string(),
596 FinalisedStateError::StatusError(status_error) => status_error.to_string(),
597 FinalisedStateError::JsonRpcConnectorError(transport_error) => {
598 transport_error.to_string()
599 }
600 FinalisedStateError::IoError(error) => error.to_string(),
601 FinalisedStateError::BlockchainSourceError(blockchain_source_error) => {
602 blockchain_source_error.to_string()
603 }
604 };
605 ChainIndexError {
606 kind: ChainIndexErrorKind::InternalServerError,
607 message,
608 source: Some(Box::new(value)),
609 }
610 }
611}
612
613impl From<MempoolError> for ChainIndexError {
614 fn from(value: MempoolError) -> Self {
615 let message = match &value {
617 MempoolError::Critical(msg) => format!("critical mempool error: {msg}"),
618 MempoolError::IncorrectChainTip {
619 expected_chain_tip,
620 current_chain_tip,
621 } => {
622 format!(
623 "incorrect chain tip (expected {expected_chain_tip:?}, current {current_chain_tip:?})"
624 )
625 }
626 MempoolError::JsonRpcConnectorError(err) => {
627 format!("mempool json-rpc connector error: {err}")
628 }
629 MempoolError::BlockchainSourceError(err) => {
630 format!("mempool blockchain source error: {err}")
631 }
632 MempoolError::WatchRecvError(err) => format!("mempool watch receiver error: {err}"),
633 MempoolError::StatusError(status_err) => {
634 format!("mempool status error: {status_err:?}")
635 }
636 };
637
638 ChainIndexError {
639 kind: ChainIndexErrorKind::InternalServerError,
640 message,
641 source: Some(Box::new(value)),
642 }
643 }
644}