use crate::{BlockNumberOf, Chain, HashOf, SimpleRuntimeVersion};
use bp_header_chain::SubmitFinalityProofCallExtras;
use bp_polkadot_core::parachains::ParaId;
use jsonrpsee::core::ClientError as RpcError;
use relay_utils::MaybeConnectionError;
use sc_rpc_api::system::Health;
use sp_core::{storage::StorageKey, Bytes};
use sp_runtime::transaction_validity::TransactionValidityError;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Error, Debug)]
pub enum Error {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("RPC error: {0}")]
RpcError(#[from] RpcError),
#[error("Response parse failed: {0}")]
ResponseParseFailed(#[from] codec::Error),
#[error("Internal communication channel error: {0:?}.")]
ChannelError(String),
#[error("Parachain {0:?} head {1} is missing from the relay chain storage.")]
MissingRequiredParachainHead(ParaId, u64),
#[error("Failed to find finality proof for header {0}.")]
FinalityProofNotFound(u64),
#[error("Substrate client is not synced {0}.")]
ClientNotSynced(Health),
#[error("Failed to get system health of {chain} node: {error:?}.")]
FailedToGetSystemHealth {
chain: String,
error: Box<Error>,
},
#[error("Failed to read best finalized header hash of {chain}: {error:?}.")]
FailedToReadBestFinalizedHeaderHash {
chain: String,
error: Box<Error>,
},
#[error("Failed to read best header of {chain}: {error:?}.")]
FailedToReadBestHeader {
chain: String,
error: Box<Error>,
},
#[error("Failed to read header hash by number {number} of {chain}: {error:?}.")]
FailedToReadHeaderHashByNumber {
chain: String,
number: String,
error: Box<Error>,
},
#[error("Failed to read header {hash} of {chain}: {error:?}.")]
FailedToReadHeaderByHash {
chain: String,
hash: String,
error: Box<Error>,
},
#[error("Failed to read block {hash} of {chain}: {error:?}.")]
FailedToReadBlockByHash {
chain: String,
hash: String,
error: Box<Error>,
},
#[error("Failed to read storage value {key:?} at {chain}: {error:?}.")]
FailedToReadStorageValue {
chain: String,
hash: String,
key: StorageKey,
error: Box<Error>,
},
#[error("Failed to read runtime version of {chain}: {error:?}.")]
FailedToReadRuntimeVersion {
chain: String,
error: Box<Error>,
},
#[error("Failed to get pending extrinsics of {chain}: {error:?}.")]
FailedToGetPendingExtrinsics {
chain: String,
error: Box<Error>,
},
#[error("Failed to submit {chain} transaction: {error:?}.")]
FailedToSubmitTransaction {
chain: String,
error: Box<Error>,
},
#[error("Runtime call {method} with arguments {arguments:?} of chain {chain} at {hash} has failed: {error:?}.")]
FailedStateCall {
chain: String,
hash: String,
method: String,
arguments: Bytes,
error: Box<Error>,
},
#[error("Failed to prove storage keys {storage_keys:?} of {chain} at {hash}: {error:?}.")]
FailedToProveStorage {
chain: String,
hash: String,
storage_keys: Vec<StorageKey>,
error: Box<Error>,
},
#[error("Failed to subscribe to {chain} best headers: {error:?}.")]
FailedToSubscribeBestHeaders {
chain: String,
error: Box<Error>,
},
#[error("Failed to subscribe to {chain} finalized headers: {error:?}.")]
FailedToSubscribeFinalizedHeaders {
chain: String,
error: Box<Error>,
},
#[error("Failed to subscribe to {chain} justifications: {error:?}.")]
FailedToSubscribeJustifications {
chain: String,
error: Box<Error>,
},
#[error("Finalized headers of {chain} are unordered: previously finalized {prev_number} vs new {next_number}")]
UnorderedFinalizedHeaders {
chain: String,
prev_number: String,
next_number: String,
},
#[error("Bridge pallet is halted.")]
BridgePalletIsHalted,
#[error("Bridge pallet is not initialized.")]
BridgePalletIsNotInitialized,
#[error("Substrate transaction is invalid: {0:?}")]
TransactionInvalid(#[from] TransactionValidityError),
#[error("Waiting for {chain} runtime upgrade: expected {expected:?} actual {actual:?}")]
WaitingForRuntimeUpgrade {
chain: String,
expected: SimpleRuntimeVersion,
actual: SimpleRuntimeVersion,
},
#[error("Finality proof submission exceeds limits: {extras:?}")]
FinalityProofWeightLimitExceeded {
extras: SubmitFinalityProofCallExtras,
},
#[error("{0}")]
Custom(String),
}
impl From<tokio::task::JoinError> for Error {
fn from(error: tokio::task::JoinError) -> Self {
Error::ChannelError(format!("failed to wait tokio task: {error}"))
}
}
impl<T> From<async_std::channel::TrySendError<T>> for Error {
fn from(error: async_std::channel::TrySendError<T>) -> Self {
Error::ChannelError(format!("`try_send` has failed: {error:?}"))
}
}
impl From<async_std::channel::RecvError> for Error {
fn from(error: async_std::channel::RecvError) -> Self {
Error::ChannelError(format!("`recv` has failed: {error:?}"))
}
}
impl Error {
pub fn boxed(self) -> Box<Self> {
Box::new(self)
}
pub fn nested(&self) -> Option<&Self> {
match *self {
Self::FailedToReadBestFinalizedHeaderHash { ref error, .. } => Some(&**error),
Self::FailedToReadBestHeader { ref error, .. } => Some(&**error),
Self::FailedToReadHeaderHashByNumber { ref error, .. } => Some(&**error),
Self::FailedToReadHeaderByHash { ref error, .. } => Some(&**error),
Self::FailedToReadBlockByHash { ref error, .. } => Some(&**error),
Self::FailedToReadStorageValue { ref error, .. } => Some(&**error),
Self::FailedToReadRuntimeVersion { ref error, .. } => Some(&**error),
Self::FailedToGetPendingExtrinsics { ref error, .. } => Some(&**error),
Self::FailedToSubmitTransaction { ref error, .. } => Some(&**error),
Self::FailedStateCall { ref error, .. } => Some(&**error),
Self::FailedToProveStorage { ref error, .. } => Some(&**error),
Self::FailedToGetSystemHealth { ref error, .. } => Some(&**error),
Self::FailedToSubscribeBestHeaders { ref error, .. } => Some(&**error),
Self::FailedToSubscribeFinalizedHeaders { ref error, .. } => Some(&**error),
Self::FailedToSubscribeJustifications { ref error, .. } => Some(&**error),
_ => None,
}
}
pub fn failed_to_read_header_hash_by_number<C: Chain>(
number: BlockNumberOf<C>,
e: Error,
) -> Self {
Error::FailedToReadHeaderHashByNumber {
chain: C::NAME.into(),
number: format!("{number}"),
error: e.boxed(),
}
}
pub fn failed_to_read_header_by_hash<C: Chain>(hash: HashOf<C>, e: Error) -> Self {
Error::FailedToReadHeaderByHash {
chain: C::NAME.into(),
hash: format!("{hash}"),
error: e.boxed(),
}
}
pub fn failed_to_read_block_by_hash<C: Chain>(hash: HashOf<C>, e: Error) -> Self {
Error::FailedToReadHeaderByHash {
chain: C::NAME.into(),
hash: format!("{hash}"),
error: e.boxed(),
}
}
pub fn failed_to_read_best_finalized_header_hash<C: Chain>(e: Error) -> Self {
Error::FailedToReadBestFinalizedHeaderHash { chain: C::NAME.into(), error: e.boxed() }
}
pub fn failed_to_read_best_header<C: Chain>(e: Error) -> Self {
Error::FailedToReadBestHeader { chain: C::NAME.into(), error: e.boxed() }
}
pub fn failed_to_read_runtime_version<C: Chain>(e: Error) -> Self {
Error::FailedToReadRuntimeVersion { chain: C::NAME.into(), error: e.boxed() }
}
pub fn failed_to_read_storage_value<C: Chain>(
at: HashOf<C>,
key: StorageKey,
e: Error,
) -> Self {
Error::FailedToReadStorageValue {
chain: C::NAME.into(),
hash: format!("{at}"),
key,
error: e.boxed(),
}
}
pub fn failed_to_get_pending_extrinsics<C: Chain>(e: Error) -> Self {
Error::FailedToGetPendingExtrinsics { chain: C::NAME.into(), error: e.boxed() }
}
pub fn failed_to_submit_transaction<C: Chain>(e: Error) -> Self {
Error::FailedToSubmitTransaction { chain: C::NAME.into(), error: e.boxed() }
}
pub fn failed_state_call<C: Chain>(
at: HashOf<C>,
method: String,
arguments: Bytes,
e: Error,
) -> Self {
Error::FailedStateCall {
chain: C::NAME.into(),
hash: format!("{at}"),
method,
arguments,
error: e.boxed(),
}
}
pub fn failed_to_prove_storage<C: Chain>(
at: HashOf<C>,
storage_keys: Vec<StorageKey>,
e: Error,
) -> Self {
Error::FailedToProveStorage {
chain: C::NAME.into(),
hash: format!("{at}"),
storage_keys,
error: e.boxed(),
}
}
pub fn failed_to_get_system_health<C: Chain>(e: Error) -> Self {
Error::FailedToGetSystemHealth { chain: C::NAME.into(), error: e.boxed() }
}
pub fn failed_to_subscribe_best_headers<C: Chain>(e: Error) -> Self {
Error::FailedToSubscribeBestHeaders { chain: C::NAME.into(), error: e.boxed() }
}
pub fn failed_to_subscribe_finalized_headers<C: Chain>(e: Error) -> Self {
Error::FailedToSubscribeFinalizedHeaders { chain: C::NAME.into(), error: e.boxed() }
}
pub fn failed_to_subscribe_justification<C: Chain>(e: Error) -> Self {
Error::FailedToSubscribeJustifications { chain: C::NAME.into(), error: e.boxed() }
}
pub fn unordered_finalized_headers<C: Chain>(
prev_number: BlockNumberOf<C>,
next_number: BlockNumberOf<C>,
) -> Self {
Error::UnorderedFinalizedHeaders {
chain: C::NAME.into(),
prev_number: format!("{}", prev_number),
next_number: format!("{}", next_number),
}
}
}
impl MaybeConnectionError for Error {
fn is_connection_error(&self) -> bool {
match *self {
Error::ChannelError(_) => true,
Error::RpcError(ref e) =>
matches!(*e, RpcError::Transport(_) | RpcError::RestartNeeded(_),),
Error::ClientNotSynced(_) => true,
Error::UnorderedFinalizedHeaders { .. } => true,
_ => self.nested().map(|e| e.is_connection_error()).unwrap_or(false),
}
}
}