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 StateServiceError {
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 StateServiceError {
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<StateServiceError> for tonic::Status {
119 fn from(error: StateServiceError) -> Self {
120 match error {
121 StateServiceError::Critical(message) => tonic::Status::internal(message),
122 StateServiceError::Custom(message) => tonic::Status::internal(message),
123 StateServiceError::JoinError(err) => {
124 tonic::Status::internal(format!("Join error: {err}"))
125 }
126 StateServiceError::JsonRpcConnectorError(err) => {
127 tonic::Status::internal(format!("JsonRpcConnector error: {err}"))
128 }
129 StateServiceError::RpcError(err) => {
130 tonic::Status::internal(format!("RPC error: {err:?}"))
131 }
132 StateServiceError::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 StateServiceError::BlockCacheError(err) => {
139 tonic::Status::internal(format!("BlockCache error: {err:?}"))
140 }
141 StateServiceError::MempoolError(err) => {
142 tonic::Status::internal(format!("Mempool error: {err:?}"))
143 }
144 StateServiceError::TonicStatusError(err) => err,
145 StateServiceError::SerializationError(err) => {
146 tonic::Status::internal(format!("Serialization error: {err}"))
147 }
148 StateServiceError::TryFromIntError(err) => {
149 tonic::Status::internal(format!("Integer conversion error: {err}"))
150 }
151 StateServiceError::IoError(err) => tonic::Status::internal(format!("IO error: {err}")),
152 StateServiceError::Generic(err) => {
153 tonic::Status::internal(format!("Generic error: {err}"))
154 }
155 ref err @ StateServiceError::ZebradVersionMismatch { .. } => {
156 tonic::Status::internal(err.to_string())
157 }
158 StateServiceError::UnhandledRpcError(e) => tonic::Status::internal(e.to_string()),
159 StateServiceError::UnavailableNotSyncedEnough => {
160 tonic::Status::failed_precondition("zaino not yet synced".to_string())
161 }
162 }
163 }
164}
165
166impl<T: ToString> From<RpcRequestError<T>> for StateServiceError {
167 fn from(value: RpcRequestError<T>) -> Self {
168 match value {
169 RpcRequestError::Transport(transport_error) => {
170 Self::JsonRpcConnectorError(transport_error)
171 }
172 RpcRequestError::Method(e) => Self::UnhandledRpcError(format!(
173 "{}: {}",
174 std::any::type_name::<T>(),
175 e.to_string()
176 )),
177 RpcRequestError::JsonRpc(error) => Self::Custom(format!("bad argument: {error}")),
178 RpcRequestError::InternalUnrecoverable(e) => Self::Custom(e.to_string()),
179 RpcRequestError::ServerWorkQueueFull => {
180 Self::Custom("Server queue full. Handling for this not yet implemented".to_string())
181 }
182 RpcRequestError::UnexpectedErrorResponse(error) => Self::Custom(format!("{error}")),
183 }
184 }
185}
186
187#[deprecated]
189#[derive(Debug, thiserror::Error)]
190pub enum FetchServiceError {
191 #[error("Critical error: {0}")]
193 Critical(String),
194
195 #[error("JsonRpcConnector error: {0}")]
197 JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
198
199 #[error("Chain index error: {0}")]
201 ChainIndexError(#[from] ChainIndexError),
202
203 #[error("RPC error: {0:?}")]
205 RpcError(#[from] zaino_fetch::jsonrpsee::connector::RpcError),
206
207 #[error("Tonic status error: {0}")]
209 TonicStatusError(#[from] tonic::Status),
210
211 #[error("Serialization error: {0}")]
213 SerializationError(#[from] zebra_chain::serialization::SerializationError),
214 #[error("Zaino has not synced high enough to serve this data")]
215 UnavailableNotSyncedEnough,
217}
218
219impl From<FetchServiceError> for tonic::Status {
220 fn from(error: FetchServiceError) -> Self {
221 match error {
222 FetchServiceError::Critical(message) => tonic::Status::internal(message),
223 FetchServiceError::JsonRpcConnectorError(err) => {
224 tonic::Status::internal(format!("JsonRpcConnector error: {err}"))
225 }
226 FetchServiceError::ChainIndexError(err) => match err.kind {
227 ChainIndexErrorKind::InternalServerError => tonic::Status::internal(err.message),
228 ChainIndexErrorKind::InvalidSnapshot => {
229 tonic::Status::failed_precondition(err.message)
230 }
231 },
232 FetchServiceError::RpcError(err) => {
233 tonic::Status::internal(format!("RPC error: {err:?}"))
234 }
235 FetchServiceError::TonicStatusError(err) => err,
236 FetchServiceError::SerializationError(err) => {
237 tonic::Status::internal(format!("Serialization error: {err}"))
238 }
239 FetchServiceError::UnavailableNotSyncedEnough => {
240 tonic::Status::failed_precondition("zaino not yet synced".to_string())
241 }
242 }
243 }
244}
245
246impl<T: ToString> From<RpcRequestError<T>> for FetchServiceError {
247 fn from(value: RpcRequestError<T>) -> Self {
248 match value {
249 RpcRequestError::Transport(transport_error) => {
250 FetchServiceError::JsonRpcConnectorError(transport_error)
251 }
252 RpcRequestError::JsonRpc(error) => {
253 FetchServiceError::Critical(format!("argument failed to serialze: {error}"))
254 }
255 RpcRequestError::InternalUnrecoverable(e) => {
256 FetchServiceError::Critical(format!("Internal unrecoverable error: {e}"))
257 }
258 RpcRequestError::ServerWorkQueueFull => FetchServiceError::Critical(
259 "Server queue full. Handling for this not yet implemented".to_string(),
260 ),
261 RpcRequestError::Method(e) => FetchServiceError::Critical(format!(
262 "unhandled rpc-specific {} error: {}",
263 type_name::<T>(),
264 e.to_string()
265 )),
266 RpcRequestError::UnexpectedErrorResponse(error) => {
267 FetchServiceError::Critical(format!(
268 "unhandled rpc-specific {} error: {}",
269 type_name::<T>(),
270 error
271 ))
272 }
273 }
274 }
275}
276
277impl From<GetBlockRangeError> for FetchServiceError {
278 fn from(value: GetBlockRangeError) -> Self {
279 match value {
280 GetBlockRangeError::StartHeightOutOfRange => {
281 FetchServiceError::TonicStatusError(tonic::Status::out_of_range(
282 "Error: Start height out of range. Failed to convert to u32.",
283 ))
284 }
285 GetBlockRangeError::NoStartHeightProvided => FetchServiceError::TonicStatusError(
286 tonic::Status::out_of_range("Error: No start height given"),
287 ),
288 GetBlockRangeError::EndHeightOutOfRange => {
289 FetchServiceError::TonicStatusError(tonic::Status::out_of_range(
290 "Error: End height out of range. Failed to convert to u32.",
291 ))
292 }
293 GetBlockRangeError::NoEndHeightProvided => FetchServiceError::TonicStatusError(
294 tonic::Status::out_of_range("Error: No end height given."),
295 ),
296 GetBlockRangeError::PoolTypeArgumentError(_) => FetchServiceError::TonicStatusError(
297 tonic::Status::invalid_argument("Error: invalid pool type"),
298 ),
299 }
300 }
301}
302
303impl<T: ToString> From<RpcRequestError<T>> for MempoolError {
306 fn from(value: RpcRequestError<T>) -> Self {
307 match value {
308 RpcRequestError::Transport(transport_error) => {
309 MempoolError::JsonRpcConnectorError(transport_error)
310 }
311 RpcRequestError::JsonRpc(error) => {
312 MempoolError::Critical(format!("argument failed to serialze: {error}"))
313 }
314 RpcRequestError::InternalUnrecoverable(e) => {
315 MempoolError::Critical(format!("Internal unrecoverable error: {e}"))
316 }
317 RpcRequestError::ServerWorkQueueFull => MempoolError::Critical(
318 "Server queue full. Handling for this not yet implemented".to_string(),
319 ),
320 RpcRequestError::Method(e) => MempoolError::Critical(format!(
321 "unhandled rpc-specific {} error: {}",
322 type_name::<T>(),
323 e.to_string()
324 )),
325 RpcRequestError::UnexpectedErrorResponse(error) => MempoolError::Critical(format!(
326 "unhandled rpc-specific {} error: {}",
327 type_name::<T>(),
328 error
329 )),
330 }
331 }
332}
333
334#[derive(Debug, thiserror::Error)]
336pub enum MempoolError {
337 #[error("Critical error: {0}")]
339 Critical(String),
340
341 #[error(
343 "Incorrect chain tip (expected {expected_chain_tip:?}, current {current_chain_tip:?})"
344 )]
345 IncorrectChainTip {
346 expected_chain_tip: BlockHash,
347 current_chain_tip: BlockHash,
348 },
349
350 #[error("JsonRpcConnector error: {0}")]
352 JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
353
354 #[error("blockchain source error: {0}")]
356 BlockchainSourceError(#[from] crate::chain_index::source::BlockchainSourceError),
357
358 #[error("Join error: {0}")]
360 WatchRecvError(#[from] tokio::sync::watch::error::RecvError),
361
362 #[error("Status error: {0:?}")]
364 StatusError(StatusError),
365}
366
367#[derive(Debug, thiserror::Error)]
369pub enum BlockCacheError {
370 #[error("Custom error: {0}")]
372 Custom(String),
373
374 #[error("Critical error: {0}")]
376 Critical(String),
377
378 #[error("NonFinalisedState Error: {0}")]
380 NonFinalisedStateError(#[from] NonFinalisedStateError),
381
382 #[error("FinalisedState Error: {0}")]
384 FinalisedStateError(#[from] FinalisedStateError),
385
386 #[error("JsonRpcConnector error: {0}")]
388 JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
389
390 #[error("Chain parse error: {0}")]
392 ChainParseError(#[from] zaino_fetch::chain::error::ParseError),
393
394 #[error("Serialization error: {0}")]
396 SerializationError(#[from] zebra_chain::serialization::SerializationError),
397
398 #[error("UTF-8 conversion error: {0}")]
400 Utf8Error(#[from] std::str::Utf8Error),
401
402 #[error("Integer parsing error: {0}")]
404 ParseIntError(#[from] std::num::ParseIntError),
405
406 #[error("Integer conversion error: {0}")]
408 TryFromIntError(#[from] std::num::TryFromIntError),
409}
410
411#[derive(Debug, thiserror::Error)]
413pub enum NonFinalisedStateError {
414 #[error("Custom error: {0}")]
416 Custom(String),
417
418 #[error("Missing data: {0}")]
420 MissingData(String),
421
422 #[error("Critical error: {0}")]
424 Critical(String),
425
426 #[error("JsonRpcConnector error: {0}")]
428 JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
429
430 #[error("Status error: {0:?}")]
432 StatusError(StatusError),
433}
434
435impl<T: ToString> From<RpcRequestError<T>> for NonFinalisedStateError {
438 fn from(value: RpcRequestError<T>) -> Self {
439 match value {
440 RpcRequestError::Transport(transport_error) => {
441 NonFinalisedStateError::JsonRpcConnectorError(transport_error)
442 }
443 RpcRequestError::JsonRpc(error) => {
444 NonFinalisedStateError::Custom(format!("argument failed to serialze: {error}"))
445 }
446 RpcRequestError::InternalUnrecoverable(e) => {
447 NonFinalisedStateError::Custom(format!("Internal unrecoverable error: {e}"))
448 }
449 RpcRequestError::ServerWorkQueueFull => NonFinalisedStateError::Custom(
450 "Server queue full. Handling for this not yet implemented".to_string(),
451 ),
452 RpcRequestError::Method(e) => NonFinalisedStateError::Custom(format!(
453 "unhandled rpc-specific {} error: {}",
454 type_name::<T>(),
455 e.to_string()
456 )),
457 RpcRequestError::UnexpectedErrorResponse(error) => {
458 NonFinalisedStateError::Custom(format!(
459 "unhandled rpc-specific {} error: {}",
460 type_name::<T>(),
461 error
462 ))
463 }
464 }
465 }
466}
467
468#[derive(Debug, thiserror::Error)]
471pub enum FinalisedStateError {
472 #[error("Custom error: {0}")]
475 Custom(String),
476
477 #[error("Missing data: {0}")]
483 DataUnavailable(String),
484
485 #[error("invalid block @ height {height} (hash {hash}): {reason}")]
491 InvalidBlock {
492 height: u32,
493 hash: BlockHash,
494 reason: String,
495 },
496
497 #[error("feature unavailable: {0}")]
500 FeatureUnavailable(&'static str),
501
502 #[error("blockchain source error: {0}")]
504 BlockchainSourceError(#[from] crate::chain_index::source::BlockchainSourceError),
505
506 #[error("Critical error: {0}")]
508 Critical(String),
509
510 #[error("LMDB database error: {0}")]
513 LmdbError(#[from] lmdb::Error),
514
515 #[error("LMDB database error: {0}")]
518 SerdeJsonError(#[from] serde_json::Error),
519
520 #[error("Status error: {0:?}")]
522 StatusError(StatusError),
523
524 #[error("JsonRpcConnector error: {0}")]
527 JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
528
529 #[error("IO error: {0}")]
531 IoError(#[from] std::io::Error),
532}
533
534impl<T: ToString> From<RpcRequestError<T>> for FinalisedStateError {
537 fn from(value: RpcRequestError<T>) -> Self {
538 match value {
539 RpcRequestError::Transport(transport_error) => {
540 FinalisedStateError::JsonRpcConnectorError(transport_error)
541 }
542 RpcRequestError::JsonRpc(error) => {
543 FinalisedStateError::Custom(format!("argument failed to serialze: {error}"))
544 }
545 RpcRequestError::InternalUnrecoverable(e) => {
546 FinalisedStateError::Custom(format!("Internal unrecoverable error: {e}"))
547 }
548 RpcRequestError::ServerWorkQueueFull => FinalisedStateError::Custom(
549 "Server queue full. Handling for this not yet implemented".to_string(),
550 ),
551 RpcRequestError::Method(e) => FinalisedStateError::Custom(format!(
552 "unhandled rpc-specific {} error: {}",
553 type_name::<T>(),
554 e.to_string()
555 )),
556 RpcRequestError::UnexpectedErrorResponse(error) => {
557 FinalisedStateError::Custom(format!(
558 "unhandled rpc-specific {} error: {}",
559 type_name::<T>(),
560 error
561 ))
562 }
563 }
564 }
565}
566
567#[derive(Debug, Clone, thiserror::Error)]
569#[error("Unexpected status error: {server_status:?}")]
570pub struct StatusError {
571 pub server_status: crate::status::StatusType,
572}
573
574#[derive(Debug, thiserror::Error)]
575#[error("{kind}: {message}")]
576pub struct ChainIndexError {
579 pub(crate) kind: ChainIndexErrorKind,
580 pub(crate) message: String,
581 pub(crate) source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
582}
583
584#[derive(Debug, Copy, Clone)]
585#[non_exhaustive]
586pub enum ChainIndexErrorKind {
588 InternalServerError,
590 #[allow(dead_code)]
596 InvalidSnapshot,
597}
598
599impl Display for ChainIndexErrorKind {
600 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
601 f.write_str(match self {
602 ChainIndexErrorKind::InternalServerError => "internal server error",
603 ChainIndexErrorKind::InvalidSnapshot => "invalid snapshot",
604 })
605 }
606}
607
608impl ChainIndexError {
609 pub fn kind(&self) -> ChainIndexErrorKind {
611 self.kind
612 }
613 pub(crate) fn internal(message: impl Into<String>) -> Self {
618 Self {
619 kind: ChainIndexErrorKind::InternalServerError,
620 message: message.into(),
621 source: None,
622 }
623 }
624
625 pub(crate) fn backing_validator(value: impl std::error::Error + Send + Sync + 'static) -> Self {
626 Self {
627 kind: ChainIndexErrorKind::InternalServerError,
628 message: "InternalServerError: error receiving data from backing node".to_string(),
629 source: Some(Box::new(value)),
630 }
631 }
632
633 pub(crate) fn database_hole(
634 missing_block: impl Display,
635 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
636 ) -> Self {
637 Self {
638 kind: ChainIndexErrorKind::InternalServerError,
639 message: format!(
640 "InternalServerError: hole in validator database, missing block {missing_block}"
641 ),
642 source,
643 }
644 }
645
646 pub(crate) fn validator_data_error_block_coinbase_height_missing() -> Self {
647 Self {
648 kind: ChainIndexErrorKind::InternalServerError,
649 message: "validator error: data error: block.coinbase_height() returned None"
650 .to_string(),
651 source: None,
652 }
653 }
654
655 pub(crate) fn child_process_status_error(process: &str, status_err: StatusError) -> Self {
656 use crate::status::StatusType;
657
658 let message = match status_err.server_status {
659 StatusType::Spawning => format!("{process} status: Spawning (not ready yet)"),
660 StatusType::Syncing => format!("{process} status: Syncing (not ready yet)"),
661 StatusType::Ready => format!("{process} status: Ready (unexpected error path)"),
662 StatusType::Busy => format!("{process} status: Busy (temporarily unavailable)"),
663 StatusType::Closing => format!("{process} status: Closing (shutting down)"),
664 StatusType::Offline => format!("{process} status: Offline (not available)"),
665 StatusType::RecoverableError => {
666 format!("{process} status: RecoverableError (retry may succeed)")
667 }
668 StatusType::CriticalError => {
669 format!("{process} status: CriticalError (requires operator action)")
670 }
671 };
672
673 ChainIndexError {
674 kind: ChainIndexErrorKind::InternalServerError,
675 message,
676 source: Some(Box::new(status_err)),
677 }
678 }
679}
680
681impl From<FinalisedStateError> for ChainIndexError {
682 fn from(value: FinalisedStateError) -> Self {
683 let message = match &value {
684 FinalisedStateError::DataUnavailable(err) => format!("unhandled missing data: {err}"),
685 FinalisedStateError::FeatureUnavailable(err) => {
686 format!("unhandled missing feature: {err}")
687 }
688 FinalisedStateError::InvalidBlock {
689 height,
690 hash: _,
691 reason,
692 } => format!("invalid block at height {height}: {reason}"),
693 FinalisedStateError::Custom(err) | FinalisedStateError::Critical(err) => err.clone(),
694 FinalisedStateError::LmdbError(error) => error.to_string(),
695 FinalisedStateError::SerdeJsonError(error) => error.to_string(),
696 FinalisedStateError::StatusError(status_error) => status_error.to_string(),
697 FinalisedStateError::JsonRpcConnectorError(transport_error) => {
698 transport_error.to_string()
699 }
700 FinalisedStateError::IoError(error) => error.to_string(),
701 FinalisedStateError::BlockchainSourceError(blockchain_source_error) => {
702 blockchain_source_error.to_string()
703 }
704 };
705 ChainIndexError {
706 kind: ChainIndexErrorKind::InternalServerError,
707 message,
708 source: Some(Box::new(value)),
709 }
710 }
711}
712
713impl From<MempoolError> for ChainIndexError {
714 fn from(value: MempoolError) -> Self {
715 let message = match &value {
717 MempoolError::Critical(msg) => format!("critical mempool error: {msg}"),
718 MempoolError::IncorrectChainTip {
719 expected_chain_tip,
720 current_chain_tip,
721 } => {
722 format!(
723 "incorrect chain tip (expected {expected_chain_tip:?}, current {current_chain_tip:?})"
724 )
725 }
726 MempoolError::JsonRpcConnectorError(err) => {
727 format!("mempool json-rpc connector error: {err}")
728 }
729 MempoolError::BlockchainSourceError(err) => {
730 format!("mempool blockchain source error: {err}")
731 }
732 MempoolError::WatchRecvError(err) => format!("mempool watch receiver error: {err}"),
733 MempoolError::StatusError(status_err) => {
734 format!("mempool status error: {status_err:?}")
735 }
736 };
737
738 ChainIndexError {
739 kind: ChainIndexErrorKind::InternalServerError,
740 message,
741 source: Some(Box::new(value)),
742 }
743 }
744}