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 backing_validator(value: impl std::error::Error + Send + Sync + 'static) -> Self {
614 Self {
615 kind: ChainIndexErrorKind::InternalServerError,
616 message: "InternalServerError: error receiving data from backing node".to_string(),
617 source: Some(Box::new(value)),
618 }
619 }
620
621 pub(crate) fn database_hole(
622 missing_block: impl Display,
623 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
624 ) -> Self {
625 Self {
626 kind: ChainIndexErrorKind::InternalServerError,
627 message: format!(
628 "InternalServerError: hole in validator database, missing block {missing_block}"
629 ),
630 source,
631 }
632 }
633
634 pub(crate) fn validator_data_error_block_coinbase_height_missing() -> Self {
635 Self {
636 kind: ChainIndexErrorKind::InternalServerError,
637 message: "validator error: data error: block.coinbase_height() returned None"
638 .to_string(),
639 source: None,
640 }
641 }
642
643 pub(crate) fn child_process_status_error(process: &str, status_err: StatusError) -> Self {
644 use crate::status::StatusType;
645
646 let message = match status_err.server_status {
647 StatusType::Spawning => format!("{process} status: Spawning (not ready yet)"),
648 StatusType::Syncing => format!("{process} status: Syncing (not ready yet)"),
649 StatusType::Ready => format!("{process} status: Ready (unexpected error path)"),
650 StatusType::Busy => format!("{process} status: Busy (temporarily unavailable)"),
651 StatusType::Closing => format!("{process} status: Closing (shutting down)"),
652 StatusType::Offline => format!("{process} status: Offline (not available)"),
653 StatusType::RecoverableError => {
654 format!("{process} status: RecoverableError (retry may succeed)")
655 }
656 StatusType::CriticalError => {
657 format!("{process} status: CriticalError (requires operator action)")
658 }
659 };
660
661 ChainIndexError {
662 kind: ChainIndexErrorKind::InternalServerError,
663 message,
664 source: Some(Box::new(status_err)),
665 }
666 }
667}
668
669impl From<FinalisedStateError> for ChainIndexError {
670 fn from(value: FinalisedStateError) -> Self {
671 let message = match &value {
672 FinalisedStateError::DataUnavailable(err) => format!("unhandled missing data: {err}"),
673 FinalisedStateError::FeatureUnavailable(err) => {
674 format!("unhandled missing feature: {err}")
675 }
676 FinalisedStateError::InvalidBlock {
677 height,
678 hash: _,
679 reason,
680 } => format!("invalid block at height {height}: {reason}"),
681 FinalisedStateError::Custom(err) | FinalisedStateError::Critical(err) => err.clone(),
682 FinalisedStateError::LmdbError(error) => error.to_string(),
683 FinalisedStateError::SerdeJsonError(error) => error.to_string(),
684 FinalisedStateError::StatusError(status_error) => status_error.to_string(),
685 FinalisedStateError::JsonRpcConnectorError(transport_error) => {
686 transport_error.to_string()
687 }
688 FinalisedStateError::IoError(error) => error.to_string(),
689 FinalisedStateError::BlockchainSourceError(blockchain_source_error) => {
690 blockchain_source_error.to_string()
691 }
692 };
693 ChainIndexError {
694 kind: ChainIndexErrorKind::InternalServerError,
695 message,
696 source: Some(Box::new(value)),
697 }
698 }
699}
700
701impl From<MempoolError> for ChainIndexError {
702 fn from(value: MempoolError) -> Self {
703 let message = match &value {
705 MempoolError::Critical(msg) => format!("critical mempool error: {msg}"),
706 MempoolError::IncorrectChainTip {
707 expected_chain_tip,
708 current_chain_tip,
709 } => {
710 format!(
711 "incorrect chain tip (expected {expected_chain_tip:?}, current {current_chain_tip:?})"
712 )
713 }
714 MempoolError::JsonRpcConnectorError(err) => {
715 format!("mempool json-rpc connector error: {err}")
716 }
717 MempoolError::BlockchainSourceError(err) => {
718 format!("mempool blockchain source error: {err}")
719 }
720 MempoolError::WatchRecvError(err) => format!("mempool watch receiver error: {err}"),
721 MempoolError::StatusError(status_err) => {
722 format!("mempool status error: {status_err:?}")
723 }
724 };
725
726 ChainIndexError {
727 kind: ChainIndexErrorKind::InternalServerError,
728 message,
729 source: Some(Box::new(value)),
730 }
731 }
732}