Skip to main content

zaino_state/
error.rs

1#![allow(deprecated)]
2//! Holds error types for Zaino-state.
3
4// Needs to be module level due to the thiserror::Error macro
5
6use 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/// Errors related to the `StateService`.
14// #[deprecated]
15#[derive(Debug, thiserror::Error)]
16#[allow(clippy::result_large_err)]
17pub enum StateServiceError {
18    /// Critical Errors, Restart Zaino.
19    #[error("Critical error: {0}")]
20    Critical(String),
21
22    /// An rpc-specific error we haven't accounted for
23    #[error("unhandled fallible RPC call {0}")]
24    UnhandledRpcError(String),
25    /// Custom Errors. *Remove before production.
26    #[error("Custom error: {0}")]
27    Custom(String),
28
29    /// Error from a Tokio JoinHandle.
30    #[error("Join error: {0}")]
31    JoinError(#[from] tokio::task::JoinError),
32
33    /// Error from JsonRpcConnector.
34    #[error("JsonRpcConnector error: {0}")]
35    JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
36
37    /// RPC error in compatibility with zcashd.
38    #[error("RPC error: {0:?}")]
39    RpcError(#[from] zaino_fetch::jsonrpsee::connector::RpcError),
40
41    /// Chain index error.
42    #[error("Chain index error: {0}")]
43    ChainIndexError(#[from] ChainIndexError),
44
45    /// Error from the block cache.
46    #[error("Mempool error: {0}")]
47    BlockCacheError(#[from] BlockCacheError),
48
49    /// Error from the mempool.
50    #[error("Mempool error: {0}")]
51    MempoolError(#[from] MempoolError),
52
53    /// Tonic gRPC error.
54    #[error("Tonic status error: {0}")]
55    TonicStatusError(#[from] tonic::Status),
56
57    /// Serialization error.
58    #[error("Serialization error: {0}")]
59    SerializationError(#[from] zebra_chain::serialization::SerializationError),
60
61    /// Integer conversion error.
62    #[error("Integer conversion error: {0}")]
63    TryFromIntError(#[from] std::num::TryFromIntError),
64
65    /// std::io::Error
66    #[error("IO error: {0}")]
67    IoError(#[from] std::io::Error),
68
69    /// A generic boxed error.
70    #[error("Generic error: {0}")]
71    Generic(#[from] Box<dyn std::error::Error + Send + Sync>),
72
73    /// The zebrad version and zebra library version do not align
74    #[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        /// The version string or commit hash we specify in Cargo.lock
81        expected_zebrad_version: String,
82        /// The version string of the zebrad, plus its git describe
83        /// information if applicable
84        connected_zebrad_version: String,
85    },
86    #[error("zaino not yet synced")]
87    /// Zaino has not yet synced.
88    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/// Errors related to the `FetchService`.
188#[deprecated]
189#[derive(Debug, thiserror::Error)]
190pub enum FetchServiceError {
191    /// Critical Errors, Restart Zaino.
192    #[error("Critical error: {0}")]
193    Critical(String),
194
195    /// Error from JsonRpcConnector.
196    #[error("JsonRpcConnector error: {0}")]
197    JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
198
199    /// Chain index error.
200    #[error("Chain index error: {0}")]
201    ChainIndexError(#[from] ChainIndexError),
202
203    /// RPC error in compatibility with zcashd.
204    #[error("RPC error: {0:?}")]
205    RpcError(#[from] zaino_fetch::jsonrpsee::connector::RpcError),
206
207    /// Tonic gRPC error.
208    #[error("Tonic status error: {0}")]
209    TonicStatusError(#[from] tonic::Status),
210
211    /// Serialization error.
212    #[error("Serialization error: {0}")]
213    SerializationError(#[from] zebra_chain::serialization::SerializationError),
214    #[error("Zaino has not synced high enough to serve this data")]
215    /// Zaino has not yet synced.
216    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
303/// These aren't the best conversions, but the MempoolError should go away
304/// in favor of a new type with the new chain cache is complete
305impl<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/// Errors related to the `Mempool`.
335#[derive(Debug, thiserror::Error)]
336pub enum MempoolError {
337    /// Critical Errors, Restart Zaino.
338    #[error("Critical error: {0}")]
339    Critical(String),
340
341    /// Incorrect expected chain tip given from client.
342    #[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 from JsonRpcConnector.
351    #[error("JsonRpcConnector error: {0}")]
352    JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
353
354    /// Errors originating from the BlockchainSource in use.
355    #[error("blockchain source error: {0}")]
356    BlockchainSourceError(#[from] crate::chain_index::source::BlockchainSourceError),
357
358    /// Error from a Tokio Watch Receiver.
359    #[error("Join error: {0}")]
360    WatchRecvError(#[from] tokio::sync::watch::error::RecvError),
361
362    /// Unexpected status-related error.
363    #[error("Status error: {0:?}")]
364    StatusError(StatusError),
365}
366
367/// Errors related to the `BlockCache`.
368#[derive(Debug, thiserror::Error)]
369pub enum BlockCacheError {
370    /// Custom Errors. *Remove before production.
371    #[error("Custom error: {0}")]
372    Custom(String),
373
374    /// Critical Errors, Restart Zaino.
375    #[error("Critical error: {0}")]
376    Critical(String),
377
378    /// Errors from the NonFinalisedState.
379    #[error("NonFinalisedState Error: {0}")]
380    NonFinalisedStateError(#[from] NonFinalisedStateError),
381
382    /// Errors from the FinalisedState.
383    #[error("FinalisedState Error: {0}")]
384    FinalisedStateError(#[from] FinalisedStateError),
385
386    /// Error from JsonRpcConnector.
387    #[error("JsonRpcConnector error: {0}")]
388    JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
389
390    /// Chain parse error.
391    #[error("Chain parse error: {0}")]
392    ChainParseError(#[from] zaino_fetch::chain::error::ParseError),
393
394    /// Serialization error.
395    #[error("Serialization error: {0}")]
396    SerializationError(#[from] zebra_chain::serialization::SerializationError),
397
398    /// UTF-8 conversion error.
399    #[error("UTF-8 conversion error: {0}")]
400    Utf8Error(#[from] std::str::Utf8Error),
401
402    /// Integer parsing error.
403    #[error("Integer parsing error: {0}")]
404    ParseIntError(#[from] std::num::ParseIntError),
405
406    /// Integer conversion error.
407    #[error("Integer conversion error: {0}")]
408    TryFromIntError(#[from] std::num::TryFromIntError),
409}
410
411/// Errors related to the `NonFinalisedState`.
412#[derive(Debug, thiserror::Error)]
413pub enum NonFinalisedStateError {
414    /// Custom Errors. *Remove before production.
415    #[error("Custom error: {0}")]
416    Custom(String),
417
418    /// Required data is missing from the non-finalised state.
419    #[error("Missing data: {0}")]
420    MissingData(String),
421
422    /// Critical Errors, Restart Zaino.
423    #[error("Critical error: {0}")]
424    Critical(String),
425
426    /// Error from JsonRpcConnector.
427    #[error("JsonRpcConnector error: {0}")]
428    JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
429
430    /// Unexpected status-related error.
431    #[error("Status error: {0:?}")]
432    StatusError(StatusError),
433}
434
435/// These aren't the best conversions, but the NonFinalizedStateError should go away
436/// in favor of a new type with the new chain cache is complete
437impl<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/// Errors related to the `FinalisedState`.
469// TODO: Update name to DbError when ZainoDB replaces legacy finalised state.
470#[derive(Debug, thiserror::Error)]
471pub enum FinalisedStateError {
472    /// Custom Errors.
473    // TODO: Remove before production
474    #[error("Custom error: {0}")]
475    Custom(String),
476
477    /// Requested data is missing from the finalised state.
478    ///
479    /// This could be due to the databae not yet being synced or due to a bad request input.
480    ///
481    /// We could split this into 2 distinct types if needed.
482    #[error("Missing data: {0}")]
483    DataUnavailable(String),
484
485    /// A block is present on disk but failed internal validation.
486    ///
487    /// *Typically means: checksum mismatch, corrupt CBOR, Merkle check
488    /// failed, etc.*  The caller should fetch the correct data and
489    /// overwrite the faulty block.
490    #[error("invalid block @ height {height} (hash {hash}): {reason}")]
491    InvalidBlock {
492        height: u32,
493        hash: BlockHash,
494        reason: String,
495    },
496
497    /// Returned when a caller asks for a feature that the
498    /// currently-opened database version does not advertise.
499    #[error("feature unavailable: {0}")]
500    FeatureUnavailable(&'static str),
501
502    /// Errors originating from the BlockchainSource in use.
503    #[error("blockchain source error: {0}")]
504    BlockchainSourceError(#[from] crate::chain_index::source::BlockchainSourceError),
505
506    /// Critical Errors, Restart Zaino.
507    #[error("Critical error: {0}")]
508    Critical(String),
509
510    /// Error from the LMDB database.
511    // NOTE: Should this error type be here or should we handle all LMDB errors internally?
512    #[error("LMDB database error: {0}")]
513    LmdbError(#[from] lmdb::Error),
514
515    /// Serde Json serialisation / deserialisation errors.
516    // TODO: Remove when ZainoDB replaces legacy finalised state.
517    #[error("LMDB database error: {0}")]
518    SerdeJsonError(#[from] serde_json::Error),
519
520    /// Unexpected status-related error.
521    #[error("Status error: {0:?}")]
522    StatusError(StatusError),
523
524    /// Error from JsonRpcConnector.
525    // TODO: Remove when ZainoDB replaces legacy finalised state.
526    #[error("JsonRpcConnector error: {0}")]
527    JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
528
529    /// std::io::Error
530    #[error("IO error: {0}")]
531    IoError(#[from] std::io::Error),
532}
533
534/// These aren't the best conversions, but the FinalizedStateError should go away
535/// in favor of a new type with the new chain cache is complete
536impl<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/// A general error type to represent error StatusTypes.
568#[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}")]
576/// The set of errors that can occur during the public API calls
577/// of a NodeBackedChainIndex
578pub 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]
586/// The high-level kinds of thing that can fail
587pub enum ChainIndexErrorKind {
588    /// Zaino is in some way nonfunctional
589    InternalServerError,
590    /// The given snapshot contains invalid data.
591    // This variant isn't used yet...it should indicate
592    // that the provided snapshot contains information unknown to Zebra
593    // Unlike an internal server error, generating a new snapshot may solve
594    // whatever went wrong
595    #[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    /// The error kind
610    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        // Construct a user-facing message depending on the variant
704        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}