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 returned by the [`NodeBackedIndexerService`](crate::NodeBackedIndexerService)
14/// subscriber's `ZcashIndexer` / `LightWalletIndexer` methods.
15#[derive(Debug, thiserror::Error)]
16#[allow(clippy::result_large_err)]
17pub enum NodeBackedIndexerServiceError {
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 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
191/// These aren't the best conversions, but the MempoolError should go away
192/// in favor of a new type with the new chain cache is complete
193impl<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/// Errors related to the `Mempool`.
223#[derive(Debug, thiserror::Error)]
224pub enum MempoolError {
225    /// Critical Errors, Restart Zaino.
226    #[error("Critical error: {0}")]
227    Critical(String),
228
229    /// Incorrect expected chain tip given from client.
230    #[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 from JsonRpcConnector.
239    #[error("JsonRpcConnector error: {0}")]
240    JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
241
242    /// Errors originating from the BlockchainSource in use.
243    #[error("blockchain source error: {0}")]
244    BlockchainSourceError(#[from] crate::chain_index::source::BlockchainSourceError),
245
246    /// Error from a Tokio Watch Receiver.
247    #[error("Join error: {0}")]
248    WatchRecvError(#[from] tokio::sync::watch::error::RecvError),
249
250    /// Unexpected status-related error.
251    #[error("Status error: {0:?}")]
252    StatusError(StatusError),
253}
254
255/// Errors related to the `BlockCache`.
256#[derive(Debug, thiserror::Error)]
257pub enum BlockCacheError {
258    /// Custom Errors. *Remove before production.
259    #[error("Custom error: {0}")]
260    Custom(String),
261
262    /// Critical Errors, Restart Zaino.
263    #[error("Critical error: {0}")]
264    Critical(String),
265
266    /// Errors from the NonFinalisedState.
267    #[error("NonFinalisedState Error: {0}")]
268    NonFinalisedStateError(#[from] NonFinalisedStateError),
269
270    /// Errors from the FinalisedState.
271    #[error("FinalisedState Error: {0}")]
272    FinalisedStateError(#[from] FinalisedStateError),
273
274    /// Error from JsonRpcConnector.
275    #[error("JsonRpcConnector error: {0}")]
276    JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
277
278    /// Chain parse error.
279    #[error("Chain parse error: {0}")]
280    ChainParseError(#[from] zaino_fetch::chain::error::ParseError),
281
282    /// Serialization error.
283    #[error("Serialization error: {0}")]
284    SerializationError(#[from] zebra_chain::serialization::SerializationError),
285
286    /// UTF-8 conversion error.
287    #[error("UTF-8 conversion error: {0}")]
288    Utf8Error(#[from] std::str::Utf8Error),
289
290    /// Integer parsing error.
291    #[error("Integer parsing error: {0}")]
292    ParseIntError(#[from] std::num::ParseIntError),
293
294    /// Integer conversion error.
295    #[error("Integer conversion error: {0}")]
296    TryFromIntError(#[from] std::num::TryFromIntError),
297}
298
299/// Errors related to the `NonFinalisedState`.
300#[derive(Debug, thiserror::Error)]
301pub enum NonFinalisedStateError {
302    /// Custom Errors. *Remove before production.
303    #[error("Custom error: {0}")]
304    Custom(String),
305
306    /// Required data is missing from the non-finalised state.
307    #[error("Missing data: {0}")]
308    MissingData(String),
309
310    /// Critical Errors, Restart Zaino.
311    #[error("Critical error: {0}")]
312    Critical(String),
313
314    /// Error from JsonRpcConnector.
315    #[error("JsonRpcConnector error: {0}")]
316    JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
317
318    /// Unexpected status-related error.
319    #[error("Status error: {0:?}")]
320    StatusError(StatusError),
321}
322
323/// These aren't the best conversions, but the NonFinalizedStateError should go away
324/// in favor of a new type with the new chain cache is complete
325impl<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/// Errors related to the `FinalisedState`.
357// TODO: Update name to DbError when FinalisedState replaces legacy finalised state.
358#[derive(Debug, thiserror::Error)]
359pub enum FinalisedStateError {
360    /// Custom Errors.
361    // TODO: Remove before production
362    #[error("Custom error: {0}")]
363    Custom(String),
364
365    /// Requested data is missing from the finalised state.
366    ///
367    /// This could be due to the databae not yet being synced or due to a bad request input.
368    ///
369    /// We could split this into 2 distinct types if needed.
370    #[error("Missing data: {0}")]
371    DataUnavailable(String),
372
373    /// A block is present on disk but failed internal validation.
374    ///
375    /// *Typically means: checksum mismatch, corrupt CBOR, Merkle check
376    /// failed, etc.*  The caller should fetch the correct data and
377    /// overwrite the faulty block.
378    #[error("invalid block @ height {height} (hash {hash}): {reason}")]
379    InvalidBlock {
380        height: u32,
381        hash: BlockHash,
382        reason: String,
383    },
384
385    /// Returned when a caller asks for a feature that the
386    /// currently-opened database version does not advertise.
387    #[error("feature unavailable: {0}")]
388    FeatureUnavailable(&'static str),
389
390    /// Errors originating from the BlockchainSource in use.
391    #[error("blockchain source error: {0}")]
392    BlockchainSourceError(#[from] crate::chain_index::source::BlockchainSourceError),
393
394    /// Critical Errors, Restart Zaino.
395    #[error("Critical error: {0}")]
396    Critical(String),
397
398    /// Error from the LMDB database.
399    // NOTE: Should this error type be here or should we handle all LMDB errors internally?
400    #[error("LMDB database error: {0}")]
401    LmdbError(#[from] lmdb::Error),
402
403    /// Serde Json serialisation / deserialisation errors.
404    // TODO: Remove when FinalisedState replaces legacy finalised state.
405    #[error("LMDB database error: {0}")]
406    SerdeJsonError(#[from] serde_json::Error),
407
408    /// Unexpected status-related error.
409    #[error("Status error: {0:?}")]
410    StatusError(StatusError),
411
412    /// Error from JsonRpcConnector.
413    // TODO: Remove when FinalisedState replaces legacy finalised state.
414    #[error("JsonRpcConnector error: {0}")]
415    JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
416
417    /// std::io::Error
418    #[error("IO error: {0}")]
419    IoError(#[from] std::io::Error),
420}
421
422/// These aren't the best conversions, but the FinalizedStateError should go away
423/// in favor of a new type with the new chain cache is complete
424impl<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/// A general error type to represent error StatusTypes.
456#[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}")]
464/// The set of errors that can occur during the public API calls
465/// of a NodeBackedChainIndex
466pub 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]
474/// The high-level kinds of thing that can fail
475pub enum ChainIndexErrorKind {
476    /// Zaino is in some way nonfunctional
477    InternalServerError,
478    /// The given snapshot contains invalid data.
479    // This variant isn't used yet...it should indicate
480    // that the provided snapshot contains information unknown to Zebra
481    // Unlike an internal server error, generating a new snapshot may solve
482    // whatever went wrong
483    #[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    /// The error kind
498    pub fn kind(&self) -> ChainIndexErrorKind {
499        self.kind
500    }
501    /// Constructs an `InternalServerError`-kind error with a free-form message.
502    ///
503    /// Intended for call sites that produce a string error and have no underlying
504    /// `std::error::Error` source to attach.
505    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    /// Constructs an `InternalServerError`-kind error from a typed error,
514    /// preserving it as `source` so zaino-serve's RPC-error-code recovery
515    /// walks can downcast to it (e.g. the legacy `-8` code carried by an
516    /// [`RpcError`](zaino_fetch::jsonrpsee::connector::RpcError)).
517    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        // Construct a user-facing message depending on the variant
616        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}