#![allow(deprecated)]
use crate::BlockHash;
use std::{any::type_name, fmt::Display};
use zaino_fetch::jsonrpsee::connector::RpcRequestError;
use zaino_proto::proto::utils::GetBlockRangeError;
#[derive(Debug, thiserror::Error)]
#[allow(clippy::result_large_err)]
pub enum StateServiceError {
#[error("Critical error: {0}")]
Critical(String),
#[error("unhandled fallible RPC call {0}")]
UnhandledRpcError(String),
#[error("Custom error: {0}")]
Custom(String),
#[error("Join error: {0}")]
JoinError(#[from] tokio::task::JoinError),
#[error("JsonRpcConnector error: {0}")]
JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
#[error("RPC error: {0:?}")]
RpcError(#[from] zaino_fetch::jsonrpsee::connector::RpcError),
#[error("Chain index error: {0}")]
ChainIndexError(#[from] ChainIndexError),
#[error("Mempool error: {0}")]
BlockCacheError(#[from] BlockCacheError),
#[error("Mempool error: {0}")]
MempoolError(#[from] MempoolError),
#[error("Tonic status error: {0}")]
TonicStatusError(#[from] tonic::Status),
#[error("Serialization error: {0}")]
SerializationError(#[from] zebra_chain::serialization::SerializationError),
#[error("Integer conversion error: {0}")]
TryFromIntError(#[from] std::num::TryFromIntError),
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[error("Generic error: {0}")]
Generic(#[from] Box<dyn std::error::Error + Send + Sync>),
#[error(
"zebrad version mismatch. this build of zaino requires a \
version of {expected_zebrad_version}, but the connected zebrad \
is version {connected_zebrad_version}"
)]
ZebradVersionMismatch {
expected_zebrad_version: String,
connected_zebrad_version: String,
},
#[error("zaino not yet synced")]
UnavailableNotSyncedEnough,
}
impl From<GetBlockRangeError> for StateServiceError {
fn from(value: GetBlockRangeError) -> Self {
match value {
GetBlockRangeError::StartHeightOutOfRange => {
Self::TonicStatusError(tonic::Status::out_of_range(
"Error: Start height out of range. Failed to convert to u32.",
))
}
GetBlockRangeError::NoStartHeightProvided => {
Self::TonicStatusError(tonic::Status::out_of_range("Error: No start height given"))
}
GetBlockRangeError::EndHeightOutOfRange => {
Self::TonicStatusError(tonic::Status::out_of_range(
"Error: End height out of range. Failed to convert to u32.",
))
}
GetBlockRangeError::NoEndHeightProvided => {
Self::TonicStatusError(tonic::Status::out_of_range("Error: No end height given."))
}
GetBlockRangeError::PoolTypeArgumentError(_) => {
Self::TonicStatusError(tonic::Status::invalid_argument("Error: invalid pool type"))
}
}
}
}
#[allow(deprecated)]
impl From<StateServiceError> for tonic::Status {
fn from(error: StateServiceError) -> Self {
match error {
StateServiceError::Critical(message) => tonic::Status::internal(message),
StateServiceError::Custom(message) => tonic::Status::internal(message),
StateServiceError::JoinError(err) => {
tonic::Status::internal(format!("Join error: {err}"))
}
StateServiceError::JsonRpcConnectorError(err) => {
tonic::Status::internal(format!("JsonRpcConnector error: {err}"))
}
StateServiceError::RpcError(err) => {
tonic::Status::internal(format!("RPC error: {err:?}"))
}
StateServiceError::ChainIndexError(err) => match err.kind {
ChainIndexErrorKind::InternalServerError => tonic::Status::internal(err.message),
ChainIndexErrorKind::InvalidSnapshot => {
tonic::Status::failed_precondition(err.message)
}
},
StateServiceError::BlockCacheError(err) => {
tonic::Status::internal(format!("BlockCache error: {err:?}"))
}
StateServiceError::MempoolError(err) => {
tonic::Status::internal(format!("Mempool error: {err:?}"))
}
StateServiceError::TonicStatusError(err) => err,
StateServiceError::SerializationError(err) => {
tonic::Status::internal(format!("Serialization error: {err}"))
}
StateServiceError::TryFromIntError(err) => {
tonic::Status::internal(format!("Integer conversion error: {err}"))
}
StateServiceError::IoError(err) => tonic::Status::internal(format!("IO error: {err}")),
StateServiceError::Generic(err) => {
tonic::Status::internal(format!("Generic error: {err}"))
}
ref err @ StateServiceError::ZebradVersionMismatch { .. } => {
tonic::Status::internal(err.to_string())
}
StateServiceError::UnhandledRpcError(e) => tonic::Status::internal(e.to_string()),
StateServiceError::UnavailableNotSyncedEnough => {
tonic::Status::failed_precondition("zaino not yet synced".to_string())
}
}
}
}
impl<T: ToString> From<RpcRequestError<T>> for StateServiceError {
fn from(value: RpcRequestError<T>) -> Self {
match value {
RpcRequestError::Transport(transport_error) => {
Self::JsonRpcConnectorError(transport_error)
}
RpcRequestError::Method(e) => Self::UnhandledRpcError(format!(
"{}: {}",
std::any::type_name::<T>(),
e.to_string()
)),
RpcRequestError::JsonRpc(error) => Self::Custom(format!("bad argument: {error}")),
RpcRequestError::InternalUnrecoverable(e) => Self::Custom(e.to_string()),
RpcRequestError::ServerWorkQueueFull => {
Self::Custom("Server queue full. Handling for this not yet implemented".to_string())
}
RpcRequestError::UnexpectedErrorResponse(error) => Self::Custom(format!("{error}")),
}
}
}
#[deprecated]
#[derive(Debug, thiserror::Error)]
pub enum FetchServiceError {
#[error("Critical error: {0}")]
Critical(String),
#[error("JsonRpcConnector error: {0}")]
JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
#[error("Chain index error: {0}")]
ChainIndexError(#[from] ChainIndexError),
#[error("RPC error: {0:?}")]
RpcError(#[from] zaino_fetch::jsonrpsee::connector::RpcError),
#[error("Tonic status error: {0}")]
TonicStatusError(#[from] tonic::Status),
#[error("Serialization error: {0}")]
SerializationError(#[from] zebra_chain::serialization::SerializationError),
#[error("Zaino has not synced high enough to serve this data")]
UnavailableNotSyncedEnough,
}
impl From<FetchServiceError> for tonic::Status {
fn from(error: FetchServiceError) -> Self {
match error {
FetchServiceError::Critical(message) => tonic::Status::internal(message),
FetchServiceError::JsonRpcConnectorError(err) => {
tonic::Status::internal(format!("JsonRpcConnector error: {err}"))
}
FetchServiceError::ChainIndexError(err) => match err.kind {
ChainIndexErrorKind::InternalServerError => tonic::Status::internal(err.message),
ChainIndexErrorKind::InvalidSnapshot => {
tonic::Status::failed_precondition(err.message)
}
},
FetchServiceError::RpcError(err) => {
tonic::Status::internal(format!("RPC error: {err:?}"))
}
FetchServiceError::TonicStatusError(err) => err,
FetchServiceError::SerializationError(err) => {
tonic::Status::internal(format!("Serialization error: {err}"))
}
FetchServiceError::UnavailableNotSyncedEnough => {
tonic::Status::failed_precondition("zaino not yet synced".to_string())
}
}
}
}
impl<T: ToString> From<RpcRequestError<T>> for FetchServiceError {
fn from(value: RpcRequestError<T>) -> Self {
match value {
RpcRequestError::Transport(transport_error) => {
FetchServiceError::JsonRpcConnectorError(transport_error)
}
RpcRequestError::JsonRpc(error) => {
FetchServiceError::Critical(format!("argument failed to serialze: {error}"))
}
RpcRequestError::InternalUnrecoverable(e) => {
FetchServiceError::Critical(format!("Internal unrecoverable error: {e}"))
}
RpcRequestError::ServerWorkQueueFull => FetchServiceError::Critical(
"Server queue full. Handling for this not yet implemented".to_string(),
),
RpcRequestError::Method(e) => FetchServiceError::Critical(format!(
"unhandled rpc-specific {} error: {}",
type_name::<T>(),
e.to_string()
)),
RpcRequestError::UnexpectedErrorResponse(error) => {
FetchServiceError::Critical(format!(
"unhandled rpc-specific {} error: {}",
type_name::<T>(),
error
))
}
}
}
}
impl From<GetBlockRangeError> for FetchServiceError {
fn from(value: GetBlockRangeError) -> Self {
match value {
GetBlockRangeError::StartHeightOutOfRange => {
FetchServiceError::TonicStatusError(tonic::Status::out_of_range(
"Error: Start height out of range. Failed to convert to u32.",
))
}
GetBlockRangeError::NoStartHeightProvided => FetchServiceError::TonicStatusError(
tonic::Status::out_of_range("Error: No start height given"),
),
GetBlockRangeError::EndHeightOutOfRange => {
FetchServiceError::TonicStatusError(tonic::Status::out_of_range(
"Error: End height out of range. Failed to convert to u32.",
))
}
GetBlockRangeError::NoEndHeightProvided => FetchServiceError::TonicStatusError(
tonic::Status::out_of_range("Error: No end height given."),
),
GetBlockRangeError::PoolTypeArgumentError(_) => FetchServiceError::TonicStatusError(
tonic::Status::invalid_argument("Error: invalid pool type"),
),
}
}
}
impl<T: ToString> From<RpcRequestError<T>> for MempoolError {
fn from(value: RpcRequestError<T>) -> Self {
match value {
RpcRequestError::Transport(transport_error) => {
MempoolError::JsonRpcConnectorError(transport_error)
}
RpcRequestError::JsonRpc(error) => {
MempoolError::Critical(format!("argument failed to serialze: {error}"))
}
RpcRequestError::InternalUnrecoverable(e) => {
MempoolError::Critical(format!("Internal unrecoverable error: {e}"))
}
RpcRequestError::ServerWorkQueueFull => MempoolError::Critical(
"Server queue full. Handling for this not yet implemented".to_string(),
),
RpcRequestError::Method(e) => MempoolError::Critical(format!(
"unhandled rpc-specific {} error: {}",
type_name::<T>(),
e.to_string()
)),
RpcRequestError::UnexpectedErrorResponse(error) => MempoolError::Critical(format!(
"unhandled rpc-specific {} error: {}",
type_name::<T>(),
error
)),
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum MempoolError {
#[error("Critical error: {0}")]
Critical(String),
#[error(
"Incorrect chain tip (expected {expected_chain_tip:?}, current {current_chain_tip:?})"
)]
IncorrectChainTip {
expected_chain_tip: BlockHash,
current_chain_tip: BlockHash,
},
#[error("JsonRpcConnector error: {0}")]
JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
#[error("blockchain source error: {0}")]
BlockchainSourceError(#[from] crate::chain_index::source::BlockchainSourceError),
#[error("Join error: {0}")]
WatchRecvError(#[from] tokio::sync::watch::error::RecvError),
#[error("Status error: {0:?}")]
StatusError(StatusError),
}
#[derive(Debug, thiserror::Error)]
pub enum BlockCacheError {
#[error("Custom error: {0}")]
Custom(String),
#[error("Critical error: {0}")]
Critical(String),
#[error("NonFinalisedState Error: {0}")]
NonFinalisedStateError(#[from] NonFinalisedStateError),
#[error("FinalisedState Error: {0}")]
FinalisedStateError(#[from] FinalisedStateError),
#[error("JsonRpcConnector error: {0}")]
JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
#[error("Chain parse error: {0}")]
ChainParseError(#[from] zaino_fetch::chain::error::ParseError),
#[error("Serialization error: {0}")]
SerializationError(#[from] zebra_chain::serialization::SerializationError),
#[error("UTF-8 conversion error: {0}")]
Utf8Error(#[from] std::str::Utf8Error),
#[error("Integer parsing error: {0}")]
ParseIntError(#[from] std::num::ParseIntError),
#[error("Integer conversion error: {0}")]
TryFromIntError(#[from] std::num::TryFromIntError),
}
#[derive(Debug, thiserror::Error)]
pub enum NonFinalisedStateError {
#[error("Custom error: {0}")]
Custom(String),
#[error("Missing data: {0}")]
MissingData(String),
#[error("Critical error: {0}")]
Critical(String),
#[error("JsonRpcConnector error: {0}")]
JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
#[error("Status error: {0:?}")]
StatusError(StatusError),
}
impl<T: ToString> From<RpcRequestError<T>> for NonFinalisedStateError {
fn from(value: RpcRequestError<T>) -> Self {
match value {
RpcRequestError::Transport(transport_error) => {
NonFinalisedStateError::JsonRpcConnectorError(transport_error)
}
RpcRequestError::JsonRpc(error) => {
NonFinalisedStateError::Custom(format!("argument failed to serialze: {error}"))
}
RpcRequestError::InternalUnrecoverable(e) => {
NonFinalisedStateError::Custom(format!("Internal unrecoverable error: {e}"))
}
RpcRequestError::ServerWorkQueueFull => NonFinalisedStateError::Custom(
"Server queue full. Handling for this not yet implemented".to_string(),
),
RpcRequestError::Method(e) => NonFinalisedStateError::Custom(format!(
"unhandled rpc-specific {} error: {}",
type_name::<T>(),
e.to_string()
)),
RpcRequestError::UnexpectedErrorResponse(error) => {
NonFinalisedStateError::Custom(format!(
"unhandled rpc-specific {} error: {}",
type_name::<T>(),
error
))
}
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum FinalisedStateError {
#[error("Custom error: {0}")]
Custom(String),
#[error("Missing data: {0}")]
DataUnavailable(String),
#[error("invalid block @ height {height} (hash {hash}): {reason}")]
InvalidBlock {
height: u32,
hash: BlockHash,
reason: String,
},
#[error("feature unavailable: {0}")]
FeatureUnavailable(&'static str),
#[error("blockchain source error: {0}")]
BlockchainSourceError(#[from] crate::chain_index::source::BlockchainSourceError),
#[error("Critical error: {0}")]
Critical(String),
#[error("LMDB database error: {0}")]
LmdbError(#[from] lmdb::Error),
#[error("LMDB database error: {0}")]
SerdeJsonError(#[from] serde_json::Error),
#[error("Status error: {0:?}")]
StatusError(StatusError),
#[error("JsonRpcConnector error: {0}")]
JsonRpcConnectorError(#[from] zaino_fetch::jsonrpsee::error::TransportError),
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
}
impl<T: ToString> From<RpcRequestError<T>> for FinalisedStateError {
fn from(value: RpcRequestError<T>) -> Self {
match value {
RpcRequestError::Transport(transport_error) => {
FinalisedStateError::JsonRpcConnectorError(transport_error)
}
RpcRequestError::JsonRpc(error) => {
FinalisedStateError::Custom(format!("argument failed to serialze: {error}"))
}
RpcRequestError::InternalUnrecoverable(e) => {
FinalisedStateError::Custom(format!("Internal unrecoverable error: {e}"))
}
RpcRequestError::ServerWorkQueueFull => FinalisedStateError::Custom(
"Server queue full. Handling for this not yet implemented".to_string(),
),
RpcRequestError::Method(e) => FinalisedStateError::Custom(format!(
"unhandled rpc-specific {} error: {}",
type_name::<T>(),
e.to_string()
)),
RpcRequestError::UnexpectedErrorResponse(error) => {
FinalisedStateError::Custom(format!(
"unhandled rpc-specific {} error: {}",
type_name::<T>(),
error
))
}
}
}
}
#[derive(Debug, Clone, thiserror::Error)]
#[error("Unexpected status error: {server_status:?}")]
pub struct StatusError {
pub server_status: crate::status::StatusType,
}
#[derive(Debug, thiserror::Error)]
#[error("{kind}: {message}")]
pub struct ChainIndexError {
pub(crate) kind: ChainIndexErrorKind,
pub(crate) message: String,
pub(crate) source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
}
#[derive(Debug, Copy, Clone)]
#[non_exhaustive]
pub enum ChainIndexErrorKind {
InternalServerError,
#[allow(dead_code)]
InvalidSnapshot,
}
impl Display for ChainIndexErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
ChainIndexErrorKind::InternalServerError => "internal server error",
ChainIndexErrorKind::InvalidSnapshot => "invalid snapshot",
})
}
}
impl ChainIndexError {
pub fn kind(&self) -> ChainIndexErrorKind {
self.kind
}
pub(crate) fn backing_validator(value: impl std::error::Error + Send + Sync + 'static) -> Self {
Self {
kind: ChainIndexErrorKind::InternalServerError,
message: "InternalServerError: error receiving data from backing node".to_string(),
source: Some(Box::new(value)),
}
}
pub(crate) fn database_hole(
missing_block: impl Display,
source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
) -> Self {
Self {
kind: ChainIndexErrorKind::InternalServerError,
message: format!(
"InternalServerError: hole in validator database, missing block {missing_block}"
),
source,
}
}
pub(crate) fn validator_data_error_block_coinbase_height_missing() -> Self {
Self {
kind: ChainIndexErrorKind::InternalServerError,
message: "validator error: data error: block.coinbase_height() returned None"
.to_string(),
source: None,
}
}
pub(crate) fn child_process_status_error(process: &str, status_err: StatusError) -> Self {
use crate::status::StatusType;
let message = match status_err.server_status {
StatusType::Spawning => format!("{process} status: Spawning (not ready yet)"),
StatusType::Syncing => format!("{process} status: Syncing (not ready yet)"),
StatusType::Ready => format!("{process} status: Ready (unexpected error path)"),
StatusType::Busy => format!("{process} status: Busy (temporarily unavailable)"),
StatusType::Closing => format!("{process} status: Closing (shutting down)"),
StatusType::Offline => format!("{process} status: Offline (not available)"),
StatusType::RecoverableError => {
format!("{process} status: RecoverableError (retry may succeed)")
}
StatusType::CriticalError => {
format!("{process} status: CriticalError (requires operator action)")
}
};
ChainIndexError {
kind: ChainIndexErrorKind::InternalServerError,
message,
source: Some(Box::new(status_err)),
}
}
}
impl From<FinalisedStateError> for ChainIndexError {
fn from(value: FinalisedStateError) -> Self {
let message = match &value {
FinalisedStateError::DataUnavailable(err) => format!("unhandled missing data: {err}"),
FinalisedStateError::FeatureUnavailable(err) => {
format!("unhandled missing feature: {err}")
}
FinalisedStateError::InvalidBlock {
height,
hash: _,
reason,
} => format!("invalid block at height {height}: {reason}"),
FinalisedStateError::Custom(err) | FinalisedStateError::Critical(err) => err.clone(),
FinalisedStateError::LmdbError(error) => error.to_string(),
FinalisedStateError::SerdeJsonError(error) => error.to_string(),
FinalisedStateError::StatusError(status_error) => status_error.to_string(),
FinalisedStateError::JsonRpcConnectorError(transport_error) => {
transport_error.to_string()
}
FinalisedStateError::IoError(error) => error.to_string(),
FinalisedStateError::BlockchainSourceError(blockchain_source_error) => {
blockchain_source_error.to_string()
}
};
ChainIndexError {
kind: ChainIndexErrorKind::InternalServerError,
message,
source: Some(Box::new(value)),
}
}
}
impl From<MempoolError> for ChainIndexError {
fn from(value: MempoolError) -> Self {
let message = match &value {
MempoolError::Critical(msg) => format!("critical mempool error: {msg}"),
MempoolError::IncorrectChainTip {
expected_chain_tip,
current_chain_tip,
} => {
format!(
"incorrect chain tip (expected {expected_chain_tip:?}, current {current_chain_tip:?})"
)
}
MempoolError::JsonRpcConnectorError(err) => {
format!("mempool json-rpc connector error: {err}")
}
MempoolError::BlockchainSourceError(err) => {
format!("mempool blockchain source error: {err}")
}
MempoolError::WatchRecvError(err) => format!("mempool watch receiver error: {err}"),
MempoolError::StatusError(status_err) => {
format!("mempool status error: {status_err:?}")
}
};
ChainIndexError {
kind: ChainIndexErrorKind::InternalServerError,
message,
source: Some(Box::new(value)),
}
}
}