diff --git a/Cargo.lock b/Cargo.lock index 40b9fbb47b..eec7633115 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -792,6 +792,16 @@ dependencies = [ "termcolor", ] +[[package]] +name = "eyre" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "221239d1d5ea86bf5d6f91c9d6bc3646ffe471b08ff9b0f91c44f115ac969d2b" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "ff" version = "0.10.0" @@ -814,6 +824,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "flex-error" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c21e3345cc1318971ddb5d04add3b4eaa970349e4f3e1d870535173cb745cf" +dependencies = [ + "eyre", + "paste", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1316,11 +1336,11 @@ dependencies = [ name = "ibc" version = "0.6.1" dependencies = [ - "anomaly", "bytes", "chrono", "dyn-clonable", "env_logger", + "flex-error", "ibc-proto", "ics23", "modelator", @@ -1346,12 +1366,10 @@ dependencies = [ name = "ibc-proto" version = "0.9.0" dependencies = [ - "anomaly", "bytes", "prost", "prost-types", "tendermint-proto", - "thiserror", "tonic", ] @@ -1359,7 +1377,7 @@ dependencies = [ name = "ibc-relayer" version = "0.6.1" dependencies = [ - "anomaly", + "anyhow", "async-stream", "async-trait", "bech32", @@ -1370,10 +1388,12 @@ dependencies = [ "dyn-clonable", "dyn-clone", "env_logger", + "flex-error", "fraction", "futures", "hdpath", "hex", + "http", "humantime-serde", "ibc", "ibc-proto", @@ -1391,6 +1411,7 @@ dependencies = [ "serde_json", "serial_test", "sha2", + "signature", "sled", "subtle-encoding", "tendermint", @@ -1413,10 +1434,10 @@ name = "ibc-relayer-cli" version = "0.6.1" dependencies = [ "abscissa_core", - "anomaly", "atty", "crossbeam-channel 0.5.1", "dirs-next", + "flex-error", "futures", "gumdrop 0.7.0", "hex", @@ -1439,7 +1460,6 @@ dependencies = [ "tendermint-light-client", "tendermint-proto", "tendermint-rpc", - "thiserror", "tokio", "toml", "tracing", @@ -1491,6 +1511,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" version = "1.7.0" @@ -1978,6 +2004,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "paste" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58" + [[package]] name = "pbkdf2" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 82a431efe2..7dfbc86374 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ exclude = [ "proto-compiler" ] -# [patch.crates-io] +[patch.crates-io] # tendermint = { git = "https://github.com/informalsystems/tendermint-rs", branch = "master" } # tendermint-rpc = { git = "https://github.com/informalsystems/tendermint-rs", branch = "master" } # tendermint-proto = { git = "https://github.com/informalsystems/tendermint-rs", branch = "master" } diff --git a/modules/Cargo.toml b/modules/Cargo.toml index 94634b45ca..9af8eb0490 100644 --- a/modules/Cargo.toml +++ b/modules/Cargo.toml @@ -16,13 +16,17 @@ description = """ [features] # This feature grants access to development-time mocking libraries, such as `MockContext` or `MockHeader`. # Depends on the `testgen` suite for generating Tendermint light blocks. +default = ["std", "eyre_tracer"] +std = [ + "flex-error/std" +] +eyre_tracer = ["flex-error/eyre_tracer"] mocks = [ "tendermint-testgen", "sha2" ] [dependencies] # Proto definitions for all IBC-related interfaces, e.g., connections or channels. ibc-proto = { version = "0.9.0", path = "../proto" } ics23 = "0.6.5" -anomaly = "0.2.0" chrono = "0.4.19" thiserror = "1.0.26" serde_derive = "1.0.104" @@ -36,6 +40,7 @@ dyn-clonable = "0.9.0" regex = "1" subtle-encoding = "0.5" sha2 = { version = "0.9.3", optional = true } +flex-error = { version = "0.4.1", default-features = false } [dependencies.tendermint] version = "=0.21.0" diff --git a/modules/src/application/ics20_fungible_token_transfer/error.rs b/modules/src/application/ics20_fungible_token_transfer/error.rs index 6751d16fd0..b956236439 100644 --- a/modules/src/application/ics20_fungible_token_transfer/error.rs +++ b/modules/src/application/ics20_fungible_token_transfer/error.rs @@ -1,44 +1,46 @@ -use anomaly::{BoxError, Context}; -use thiserror::Error; - +use crate::ics04_channel::error as channel_error; +use crate::ics24_host::error::ValidationError; use crate::ics24_host::identifier::{ChannelId, PortId}; - -pub type Error = anomaly::Error; - -#[derive(Clone, Debug, Error, PartialEq, Eq)] -pub enum Kind { - #[error("unrecognized ICS-20 transfer message type URL {0}")] - UnknownMessageTypeUrl(String), - - #[error("error raised by message handler")] - HandlerRaisedError, - - #[error("sending sequence number not found for port {0} and channel {1}")] - SequenceSendNotFound(PortId, ChannelId), - - #[error("missing channel for port_id {0} and channel_id {1} ")] - ChannelNotFound(PortId, ChannelId), - - #[error( - "destination channel not found in the counterparty of port_id {0} and channel_id {1} " - )] - DestinationChannelNotFound(PortId, ChannelId), - - #[error("invalid port identifier")] - InvalidPortId(String), - - #[error("invalid channel identifier")] - InvalidChannelId(String), - - #[error("invalid packet timeout height value")] - InvalidPacketTimeoutHeight(String), - - #[error("invalid packet timeout timestamp value")] - InvalidPacketTimeoutTimestamp(u64), -} - -impl Kind { - pub fn context(self, source: impl Into) -> Context { - Context::new(self, Some(source.into())) +use flex_error::define_error; + +define_error! { + Error { + UnknowMessageTypeUrl + { url: String } + | e | { format_args!("unrecognized ICS-20 transfer message type URL {0}", e.url) }, + + Ics04Channel + [ channel_error::Error ] + |_ | { "Ics04 channel error" }, + + SequenceSendNotFound + { port_id: PortId, channel_id: ChannelId } + | e | { format_args!("sending sequence number not found for port {0} and channel {1}", e.port_id, e.channel_id) }, + + ChannelNotFound + { port_id: PortId, channel_id: ChannelId } + | e | { format_args!("sending sequence number not found for port {0} and channel {1}", e.port_id, e.channel_id) }, + + DestinationChannelNotFound + { port_id: PortId, channel_id: ChannelId } + | e | { format_args!("destination channel not found in the counterparty of port_id {0} and channel_id {1} ", e.port_id, e.channel_id) }, + + InvalidPortId + { context: String } + [ ValidationError ] + | _ | { "invalid port identifier" }, + + InvalidChannelId + { context: String } + [ ValidationError ] + | _ | { "invalid channel identifier" }, + + InvalidPacketTimeoutHeight + { context: String } + | _ | { "invalid packet timeout height value" }, + + InvalidPacketTimeoutTimestamp + { timestamp: u64 } + | _ | { "invalid packet timeout timestamp value" }, } } diff --git a/modules/src/application/ics20_fungible_token_transfer/msgs/transfer.rs b/modules/src/application/ics20_fungible_token_transfer/msgs/transfer.rs index 1994b2192b..75f6a2eb49 100644 --- a/modules/src/application/ics20_fungible_token_transfer/msgs/transfer.rs +++ b/modules/src/application/ics20_fungible_token_transfer/msgs/transfer.rs @@ -6,7 +6,7 @@ use tendermint_proto::Protobuf; use ibc_proto::ibc::apps::transfer::v1::MsgTransfer as RawMsgTransfer; -use crate::application::ics20_fungible_token_transfer::error::{Error, Kind}; +use crate::application::ics20_fungible_token_transfer::error::Error; use crate::ics02_client::height::Height; use crate::ics24_host::identifier::{ChannelId, PortId}; use crate::signer::Signer; @@ -54,16 +54,16 @@ impl Msg for MsgTransfer { impl Protobuf for MsgTransfer {} impl TryFrom for MsgTransfer { - type Error = Kind; + type Error = Error; fn try_from(raw_msg: RawMsgTransfer) -> Result { let timeout_timestamp = Timestamp::from_nanoseconds(raw_msg.timeout_timestamp) - .map_err(|_| Kind::InvalidPacketTimeoutTimestamp(raw_msg.timeout_timestamp))?; + .map_err(|_| Error::invalid_packet_timeout_timestamp(raw_msg.timeout_timestamp))?; let timeout_height = match raw_msg.timeout_height.clone() { None => Height::zero(), Some(raw_height) => raw_height.try_into().map_err(|e| { - Kind::InvalidPacketTimeoutHeight(format!("invalid timeout height {}", e)) + Error::invalid_packet_timeout_height(format!("invalid timeout height {}", e)) })?, }; @@ -71,11 +71,11 @@ impl TryFrom for MsgTransfer { source_port: raw_msg .source_port .parse() - .map_err(|_| Kind::InvalidPortId(raw_msg.source_port.clone()))?, + .map_err(|e| Error::invalid_port_id(raw_msg.source_port.clone(), e))?, source_channel: raw_msg .source_channel .parse() - .map_err(|_| Kind::InvalidChannelId(raw_msg.source_channel.clone()))?, + .map_err(|e| Error::invalid_channel_id(raw_msg.source_channel.clone(), e))?, token: raw_msg.token, sender: raw_msg.sender.into(), receiver: raw_msg.receiver.into(), diff --git a/modules/src/application/ics20_fungible_token_transfer/relay_application_logic/send_transfer.rs b/modules/src/application/ics20_fungible_token_transfer/relay_application_logic/send_transfer.rs index 2bb200ad9b..c23aed9688 100644 --- a/modules/src/application/ics20_fungible_token_transfer/relay_application_logic/send_transfer.rs +++ b/modules/src/application/ics20_fungible_token_transfer/relay_application_logic/send_transfer.rs @@ -1,5 +1,5 @@ use crate::application::ics20_fungible_token_transfer::context::Ics20Context; -use crate::application::ics20_fungible_token_transfer::error::{Error, Kind}; +use crate::application::ics20_fungible_token_transfer::error::Error; use crate::application::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; use crate::handler::HandlerOutput; use crate::ics04_channel::handler::send_packet::send_packet; @@ -16,7 +16,7 @@ where let source_channel_end = ctx .channel_end(&(msg.source_port.clone(), msg.source_channel.clone())) .ok_or_else(|| { - Kind::ChannelNotFound(msg.source_port.clone(), msg.source_channel.clone()) + Error::channel_not_found(msg.source_port.clone(), msg.source_channel.clone()) })?; let destination_port = source_channel_end.counterparty().port_id().clone(); @@ -24,14 +24,17 @@ where .counterparty() .channel_id() .ok_or_else(|| { - Kind::DestinationChannelNotFound(msg.source_port.clone(), msg.source_channel.clone()) + Error::destination_channel_not_found( + msg.source_port.clone(), + msg.source_channel.clone(), + ) })?; // get the next sequence let sequence = ctx .get_next_sequence_send(&(msg.source_port.clone(), msg.source_channel.clone())) .ok_or_else(|| { - Kind::SequenceSendNotFound(msg.source_port.clone(), msg.source_channel.clone()) + Error::sequence_send_not_found(msg.source_port.clone(), msg.source_channel.clone()) })?; //TODO: Application LOGIC. @@ -47,8 +50,7 @@ where timeout_timestamp: msg.timeout_timestamp, }; - let handler_output = - send_packet(ctx, packet).map_err(|e| Kind::HandlerRaisedError.context(e))?; + let handler_output = send_packet(ctx, packet).map_err(Error::ics04_channel)?; //TODO: add event/atributes and writes to the store issued by the application logic for packet sending. Ok(handler_output) diff --git a/modules/src/events.rs b/modules/src/events.rs index 1b9542c264..b25fccbb1b 100644 --- a/modules/src/events.rs +++ b/modules/src/events.rs @@ -1,19 +1,69 @@ use std::collections::HashMap; -use anomaly::BoxError; use serde_derive::{Deserialize, Serialize}; +use crate::ics02_client::error as client_error; use crate::ics02_client::events as ClientEvents; use crate::ics02_client::events::NewBlock; +use crate::ics02_client::height::HeightError; use crate::ics03_connection::events as ConnectionEvents; use crate::ics03_connection::events::Attributes as ConnectionAttributes; +use crate::ics04_channel::error as channel_error; use crate::ics04_channel::events as ChannelEvents; use crate::ics04_channel::events::Attributes as ChannelAttributes; - +use crate::ics24_host::error::ValidationError; +use crate::timestamp::ParseTimestampError; use crate::Height; +use flex_error::{define_error, TraceError}; use prost::alloc::fmt::Formatter; use std::fmt; +define_error! { + Error { + Height + [ HeightError ] + | _ | { "error parsing height" }, + + Parse + [ ValidationError ] + | _ | { "parse error" }, + + Client + [ client_error::Error ] + | _ | { "ICS02 client error" }, + + Channel + [ channel_error::Error ] + | _ | { "channel error" }, + + Timestamp + [ ParseTimestampError ] + | _ | { "error parsing timestamp" }, + + MissingKey + { key: String } + | e | { format_args!("missing event key {}", e.key) }, + + Decode + [ TraceError ] + | _ | { "error decoding protobuf" }, + + SubtleEncoding + [ TraceError ] + | _ | { "error decoding hex" }, + + MissingActionString + | _ | { "Missing action string" }, + + IncorrectEventType + { event: String } + | e | { + format_args!("Incorrect Event Type {}", + e.event) + }, + } +} + /// Events types #[derive(Debug, Clone, Deserialize, Serialize)] pub enum IbcEventType { @@ -237,14 +287,28 @@ impl RawObject { pub fn extract_events( events: &HashMap, S>, action_string: &str, -) -> Result<(), BoxError> { +) -> Result<(), Error> { if let Some(message_action) = events.get("message.action") { if message_action.contains(&action_string.to_owned()) { return Ok(()); } - return Err("Missing action string".into()); + return Err(Error::missing_action_string()); } - Err("Incorrect Event Type".into()) + Err(Error::incorrect_event_type(action_string.to_string())) +} + +pub fn extract_attribute(object: &RawObject, key: &str) -> Result { + let value = object + .events + .get(key) + .ok_or_else(|| Error::missing_key(key.to_string()))?[object.idx] + .clone(); + + Ok(value) +} + +pub fn maybe_extract_attribute(object: &RawObject, key: &str) -> Option { + object.events.get(key).map(|tags| tags[object.idx].clone()) } #[macro_export] @@ -255,32 +319,14 @@ macro_rules! make_event { pub data: ::std::collections::HashMap>, } impl ::std::convert::TryFrom<$crate::events::RawObject> for $a { - type Error = ::anomaly::BoxError; + type Error = $crate::event::Error; fn try_from(result: $crate::events::RawObject) -> Result { - match $crate::events::extract_events(&result.events, $b) { - Ok(()) => Ok($a { - data: result.events.clone(), - }), - Err(e) => Err(e), - } + $crate::events::extract_events(&result.events, $b)?; + Ok($a { + data: result.events.clone(), + }) } } }; } - -#[macro_export] -macro_rules! attribute { - ($a:ident, $b:literal) => { - $a.events.get($b).ok_or($b)?[$a.idx].parse()? - }; -} - -#[macro_export] -macro_rules! some_attribute { - ($a:ident, $b:literal) => { - $a.events - .get($b) - .map_or_else(|| None, |tags| tags[$a.idx].parse().ok()) - }; -} diff --git a/modules/src/ics02_client/client_consensus.rs b/modules/src/ics02_client/client_consensus.rs index 5a965fac43..da2d0bb12c 100644 --- a/modules/src/ics02_client/client_consensus.rs +++ b/modules/src/ics02_client/client_consensus.rs @@ -1,22 +1,22 @@ use core::marker::{Send, Sync}; -use std::convert::{TryFrom, TryInto}; +use std::convert::TryFrom; use chrono::{DateTime, Utc}; use prost_types::Any; use serde::Serialize; +use std::convert::Infallible; use tendermint_proto::Protobuf; use ibc_proto::ibc::core::client::v1::ConsensusStateWithHeight; use crate::events::IbcEventType; use crate::ics02_client::client_type::ClientType; -use crate::ics02_client::error::{Error, Kind}; +use crate::ics02_client::error::Error; use crate::ics02_client::height::Height; use crate::ics07_tendermint::consensus_state; use crate::ics23_commitment::commitment::CommitmentRoot; use crate::ics24_host::identifier::ClientId; use crate::timestamp::Timestamp; -use crate::utils::UnwrapInfallible; #[cfg(any(test, feature = "mocks"))] use crate::mock::client_state::MockConsensusState; @@ -26,8 +26,9 @@ pub const TENDERMINT_CONSENSUS_STATE_TYPE_URL: &str = pub const MOCK_CONSENSUS_STATE_TYPE_URL: &str = "/ibc.mock.ConsensusState"; -#[dyn_clonable::clonable] pub trait ConsensusState: Clone + std::fmt::Debug + Send + Sync { + type Error; + /// Type of client associated with this consensus state (eg. Tendermint) fn client_type(&self) -> ClientType; @@ -35,7 +36,7 @@ pub trait ConsensusState: Clone + std::fmt::Debug + Send + Sync { fn root(&self) -> &CommitmentRoot; /// Performs basic validation of the consensus state - fn validate_basic(&self) -> Result<(), Box>; + fn validate_basic(&self) -> Result<(), Self::Error>; /// Wrap into an `AnyConsensusState` fn wrap_any(self) -> AnyConsensusState; @@ -80,20 +81,20 @@ impl TryFrom for AnyConsensusState { fn try_from(value: Any) -> Result { match value.type_url.as_str() { - "" => Err(Kind::EmptyConsensusStateResponse.into()), + "" => Err(Error::empty_consensus_state_response()), TENDERMINT_CONSENSUS_STATE_TYPE_URL => Ok(AnyConsensusState::Tendermint( consensus_state::ConsensusState::decode_vec(&value.value) - .map_err(|e| Kind::InvalidRawConsensusState.context(e))?, + .map_err(Error::decode_raw_client_state)?, )), #[cfg(any(test, feature = "mocks"))] MOCK_CONSENSUS_STATE_TYPE_URL => Ok(AnyConsensusState::Mock( MockConsensusState::decode_vec(&value.value) - .map_err(|e| Kind::InvalidRawConsensusState.context(e))?, + .map_err(Error::decode_raw_client_state)?, )), - _ => Err(Kind::UnknownConsensusStateType(value.type_url).into()), + _ => Err(Error::unknown_consensus_state_type(value.type_url)), } } } @@ -127,22 +128,17 @@ pub struct AnyConsensusStateWithHeight { impl Protobuf for AnyConsensusStateWithHeight {} impl TryFrom for AnyConsensusStateWithHeight { - type Error = Kind; + type Error = Error; fn try_from(value: ConsensusStateWithHeight) -> Result { let state = value .consensus_state .map(AnyConsensusState::try_from) - .transpose() - .map_err(|_| Kind::InvalidRawConsensusState)? - .ok_or(Kind::EmptyConsensusStateResponse)?; + .transpose()? + .ok_or_else(Error::empty_consensus_state_response)?; Ok(AnyConsensusStateWithHeight { - height: value - .height - .ok_or(Kind::MissingHeight)? - .try_into() - .unwrap_infallible(), + height: value.height.ok_or_else(Error::missing_height)?.into(), consensus_state: state, }) } @@ -158,6 +154,8 @@ impl From for ConsensusStateWithHeight { } impl ConsensusState for AnyConsensusState { + type Error = Infallible; + fn client_type(&self) -> ClientType { self.client_type() } @@ -166,7 +164,7 @@ impl ConsensusState for AnyConsensusState { todo!() } - fn validate_basic(&self) -> Result<(), Box> { + fn validate_basic(&self) -> Result<(), Infallible> { todo!() } diff --git a/modules/src/ics02_client/client_def.rs b/modules/src/ics02_client/client_def.rs index cc8d2e8e1a..afb12f5808 100644 --- a/modules/src/ics02_client/client_def.rs +++ b/modules/src/ics02_client/client_def.rs @@ -4,7 +4,7 @@ use crate::downcast; use crate::ics02_client::client_consensus::{AnyConsensusState, ConsensusState}; use crate::ics02_client::client_state::{AnyClientState, ClientState}; use crate::ics02_client::client_type::ClientType; -use crate::ics02_client::error::Kind; +use crate::ics02_client::error::Error; use crate::ics02_client::header::{AnyHeader, Header}; use crate::ics03_connection::connection::ConnectionEnd; use crate::ics04_channel::channel::ChannelEnd; @@ -27,7 +27,7 @@ pub trait ClientDef: Clone { &self, client_state: Self::ClientState, header: Self::Header, - ) -> Result<(Self::ClientState, Self::ConsensusState), Box>; + ) -> Result<(Self::ClientState, Self::ConsensusState), Error>; fn verify_upgrade_and_update_state( &self, @@ -35,7 +35,7 @@ pub trait ClientDef: Clone { consensus_state: &Self::ConsensusState, proof_upgrade_client: MerkleProof, proof_upgrade_consensus_state: MerkleProof, - ) -> Result<(Self::ClientState, Self::ConsensusState), Box>; + ) -> Result<(Self::ClientState, Self::ConsensusState), Error>; /// Verification functions as specified in: /// @@ -54,7 +54,7 @@ pub trait ClientDef: Clone { client_id: &ClientId, consensus_height: Height, expected_consensus_state: &AnyConsensusState, - ) -> Result<(), Box>; + ) -> Result<(), Error>; /// Verify a `proof` that a connection state matches that of the input `connection_end`. fn verify_connection_state( @@ -65,7 +65,7 @@ pub trait ClientDef: Clone { proof: &CommitmentProofBytes, connection_id: Option<&ConnectionId>, expected_connection_end: &ConnectionEnd, - ) -> Result<(), Box>; + ) -> Result<(), Error>; /// Verify a `proof` that a channel state matches that of the input `channel_end`. #[allow(clippy::too_many_arguments)] @@ -78,7 +78,7 @@ pub trait ClientDef: Clone { port_id: &PortId, channel_id: &ChannelId, expected_channel_end: &ChannelEnd, - ) -> Result<(), Box>; + ) -> Result<(), Error>; /// Verify the client state for this chain that it is stored on the counterparty chain. #[allow(clippy::too_many_arguments)] @@ -91,7 +91,7 @@ pub trait ClientDef: Clone { client_id: &ClientId, proof: &CommitmentProofBytes, client_state: &AnyClientState, - ) -> Result<(), Box>; + ) -> Result<(), Error>; /// Verify a `proof` that a packet has been commited. #[allow(clippy::too_many_arguments)] @@ -104,7 +104,7 @@ pub trait ClientDef: Clone { channel_id: &ChannelId, seq: &Sequence, commitment: String, - ) -> Result<(), Box>; + ) -> Result<(), Error>; /// Verify a `proof` that a packet has been commited. #[allow(clippy::too_many_arguments)] @@ -117,7 +117,7 @@ pub trait ClientDef: Clone { channel_id: &ChannelId, seq: &Sequence, ack: Vec, - ) -> Result<(), Box>; + ) -> Result<(), Error>; /// Verify a `proof` that of the next_seq_received. #[allow(clippy::too_many_arguments)] @@ -129,7 +129,7 @@ pub trait ClientDef: Clone { port_id: &PortId, channel_id: &ChannelId, seq: &Sequence, - ) -> Result<(), Box>; + ) -> Result<(), Error>; /// Verify a `proof` that a packet has not been received. #[allow(clippy::too_many_arguments)] @@ -141,7 +141,7 @@ pub trait ClientDef: Clone { port_id: &PortId, channel_id: &ChannelId, seq: &Sequence, - ) -> Result<(), Box>; + ) -> Result<(), Error>; } #[derive(Clone, Debug, PartialEq, Eq)] @@ -174,14 +174,14 @@ impl ClientDef for AnyClient { &self, client_state: AnyClientState, header: AnyHeader, - ) -> Result<(AnyClientState, AnyConsensusState), Box> { + ) -> Result<(AnyClientState, AnyConsensusState), Error> { match self { Self::Tendermint(client) => { let (client_state, header) = downcast!( client_state => AnyClientState::Tendermint, header => AnyHeader::Tendermint, ) - .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Tendermint))?; + .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Tendermint))?; let (new_state, new_consensus) = client.check_header_and_update_state(client_state, header)?; @@ -198,7 +198,7 @@ impl ClientDef for AnyClient { client_state => AnyClientState::Mock, header => AnyHeader::Mock, ) - .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Mock))?; + .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Mock))?; let (new_state, new_consensus) = client.check_header_and_update_state(client_state, header)?; @@ -220,13 +220,13 @@ impl ClientDef for AnyClient { client_id: &ClientId, consensus_height: Height, expected_consensus_state: &AnyConsensusState, - ) -> Result<(), Box> { + ) -> Result<(), Error> { match self { Self::Tendermint(client) => { let client_state = downcast!( client_state => AnyClientState::Tendermint ) - .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Tendermint))?; + .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Tendermint))?; client.verify_client_consensus_state( client_state, @@ -244,7 +244,7 @@ impl ClientDef for AnyClient { let client_state = downcast!( client_state => AnyClientState::Mock ) - .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Mock))?; + .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Mock))?; client.verify_client_consensus_state( client_state, @@ -267,11 +267,11 @@ impl ClientDef for AnyClient { proof: &CommitmentProofBytes, connection_id: Option<&ConnectionId>, expected_connection_end: &ConnectionEnd, - ) -> Result<(), Box> { + ) -> Result<(), Error> { match self { Self::Tendermint(client) => { let client_state = downcast!(client_state => AnyClientState::Tendermint) - .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Tendermint))?; + .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Tendermint))?; client.verify_connection_state( client_state, @@ -286,7 +286,7 @@ impl ClientDef for AnyClient { #[cfg(any(test, feature = "mocks"))] Self::Mock(client) => { let client_state = downcast!(client_state => AnyClientState::Mock) - .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Mock))?; + .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Mock))?; client.verify_connection_state( client_state, @@ -309,11 +309,11 @@ impl ClientDef for AnyClient { port_id: &PortId, channel_id: &ChannelId, expected_channel_end: &ChannelEnd, - ) -> Result<(), Box> { + ) -> Result<(), Error> { match self { Self::Tendermint(client) => { let client_state = downcast!(client_state => AnyClientState::Tendermint) - .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Tendermint))?; + .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Tendermint))?; client.verify_channel_state( client_state, @@ -329,7 +329,7 @@ impl ClientDef for AnyClient { #[cfg(any(test, feature = "mocks"))] Self::Mock(client) => { let client_state = downcast!(client_state => AnyClientState::Mock) - .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Mock))?; + .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Mock))?; client.verify_channel_state( client_state, @@ -353,13 +353,13 @@ impl ClientDef for AnyClient { client_id: &ClientId, proof: &CommitmentProofBytes, client_state_on_counterparty: &AnyClientState, - ) -> Result<(), Box> { + ) -> Result<(), Error> { match self { Self::Tendermint(client) => { let client_state = downcast!( client_state => AnyClientState::Tendermint ) - .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Tendermint))?; + .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Tendermint))?; client.verify_client_full_state( client_state, @@ -377,7 +377,7 @@ impl ClientDef for AnyClient { let client_state = downcast!( client_state => AnyClientState::Mock ) - .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Mock))?; + .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Mock))?; client.verify_client_full_state( client_state, @@ -400,13 +400,13 @@ impl ClientDef for AnyClient { channel_id: &ChannelId, seq: &Sequence, commitment: String, - ) -> Result<(), Box> { + ) -> Result<(), Error> { match self { Self::Tendermint(client) => { let client_state = downcast!( client_state => AnyClientState::Tendermint ) - .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Tendermint))?; + .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Tendermint))?; client.verify_packet_data( client_state, @@ -424,7 +424,7 @@ impl ClientDef for AnyClient { let client_state = downcast!( client_state => AnyClientState::Mock ) - .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Mock))?; + .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Mock))?; client.verify_packet_data( client_state, @@ -448,13 +448,13 @@ impl ClientDef for AnyClient { channel_id: &ChannelId, seq: &Sequence, ack: Vec, - ) -> Result<(), Box> { + ) -> Result<(), Error> { match self { Self::Tendermint(client) => { let client_state = downcast!( client_state => AnyClientState::Tendermint ) - .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Tendermint))?; + .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Tendermint))?; client.verify_packet_acknowledgement( client_state, @@ -472,7 +472,7 @@ impl ClientDef for AnyClient { let client_state = downcast!( client_state => AnyClientState::Mock ) - .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Mock))?; + .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Mock))?; client.verify_packet_acknowledgement( client_state, @@ -495,13 +495,13 @@ impl ClientDef for AnyClient { port_id: &PortId, channel_id: &ChannelId, seq: &Sequence, - ) -> Result<(), Box> { + ) -> Result<(), Error> { match self { Self::Tendermint(client) => { let client_state = downcast!( client_state => AnyClientState::Tendermint ) - .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Tendermint))?; + .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Tendermint))?; client.verify_next_sequence_recv( client_state, @@ -518,7 +518,7 @@ impl ClientDef for AnyClient { let client_state = downcast!( client_state => AnyClientState::Mock ) - .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Mock))?; + .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Mock))?; client.verify_next_sequence_recv( client_state, @@ -539,13 +539,13 @@ impl ClientDef for AnyClient { port_id: &PortId, channel_id: &ChannelId, seq: &Sequence, - ) -> Result<(), Box> { + ) -> Result<(), Error> { match self { Self::Tendermint(client) => { let client_state = downcast!( client_state => AnyClientState::Tendermint ) - .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Tendermint))?; + .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Tendermint))?; client.verify_packet_receipt_absence( client_state, @@ -562,7 +562,7 @@ impl ClientDef for AnyClient { let client_state = downcast!( client_state => AnyClientState::Mock ) - .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Mock))?; + .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Mock))?; client.verify_packet_receipt_absence( client_state, @@ -582,14 +582,14 @@ impl ClientDef for AnyClient { consensus_state: &Self::ConsensusState, proof_upgrade_client: MerkleProof, proof_upgrade_consensus_state: MerkleProof, - ) -> Result<(Self::ClientState, Self::ConsensusState), Box> { + ) -> Result<(Self::ClientState, Self::ConsensusState), Error> { match self { Self::Tendermint(client) => { let (client_state, consensus_state) = downcast!( client_state => AnyClientState::Tendermint, consensus_state => AnyConsensusState::Tendermint, ) - .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Tendermint))?; + .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Tendermint))?; let (new_state, new_consensus) = client.verify_upgrade_and_update_state( client_state, @@ -610,7 +610,7 @@ impl ClientDef for AnyClient { client_state => AnyClientState::Mock, consensus_state => AnyConsensusState::Mock, ) - .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Mock))?; + .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Mock))?; let (new_state, new_consensus) = client.verify_upgrade_and_update_state( client_state, diff --git a/modules/src/ics02_client/client_state.rs b/modules/src/ics02_client/client_state.rs index c23715939c..52a9be60d4 100644 --- a/modules/src/ics02_client/client_state.rs +++ b/modules/src/ics02_client/client_state.rs @@ -10,7 +10,7 @@ use tendermint_proto::Protobuf; use ibc_proto::ibc::core::client::v1::IdentifiedClientState; use crate::ics02_client::client_type::ClientType; -use crate::ics02_client::error::{Error, Kind}; +use crate::ics02_client::error::Error; use crate::ics07_tendermint::client_state; use crate::ics24_host::error::ValidationError; use crate::ics24_host::identifier::{ChainId, ClientId}; @@ -103,20 +103,19 @@ impl TryFrom for AnyClientState { fn try_from(raw: Any) -> Result { match raw.type_url.as_str() { - "" => Err(Kind::EmptyClientStateResponse.into()), + "" => Err(Error::empty_client_state_response()), TENDERMINT_CLIENT_STATE_TYPE_URL => Ok(AnyClientState::Tendermint( client_state::ClientState::decode_vec(&raw.value) - .map_err(|e| Kind::InvalidRawClientState.context(e))?, + .map_err(Error::decode_raw_client_state)?, )), #[cfg(any(test, feature = "mocks"))] MOCK_CLIENT_STATE_TYPE_URL => Ok(AnyClientState::Mock( - MockClientState::decode_vec(&raw.value) - .map_err(|e| Kind::InvalidRawClientState.context(e))?, + MockClientState::decode_vec(&raw.value).map_err(Error::decode_raw_client_state)?, )), - _ => Err(Kind::UnknownClientStateType(raw.type_url).into()), + _ => Err(Error::unknown_client_state_type(raw.type_url)), } } } @@ -197,11 +196,11 @@ impl TryFrom for IdentifiedAnyClientState { fn try_from(raw: IdentifiedClientState) -> Result { Ok(IdentifiedAnyClientState { client_id: raw.client_id.parse().map_err(|e: ValidationError| { - Kind::InvalidRawClientId(raw.client_id.clone(), e.kind().clone()) + Error::invalid_raw_client_id(raw.client_id.clone(), e) })?, client_state: raw .client_state - .ok_or_else(|| Kind::InvalidRawClientState.context("missing client state"))? + .ok_or_else(Error::missing_raw_client_state)? .try_into()?, }) } diff --git a/modules/src/ics02_client/client_type.rs b/modules/src/ics02_client/client_type.rs index 033deffdc1..7f363e2e57 100644 --- a/modules/src/ics02_client/client_type.rs +++ b/modules/src/ics02_client/client_type.rs @@ -2,7 +2,7 @@ use std::fmt; use serde_derive::{Deserialize, Serialize}; -use super::error; +use super::error::Error; /// Type of the client, depending on the specific consensus algorithm. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] @@ -37,7 +37,7 @@ impl fmt::Display for ClientType { } impl std::str::FromStr for ClientType { - type Err = error::Error; + type Err = Error; fn from_str(s: &str) -> Result { match s { @@ -46,7 +46,7 @@ impl std::str::FromStr for ClientType { #[cfg(any(test, feature = "mocks"))] Self::MOCK_STR => Ok(Self::Mock), - _ => Err(error::Kind::UnknownClientType(s.to_string()).into()), + _ => Err(Error::unknown_client_type(s.to_string())), } } } diff --git a/modules/src/ics02_client/error.rs b/modules/src/ics02_client/error.rs index a462689a28..3e11448040 100644 --- a/modules/src/ics02_client/error.rs +++ b/modules/src/ics02_client/error.rs @@ -1,120 +1,211 @@ -use std::num::ParseIntError; - -use anomaly::{BoxError, Context}; -use thiserror::Error; - use crate::ics02_client::client_type::ClientType; +use crate::ics07_tendermint::error::Error as Ics07Error; use crate::ics23_commitment::error::Error as Ics23Error; -use crate::ics24_host::error::ValidationKind; +use crate::ics24_host::error::ValidationError; use crate::ics24_host::identifier::ClientId; use crate::Height; - -pub type Error = anomaly::Error; - -#[derive(Clone, Debug, Error, PartialEq, Eq)] -pub enum Kind { - #[error("unknown client type: {0}")] - UnknownClientType(String), - - #[error("Client identifier constructor failed for type {0} with counter {1}")] - ClientIdentifierConstructor(ClientType, u64), - - #[error("client already exists: {0}")] - ClientAlreadyExists(ClientId), - - #[error("client not found: {0}")] - ClientNotFound(ClientId), - - #[error("client is frozen: {0}")] - ClientFrozen(ClientId), - - #[error("consensus state not found at: {0} at height {1}")] - ConsensusStateNotFound(ClientId, Height), - - #[error("implementation specific")] - ImplementationSpecific, - - #[error("header verification failed")] - HeaderVerificationFailure, - - #[error("unknown client state type: {0}")] - UnknownClientStateType(String), - - #[error("the client state was not found")] - EmptyClientStateResponse, - - #[error("unknown client consensus state type: {0}")] - UnknownConsensusStateType(String), - - #[error("the client consensus state was not found")] - EmptyConsensusStateResponse, - - #[error("unknown header type: {0}")] - UnknownHeaderType(String), - - #[error("unknown misbehaviour type: {0}")] - UnknownMisbehaviourType(String), - - #[error("invalid raw client identifier {0} with underlying error: {1}")] - InvalidRawClientId(String, ValidationKind), - - #[error("invalid raw client state")] - InvalidRawClientState, - - #[error("invalid raw client consensus state")] - InvalidRawConsensusState, - - #[error("invalid client id in the update client message")] - InvalidMsgUpdateClientId, - - #[error("invalid raw client consensus state: the height field is missing")] - MissingHeight, - - #[error("invalid client identifier: validation error: {0}")] - InvalidClientIdentifier(ValidationKind), - - #[error("invalid raw header")] - InvalidRawHeader, - - #[error("invalid raw misbehaviour")] - InvalidRawMisbehaviour, - - #[error("invalid height result")] - InvalidHeightResult, - - #[error("cannot convert into a `Height` type from string {0}")] - HeightConversion(String, ParseIntError), - - #[error("invalid address")] - InvalidAddress, - - #[error("invalid proof for the upgraded client state")] - InvalidUpgradeClientProof(Ics23Error), - - #[error("invalid proof for the upgraded consensus state")] - InvalidUpgradeConsensusStateProof(Ics23Error), - - #[error("invalid packet timeout timestamp value")] - InvalidPacketTimestamp, - - #[error("mismatch between client and arguments types, expected: {0:?}")] - ClientArgsTypeMismatch(ClientType), - - #[error("mismatch raw client consensus state")] - RawClientAndConsensusStateTypesMismatch { - state_type: ClientType, - consensus_type: ClientType, - }, - - #[error("upgrade verification failed")] - UpgradeVerificationFailure, - - #[error("upgraded client height {0} must be at greater than current client height {1}")] - LowUpgradeHeight(Height, Height), +use std::num::TryFromIntError; +use tendermint_proto::Error as TendermintError; + +use flex_error::{define_error, TraceError}; + +define_error! { + Error { + UnknownClientType + { client_type: String } + | e | { format_args!("unknown client type: {0}", e.client_type) }, + + ClientIdentifierConstructor + { client_type: ClientType, counter: u64 } + [ ValidationError ] + | e | { + format_args!("Client identifier constructor failed for type {0} with counter {1}", + e.client_type, e.counter) + }, + + ClientAlreadyExists + { client_id: ClientId } + | e | { format_args!("client already exists: {0}", e.client_id) }, + + ClientNotFound + { client_id: ClientId } + | e | { format_args!("client not found: {0}", e.client_id) }, + + ClientFrozen + { client_id: ClientId } + | e | { format_args!("client is frozen: {0}", e.client_id) }, + + ConsensusStateNotFound + { client_id: ClientId, height: Height } + | e | { + format_args!("consensus state not found at: {0} at height {1}", + e.client_id, e.height) + }, + + ImplementationSpecific + | _ | { "implementation specific error" }, + + HeaderVerificationFailure + { reason: String } + | e | { format_args!("header verification failed with reason: {}", e.reason) }, + + UnknownClientStateType + { client_state_type: String } + | e | { format_args!("unknown client state type: {0}", e.client_state_type) }, + + EmptyClientStateResponse + | _ | { "the client state was not found" }, + + EmptyPrefix + { source: crate::ics23_commitment::merkle::EmptyPrefixError } + | _ | { "empty prefix" }, + + UnknownConsensusStateType + { consensus_state_type: String } + | e | { + format_args!("unknown client consensus state type: {0}", + e.consensus_state_type) + }, + + EmptyConsensusStateResponse + | _ | { "the client state was not found" }, + + UnknownHeaderType + { header_type: String } + | e | { + format_args!("unknown header type: {0}", + e.header_type) + }, + + UnknownMisbehaviourType + { misbehavior_type: String } + | e | { + format_args!("unknown misbehaviour type: {0}", + e.misbehavior_type) + }, + + InvalidRawClientId + { client_id: String } + [ ValidationError ] + | e | { + format_args!("invalid raw client identifier {0}", + e.client_id) + }, + + DecodeRawClientState + [ TraceError ] + | _ | { "error decoding raw client state" }, + + MissingRawClientState + | _ | { "missing raw client state" }, + + InvalidRawConsensusState + [ TraceError ] + | _ | { "invalid raw client consensus state" }, + + MissingRawConsensusState + | _ | { "missing raw client consensus state" }, + + InvalidMsgUpdateClientId + [ ValidationError ] + | _ | { "invalid client id in the update client message" }, + + Decode + [ TraceError ] + | _ | { "decode error" }, + + MissingHeight + | _ | { "invalid raw client consensus state: the height field is missing" }, + + InvalidClientIdentifier + [ ValidationError ] + | _ | { "invalid client identifier" }, + + InvalidRawHeader + [ TraceError ] + | _ | { "invalid raw header" }, + + MissingRawHeader + | _ | { "missing raw header" }, + + DecodeRawMisbehaviour + [ TraceError ] + | _ | { "invalid raw misbehaviour" }, + + InvalidRawMisbehaviour + [ ValidationError ] + | _ | { "invalid raw misbehaviour" }, + + MissingRawMisbehaviour + | _ | { "missing raw misbehaviour" }, + + InvalidHeightResult + | _ | { "height cannot end up zero or negative" }, + + InvalidAddress + | _ | { "invalid address" }, + + InvalidUpgradeClientProof + [ Ics23Error ] + | _ | { "invalid proof for the upgraded client state" }, + + InvalidUpgradeConsensusStateProof + [ Ics23Error ] + | _ | { "invalid proof for the upgraded consensus state" }, + + Tendermint + [ Ics07Error ] + | _ | { "tendermint error" }, + + InvalidPacketTimestamp + [ TraceError ] + | _ | { "invalid packet timeout timestamp value" }, + + ClientArgsTypeMismatch + { client_type: ClientType } + | e | { + format_args!("mismatch between client and arguments types, expected: {0:?}", + e.client_type) + }, + + RawClientAndConsensusStateTypesMismatch + { + state_type: ClientType, + consensus_type: ClientType, + } + | e | { + format_args!("mismatch in raw client consensus state {} with expected state {}", + e.state_type, e.consensus_type) + }, + + LowHeaderHeight + { + header_height: Height, + latest_height: Height + } + | e | { + format!("received header height ({:?}) is lower than (or equal to) client latest height ({:?})", + e.header_height, e.latest_height) + }, + + LowUpgradeHeight + { + upgraded_height: Height, + client_height: Height, + } + | e | { + format_args!("upgraded client height {0} must be at greater than current client height {1}", + e.upgraded_height, e.client_height) + }, + } } -impl Kind { - pub fn context(self, source: impl Into) -> Context { - Context::new(self, Some(source.into())) +impl Error { + pub fn upgrade_verification_failed(e: Error) -> Error { + e.add_trace(&"upgrade verification failed") + } + + pub fn invalid_raw_client_state(e: Error) -> Error { + e.add_trace(&"invalid raw client state") } } diff --git a/modules/src/ics02_client/events.rs b/modules/src/ics02_client/events.rs index a7a6dd9daa..5082e55c2d 100644 --- a/modules/src/ics02_client/events.rs +++ b/modules/src/ics02_client/events.rs @@ -1,17 +1,16 @@ //! Types for the IBC events emitted from Tendermint Websocket by the client module. use std::convert::{TryFrom, TryInto}; -use anomaly::BoxError; +use prost::Message; use serde_derive::{Deserialize, Serialize}; use subtle_encoding::hex; use tendermint_proto::Protobuf; -use crate::events::{IbcEvent, RawObject}; +use crate::events::{extract_attribute, Error, IbcEvent, RawObject}; use crate::ics02_client::client_type::ClientType; use crate::ics02_client::header::AnyHeader; use crate::ics02_client::height::Height; use crate::ics24_host::identifier::ClientId; -use crate::{attribute, some_attribute}; /// The content of the `type` field for the event that a chain produces upon executing the create client transaction. const CREATE_EVENT_TYPE: &str = "create_client"; @@ -135,6 +134,25 @@ impl std::fmt::Display for Attributes { } } +fn extract_attributes(object: &RawObject, namespace: &str) -> Result { + Ok(Attributes { + height: object.height, + + client_id: extract_attribute(object, &format!("{}.client_id", namespace))? + .parse() + .map_err(Error::parse)?, + + client_type: extract_attribute(object, &format!("{}.client_type", namespace))? + .parse() + .map_err(Error::client)?, + + consensus_height: extract_attribute(object, &format!("{}.consensus_height", namespace))? + .as_str() + .try_into() + .map_err(Error::height)?, + }) +} + /// CreateClient event signals the creation of a new on-chain client (IBC client). #[derive(Debug, Deserialize, Serialize, Clone)] pub struct CreateClient(pub Attributes); @@ -158,15 +176,9 @@ impl From for CreateClient { } impl TryFrom for CreateClient { - type Error = BoxError; + type Error = Error; fn try_from(obj: RawObject) -> Result { - let consensus_height_str: String = attribute!(obj, "create_client.consensus_height"); - Ok(CreateClient(Attributes { - height: obj.height, - client_id: attribute!(obj, "create_client.client_id"), - client_type: attribute!(obj, "create_client.client_type"), - consensus_height: consensus_height_str.as_str().try_into()?, - })) + Ok(CreateClient(extract_attributes(&obj, "create_client")?)) } } @@ -220,24 +232,30 @@ impl From for UpdateClient { } impl TryFrom for UpdateClient { - type Error = BoxError; + type Error = Error; + fn try_from(obj: RawObject) -> Result { - let header_str: Option = some_attribute!(obj, "update_client.header"); + let header_str: Option = obj + .events + .get("update_client.header") + .and_then(|tags| tags[obj.idx].parse().ok()); + let header: Option = match header_str { Some(str) => { - let header_bytes = hex::decode(str)?; - Some(Protobuf::decode(header_bytes.as_ref())?) + let header_bytes = hex::decode(str).map_err(Error::subtle_encoding)?; + + let decoded = prost_types::Any::decode(header_bytes.as_ref()) + .map_err(Error::decode)? + .try_into() + .map_err(Error::client)?; + + Some(decoded) } None => None, }; - let consensus_height_str: String = attribute!(obj, "update_client.consensus_height"); + Ok(UpdateClient { - common: Attributes { - height: obj.height, - client_id: attribute!(obj, "update_client.client_id"), - client_type: attribute!(obj, "update_client.client_type"), - consensus_height: consensus_height_str.as_str().try_into()?, - }, + common: extract_attributes(&obj, "update_client")?, header, }) } @@ -273,15 +291,12 @@ impl ClientMisbehaviour { } impl TryFrom for ClientMisbehaviour { - type Error = BoxError; - fn try_from(obj: RawObject) -> Result { - let consensus_height_str: String = attribute!(obj, "client_misbehaviour.consensus_height"); - Ok(ClientMisbehaviour(Attributes { - height: obj.height, - client_id: attribute!(obj, "client_misbehaviour.client_id"), - client_type: attribute!(obj, "client_misbehaviour.client_type"), - consensus_height: consensus_height_str.as_str().try_into()?, - })) + type Error = Error; + fn try_from(obj: RawObject) -> Result { + Ok(ClientMisbehaviour(extract_attributes( + &obj, + "client_misbehaviour", + )?)) } } diff --git a/modules/src/ics02_client/handler/create_client.rs b/modules/src/ics02_client/handler/create_client.rs index 5d2cd2fea4..558c77f296 100644 --- a/modules/src/ics02_client/handler/create_client.rs +++ b/modules/src/ics02_client/handler/create_client.rs @@ -6,7 +6,7 @@ use crate::ics02_client::client_consensus::AnyConsensusState; use crate::ics02_client::client_state::AnyClientState; use crate::ics02_client::client_type::ClientType; use crate::ics02_client::context::ClientReader; -use crate::ics02_client::error::{Error, Kind}; +use crate::ics02_client::error::Error; use crate::ics02_client::events::Attributes; use crate::ics02_client::handler::ClientResult; use crate::ics02_client::msgs::create_client::MsgCreateAnyClient; @@ -31,7 +31,7 @@ pub fn process( // Construct this client's identifier let id_counter = ctx.client_counter(); let client_id = ClientId::new(msg.client_state().client_type(), id_counter).map_err(|e| { - Kind::ClientIdentifierConstructor(msg.client_state().client_type(), id_counter).context(e) + Error::client_identifier_constructor(msg.client_state().client_type(), id_counter, e) })?; output.log(format!( diff --git a/modules/src/ics02_client/handler/update_client.rs b/modules/src/ics02_client/handler/update_client.rs index 25ef38f74e..c58eea832b 100644 --- a/modules/src/ics02_client/handler/update_client.rs +++ b/modules/src/ics02_client/handler/update_client.rs @@ -6,7 +6,7 @@ use crate::ics02_client::client_consensus::AnyConsensusState; use crate::ics02_client::client_def::{AnyClient, ClientDef}; use crate::ics02_client::client_state::AnyClientState; use crate::ics02_client::context::ClientReader; -use crate::ics02_client::error::{Error, Kind}; +use crate::ics02_client::error::Error; use crate::ics02_client::events::Attributes; use crate::ics02_client::handler::ClientResult; use crate::ics02_client::msgs::update_client::MsgUpdateAnyClient; @@ -36,25 +36,25 @@ pub fn process( // Read client type from the host chain store. The client should already exist. let client_type = ctx .client_type(&client_id) - .ok_or_else(|| Kind::ClientNotFound(client_id.clone()))?; + .ok_or_else(|| Error::client_not_found(client_id.clone()))?; let client_def = AnyClient::from_client_type(client_type); // Read client state from the host chain store. let client_state = ctx .client_state(&client_id) - .ok_or_else(|| Kind::ClientNotFound(client_id.clone()))?; + .ok_or_else(|| Error::client_not_found(client_id.clone()))?; let latest_height = client_state.latest_height(); ctx.consensus_state(&client_id, latest_height) - .ok_or_else(|| Kind::ConsensusStateNotFound(client_id.clone(), latest_height))?; + .ok_or_else(|| Error::consensus_state_not_found(client_id.clone(), latest_height))?; // Use client_state to validate the new header against the latest consensus_state. // This function will return the new client_state (its latest_height changed) and a // consensus_state obtained from header. These will be later persisted by the keeper. let (new_client_state, new_consensus_state) = client_def .check_header_and_update_state(client_state, header) - .map_err(|e| Kind::HeaderVerificationFailure.context(e.to_string()))?; + .map_err(|e| Error::header_verification_failure(e.to_string()))?; let result = ClientResult::Update(Result { client_id: client_id.clone(), @@ -79,7 +79,7 @@ mod tests { use crate::events::IbcEvent; use crate::handler::HandlerOutput; use crate::ics02_client::client_state::AnyClientState; - use crate::ics02_client::error::Kind; + use crate::ics02_client::error::{Error, ErrorDetail}; use crate::ics02_client::handler::dispatch; use crate::ics02_client::handler::ClientResult::Update; use crate::ics02_client::header::Header; @@ -154,11 +154,11 @@ mod tests { let output = dispatch(&ctx, ClientMsg::UpdateClient(msg.clone())); match output { - Ok(_) => { - panic!("unexpected success (expected error)"); + Err(Error(ErrorDetail::ClientNotFound(e), _)) => { + assert_eq!(e.client_id, msg.client_id); } - Err(err) => { - assert_eq!(err.kind(), &Kind::ClientNotFound(msg.client_id)); + _ => { + panic!("expected ClientNotFound error, instead got {:?}", output) } } } diff --git a/modules/src/ics02_client/handler/upgrade_client.rs b/modules/src/ics02_client/handler/upgrade_client.rs index b0b910c871..82e9364b66 100644 --- a/modules/src/ics02_client/handler/upgrade_client.rs +++ b/modules/src/ics02_client/handler/upgrade_client.rs @@ -4,10 +4,9 @@ use crate::events::IbcEvent; use crate::handler::{HandlerOutput, HandlerResult}; use crate::ics02_client::client_consensus::AnyConsensusState; use crate::ics02_client::client_def::{AnyClient, ClientDef}; -use crate::ics02_client::client_state::AnyClientState; -use crate::ics02_client::client_state::ClientState; +use crate::ics02_client::client_state::{AnyClientState, ClientState}; use crate::ics02_client::context::ClientReader; -use crate::ics02_client::error::{Error, Kind}; +use crate::ics02_client::error::Error; use crate::ics02_client::events::Attributes; use crate::ics02_client::handler::ClientResult; use crate::ics02_client::msgs::upgrade_client::MsgUpgradeAnyClient; @@ -32,25 +31,24 @@ pub fn process( // Read client state from the host chain store. let client_state = ctx .client_state(&client_id) - .ok_or_else(|| Kind::ClientNotFound(client_id.clone()))?; + .ok_or_else(|| Error::client_not_found(client_id.clone()))?; if client_state.is_frozen() { - return Err(Kind::ClientFrozen(client_id).into()); + return Err(Error::client_frozen(client_id)); } let upgrade_client_state = msg.client_state.clone(); if client_state.latest_height() >= upgrade_client_state.latest_height() { - return Err(Kind::LowUpgradeHeight( + return Err(Error::low_upgrade_height( client_state.latest_height(), upgrade_client_state.latest_height(), - ) - .into()); + )); } let client_type = ctx .client_type(&client_id) - .ok_or_else(|| Kind::ClientNotFound(client_id.clone()))?; + .ok_or_else(|| Error::client_not_found(client_id.clone()))?; let client_def = AnyClient::from_client_type(client_type); @@ -61,7 +59,7 @@ pub fn process( msg.proof_upgrade_client.clone(), msg.proof_upgrade_consensus_state, ) - .map_err(|e| Kind::UpgradeVerificationFailure.context(e.to_string()))?; + .map_err(Error::upgrade_verification_failed)?; // Not implemented yet: https://github.com/informalsystems/ibc-rs/issues/722 // todo!() @@ -86,7 +84,7 @@ mod tests { use crate::events::IbcEvent; use crate::handler::HandlerOutput; - use crate::ics02_client::error::Kind; + use crate::ics02_client::error::{Error, ErrorDetail}; use crate::ics02_client::handler::dispatch; use crate::ics02_client::handler::ClientResult::Upgrade; use crate::ics02_client::msgs::upgrade_client::MsgUpgradeAnyClient; @@ -176,11 +174,11 @@ mod tests { let output = dispatch(&ctx, ClientMsg::UpgradeClient(msg.clone())); match output { - Ok(_) => { - panic!("unexpected success (expected error)"); + Err(Error(ErrorDetail::ClientNotFound(e), _)) => { + assert_eq!(e.client_id, msg.client_id); } - Err(err) => { - assert_eq!(err.kind(), &Kind::ClientNotFound(msg.client_id)); + _ => { + panic!("expected ClientNotFound error, instead got {:?}", output); } } } @@ -210,14 +208,12 @@ mod tests { let output = dispatch(&ctx, ClientMsg::UpgradeClient(msg.clone())); match output { - Ok(_) => { - panic!("unexpected success (expected error)"); + Err(Error(ErrorDetail::LowUpgradeHeight(e), _)) => { + assert_eq!(e.upgraded_height, Height::new(0, 42)); + assert_eq!(e.client_height, msg.client_state.latest_height()); } - Err(err) => { - assert_eq!( - err.kind(), - &Kind::LowUpgradeHeight(Height::new(0, 42), msg.client_state.latest_height()) - ); + _ => { + panic!("expected LowUpgradeHeight error, instead got {:?}", output); } } } diff --git a/modules/src/ics02_client/header.rs b/modules/src/ics02_client/header.rs index 4bf8cd2986..16d9628938 100644 --- a/modules/src/ics02_client/header.rs +++ b/modules/src/ics02_client/header.rs @@ -1,15 +1,15 @@ use std::convert::TryFrom; - -use prost_types::Any; -use serde_derive::{Deserialize, Serialize}; -use tendermint_proto::Protobuf; +use std::ops::Deref; use crate::ics02_client::client_type::ClientType; -use crate::ics02_client::error::{Error, Kind}; -use crate::ics07_tendermint::header::Header as TendermintHeader; +use crate::ics02_client::error::Error; +use crate::ics07_tendermint::header::{decode_header, Header as TendermintHeader}; #[cfg(any(test, feature = "mocks"))] use crate::mock::header::MockHeader; use crate::Height; +use prost_types::Any; +use serde_derive::{Deserialize, Serialize}; +use tendermint_proto::Protobuf; pub const TENDERMINT_HEADER_TYPE_URL: &str = "/ibc.lightclients.tendermint.v1.Header"; pub const MOCK_HEADER_TYPE_URL: &str = "/ibc.mock.Header"; @@ -65,20 +65,20 @@ impl Protobuf for AnyHeader {} impl TryFrom for AnyHeader { type Error = Error; - fn try_from(raw: Any) -> Result { + fn try_from(raw: Any) -> Result { match raw.type_url.as_str() { - TENDERMINT_HEADER_TYPE_URL => Ok(AnyHeader::Tendermint( - TendermintHeader::decode_vec(&raw.value) - .map_err(|e| Kind::InvalidRawHeader.context(e))?, - )), + TENDERMINT_HEADER_TYPE_URL => { + let val = decode_header(raw.value.deref()).map_err(Error::tendermint)?; + + Ok(AnyHeader::Tendermint(val)) + } #[cfg(any(test, feature = "mocks"))] MOCK_HEADER_TYPE_URL => Ok(AnyHeader::Mock( - MockHeader::decode_vec(&raw.value) - .map_err(|e| Kind::InvalidRawHeader.context(e))?, + MockHeader::decode_vec(&raw.value).map_err(Error::invalid_raw_header)?, )), - _ => Err(Kind::UnknownHeaderType(raw.type_url).into()), + _ => Err(Error::unknown_header_type(raw.type_url)), } } } diff --git a/modules/src/ics02_client/height.rs b/modules/src/ics02_client/height.rs index 5f29755f90..b75d1ccdc5 100644 --- a/modules/src/ics02_client/height.rs +++ b/modules/src/ics02_client/height.rs @@ -1,13 +1,15 @@ use std::cmp::Ordering; -use std::convert::{Infallible, TryFrom}; +use std::convert::TryFrom; +use std::num::ParseIntError; use std::str::FromStr; +use flex_error::{define_error, TraceError}; use serde_derive::{Deserialize, Serialize}; use tendermint_proto::Protobuf; use ibc_proto::ibc::core::client::v1::Height as RawHeight; -use crate::ics02_client::error::{Error, Kind}; +use crate::ics02_client::error::Error; #[derive(Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Height { @@ -50,9 +52,7 @@ impl Height { pub fn sub(&self, delta: u64) -> Result { if self.revision_height <= delta { - return Err(Kind::InvalidHeightResult - .context("height cannot end up zero or negative") - .into()); + return Err(Error::invalid_height_result()); } Ok(Height { @@ -103,14 +103,12 @@ impl Ord for Height { impl Protobuf for Height {} -impl TryFrom for Height { - type Error = Infallible; - - fn try_from(raw: RawHeight) -> Result { - Ok(Height { +impl From for Height { + fn from(raw: RawHeight) -> Self { + Height { revision_number: raw.revision_number, revision_height: raw.revision_height, - }) + } } } @@ -139,18 +137,30 @@ impl std::fmt::Display for Height { } } +define_error! { + HeightError { + HeightConversion + { height: String } + [ TraceError ] + | e | { + format_args!("cannot convert into a `Height` type from string {0}", + e.height) + }, + } +} + impl TryFrom<&str> for Height { - type Error = Kind; + type Error = HeightError; fn try_from(value: &str) -> Result { let split: Vec<&str> = value.split('-').collect(); Ok(Height { revision_number: split[0] .parse::() - .map_err(|e| Kind::HeightConversion(value.to_owned(), e))?, + .map_err(|e| HeightError::height_conversion(value.to_owned(), e))?, revision_height: split[1] .parse::() - .map_err(|e| Kind::HeightConversion(value.to_owned(), e))?, + .map_err(|e| HeightError::height_conversion(value.to_owned(), e))?, }) } } @@ -162,7 +172,7 @@ impl From for String { } impl FromStr for Height { - type Err = Kind; + type Err = HeightError; fn from_str(s: &str) -> Result { Height::try_from(s) diff --git a/modules/src/ics02_client/misbehaviour.rs b/modules/src/ics02_client/misbehaviour.rs index e1ad1eb304..c36f985398 100644 --- a/modules/src/ics02_client/misbehaviour.rs +++ b/modules/src/ics02_client/misbehaviour.rs @@ -3,7 +3,7 @@ use std::convert::TryFrom; use prost_types::Any; use tendermint_proto::Protobuf; -use crate::ics02_client::error::{Error, Kind}; +use crate::ics02_client::error::Error; use crate::ics07_tendermint::misbehaviour::Misbehaviour as TmMisbehaviour; #[cfg(any(test, feature = "mocks"))] @@ -68,19 +68,17 @@ impl Protobuf for AnyMisbehaviour {} impl TryFrom for AnyMisbehaviour { type Error = Error; - fn try_from(raw: Any) -> Result { + fn try_from(raw: Any) -> Result { match raw.type_url.as_str() { TENDERMINT_MISBEHAVIOR_TYPE_URL => Ok(AnyMisbehaviour::Tendermint( - TmMisbehaviour::decode_vec(&raw.value) - .map_err(|e| Kind::InvalidRawMisbehaviour.context(e))?, + TmMisbehaviour::decode_vec(&raw.value).map_err(Error::decode_raw_misbehaviour)?, )), #[cfg(any(test, feature = "mocks"))] MOCK_MISBEHAVIOUR_TYPE_URL => Ok(AnyMisbehaviour::Mock( - MockMisbehaviour::decode_vec(&raw.value) - .map_err(|e| Kind::InvalidRawMisbehaviour.context(e))?, + MockMisbehaviour::decode_vec(&raw.value).map_err(Error::decode_raw_misbehaviour)?, )), - _ => Err(Kind::UnknownMisbehaviourType(raw.type_url).into()), + _ => Err(Error::unknown_misbehaviour_type(raw.type_url)), } } } diff --git a/modules/src/ics02_client/msgs/create_client.rs b/modules/src/ics02_client/msgs/create_client.rs index 5309239167..1fac028165 100644 --- a/modules/src/ics02_client/msgs/create_client.rs +++ b/modules/src/ics02_client/msgs/create_client.rs @@ -8,8 +8,7 @@ use ibc_proto::ibc::core::client::v1::MsgCreateClient as RawMsgCreateClient; use crate::ics02_client::client_consensus::AnyConsensusState; use crate::ics02_client::client_state::AnyClientState; -use crate::ics02_client::error; -use crate::ics02_client::error::{Error, Kind}; +use crate::ics02_client::error::Error; use crate::signer::Signer; use crate::tx_msg::Msg; @@ -30,11 +29,10 @@ impl MsgCreateAnyClient { signer: Signer, ) -> Result { if client_state.client_type() != consensus_state.client_type() { - return Err(error::Kind::RawClientAndConsensusStateTypesMismatch { - state_type: client_state.client_type(), - consensus_type: consensus_state.client_type(), - } - .into()); + return Err(Error::raw_client_and_consensus_state_types_mismatch( + client_state.client_type(), + consensus_state.client_type(), + )); } Ok(MsgCreateAnyClient { client_state, @@ -70,20 +68,19 @@ impl Protobuf for MsgCreateAnyClient {} impl TryFrom for MsgCreateAnyClient { type Error = Error; - fn try_from(raw: RawMsgCreateClient) -> Result { + fn try_from(raw: RawMsgCreateClient) -> Result { let raw_client_state = raw .client_state - .ok_or_else(|| Kind::InvalidRawClientState.context("missing client state"))?; + .ok_or_else(Error::missing_raw_client_state)?; let raw_consensus_state = raw .consensus_state - .ok_or_else(|| Kind::InvalidRawConsensusState.context("missing consensus state"))?; + .ok_or_else(Error::missing_raw_client_state)?; MsgCreateAnyClient::new( - AnyClientState::try_from(raw_client_state) - .map_err(|e| Kind::InvalidRawClientState.context(e))?, + AnyClientState::try_from(raw_client_state).map_err(Error::invalid_raw_client_state)?, AnyConsensusState::try_from(raw_consensus_state) - .map_err(|e| Kind::InvalidRawConsensusState.context(e))?, + .map_err(Error::invalid_raw_client_state)?, raw.signer.into(), ) } diff --git a/modules/src/ics02_client/msgs/misbehavior.rs b/modules/src/ics02_client/msgs/misbehavior.rs index b5666b7b18..7d9c01c355 100644 --- a/modules/src/ics02_client/msgs/misbehavior.rs +++ b/modules/src/ics02_client/msgs/misbehavior.rs @@ -4,7 +4,7 @@ use tendermint_proto::Protobuf; use ibc_proto::ibc::core::client::v1::MsgSubmitMisbehaviour as RawMsgSubmitMisbehaviour; -use crate::ics02_client::error::{Error, Kind}; +use crate::ics02_client::error::Error; use crate::ics02_client::misbehaviour::AnyMisbehaviour; use crate::ics24_host::identifier::ClientId; use crate::signer::Signer; @@ -42,13 +42,15 @@ impl TryFrom for MsgSubmitAnyMisbehaviour { type Error = Error; fn try_from(raw: RawMsgSubmitMisbehaviour) -> Result { - let raw_misbehaviour = raw.misbehaviour.ok_or(Kind::InvalidRawMisbehaviour)?; + let raw_misbehaviour = raw + .misbehaviour + .ok_or_else(Error::missing_raw_misbehaviour)?; Ok(MsgSubmitAnyMisbehaviour { client_id: raw .client_id .parse() - .map_err(|e| Kind::InvalidRawMisbehaviour.context(e))?, + .map_err(Error::invalid_raw_misbehaviour)?, misbehaviour: AnyMisbehaviour::try_from(raw_misbehaviour)?, signer: raw.signer.into(), }) diff --git a/modules/src/ics02_client/msgs/update_client.rs b/modules/src/ics02_client/msgs/update_client.rs index 1214f66db7..9062f203a1 100644 --- a/modules/src/ics02_client/msgs/update_client.rs +++ b/modules/src/ics02_client/msgs/update_client.rs @@ -6,7 +6,7 @@ use tendermint_proto::Protobuf; use ibc_proto::ibc::core::client::v1::MsgUpdateClient as RawMsgUpdateClient; -use crate::ics02_client::error::{Error, Kind}; +use crate::ics02_client::error::Error; use crate::ics02_client::header::AnyHeader; use crate::ics24_host::error::ValidationError; use crate::ics24_host::identifier::ClientId; @@ -52,13 +52,13 @@ impl TryFrom for MsgUpdateAnyClient { type Error = Error; fn try_from(raw: RawMsgUpdateClient) -> Result { - let raw_header = raw.header.ok_or(Kind::InvalidRawHeader)?; + let raw_header = raw.header.ok_or_else(Error::missing_raw_header)?; Ok(MsgUpdateAnyClient { client_id: raw .client_id .parse() - .map_err(|e| Kind::InvalidMsgUpdateClientId.context(e))?, + .map_err(Error::invalid_msg_update_client_id)?, header: AnyHeader::try_from(raw_header)?, signer: raw.signer.into(), }) diff --git a/modules/src/ics02_client/msgs/upgrade_client.rs b/modules/src/ics02_client/msgs/upgrade_client.rs index 70a6e1cbb5..293240409c 100644 --- a/modules/src/ics02_client/msgs/upgrade_client.rs +++ b/modules/src/ics02_client/msgs/upgrade_client.rs @@ -10,7 +10,7 @@ use ibc_proto::ibc::core::commitment::v1::MerkleProof as RawMerkleProof; use crate::ics02_client::client_consensus::AnyConsensusState; use crate::ics02_client::client_state::AnyClientState; -use crate::ics02_client::error::Kind; +use crate::ics02_client::error::Error; use crate::ics23_commitment::commitment::CommitmentProofBytes; use crate::ics24_host::identifier::ClientId; use crate::signer::Signer; @@ -80,28 +80,31 @@ impl From for RawMsgUpgradeClient { } impl TryFrom for MsgUpgradeAnyClient { - type Error = Kind; + type Error = Error; fn try_from(proto_msg: RawMsgUpgradeClient) -> Result { - let raw_client_state = proto_msg.client_state.ok_or(Kind::InvalidRawClientState)?; + let raw_client_state = proto_msg + .client_state + .ok_or_else(Error::missing_raw_client_state)?; + let raw_consensus_state = proto_msg .consensus_state - .ok_or(Kind::InvalidRawConsensusState)?; + .ok_or_else(Error::missing_raw_client_state)?; let c_bytes = CommitmentProofBytes::from(proto_msg.proof_upgrade_client); let cs_bytes = CommitmentProofBytes::from(proto_msg.proof_upgrade_consensus_state); Ok(MsgUpgradeAnyClient { client_id: ClientId::from_str(&proto_msg.client_id) - .map_err(|e| Kind::InvalidClientIdentifier(e.kind().clone()))?, + .map_err(Error::invalid_client_identifier)?, client_state: AnyClientState::try_from(raw_client_state) - .map_err(|_| Kind::InvalidRawClientState)?, + .map_err(Error::invalid_raw_client_state)?, consensus_state: AnyConsensusState::try_from(raw_consensus_state) - .map_err(|_| Kind::InvalidRawConsensusState)?, + .map_err(Error::invalid_raw_client_state)?, proof_upgrade_client: RawMerkleProof::try_from(c_bytes) - .map_err(Kind::InvalidUpgradeClientProof)?, + .map_err(Error::invalid_upgrade_client_proof)?, proof_upgrade_consensus_state: RawMerkleProof::try_from(cs_bytes) - .map_err(Kind::InvalidUpgradeConsensusStateProof)?, + .map_err(Error::invalid_upgrade_consensus_state_proof)?, signer: proto_msg.signer.into(), }) } diff --git a/modules/src/ics03_connection/connection.rs b/modules/src/ics03_connection/connection.rs index 3d2ca44401..7cde7959d4 100644 --- a/modules/src/ics03_connection/connection.rs +++ b/modules/src/ics03_connection/connection.rs @@ -3,7 +3,6 @@ use std::str::FromStr; use std::time::Duration; use std::u64; -use anomaly::fail; use serde::{Deserialize, Serialize}; use tendermint_proto::Protobuf; @@ -12,7 +11,7 @@ use ibc_proto::ibc::core::connection::v1::{ IdentifiedConnection as RawIdentifiedConnection, }; -use crate::ics03_connection::error::{self, Error, Kind}; +use crate::ics03_connection::error::Error; use crate::ics03_connection::version::Version; use crate::ics23_commitment::commitment::CommitmentPrefix; use crate::ics24_host::error::ValidationError; @@ -45,7 +44,7 @@ impl IdentifiedConnectionEnd { impl Protobuf for IdentifiedConnectionEnd {} impl TryFrom for IdentifiedConnectionEnd { - type Error = anomaly::Error; + type Error = Error; fn try_from(value: RawIdentifiedConnection) -> Result { let raw_connection_end = RawConnectionEnd { @@ -57,7 +56,7 @@ impl TryFrom for IdentifiedConnectionEnd { }; Ok(IdentifiedConnectionEnd { - connection_id: value.id.parse().map_err(|_| Kind::IdentifierError)?, + connection_id: value.id.parse().map_err(Error::invalid_identifier)?, connection_end: raw_connection_end.try_into()?, }) } @@ -105,32 +104,28 @@ impl Default for ConnectionEnd { impl Protobuf for ConnectionEnd {} impl TryFrom for ConnectionEnd { - type Error = anomaly::Error; + type Error = Error; fn try_from(value: RawConnectionEnd) -> Result { let state = value.state.try_into()?; if state == State::Uninitialized { return Ok(ConnectionEnd::default()); } if value.client_id.is_empty() { - return Err(Kind::EmptyProtoConnectionEnd.into()); + return Err(Error::empty_proto_connection_end()); } Ok(Self::new( state, - value - .client_id - .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, + value.client_id.parse().map_err(Error::invalid_identifier)?, value .counterparty - .ok_or(Kind::MissingCounterparty)? + .ok_or_else(Error::missing_counterparty)? .try_into()?, value .versions .into_iter() .map(Version::try_from) - .collect::, _>>() - .map_err(|e| Kind::InvalidVersion.context(e))?, + .collect::, _>>()?, Duration::from_nanos(value.delay_period), )) } @@ -259,23 +254,20 @@ impl Default for Counterparty { // Converts from the wire format RawCounterparty. Typically used from the relayer side // during queries for response validation and to extract the Counterparty structure. impl TryFrom for Counterparty { - type Error = anomaly::Error; + type Error = Error; fn try_from(value: RawCounterparty) -> Result { let connection_id = Some(value.connection_id) .filter(|x| !x.is_empty()) .map(|v| FromStr::from_str(v.as_str())) .transpose() - .map_err(|e| Kind::IdentifierError.context(e))?; + .map_err(Error::invalid_identifier)?; Ok(Counterparty::new( - value - .client_id - .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, + value.client_id.parse().map_err(Error::invalid_identifier)?, connection_id, value .prefix - .ok_or(Kind::MissingCounterparty)? + .ok_or_else(Error::missing_counterparty)? .key_prefix .into(), )) @@ -346,15 +338,14 @@ impl State { Self::Open => "OPEN", } } - - /// Parses the State out from a i32. + // Parses the State out from a i32. pub fn from_i32(s: i32) -> Result { match s { 0 => Ok(Self::Uninitialized), 1 => Ok(Self::Init), 2 => Ok(Self::TryOpen), 3 => Ok(Self::Open), - _ => fail!(error::Kind::InvalidState(s), s), + _ => Err(Error::invalid_state(s)), } } @@ -378,14 +369,14 @@ impl State { } impl TryFrom for State { - type Error = anomaly::Error; + type Error = Error; fn try_from(value: i32) -> Result { match value { 0 => Ok(Self::Uninitialized), 1 => Ok(Self::Init), 2 => Ok(Self::TryOpen), 3 => Ok(Self::Open), - _ => Err(Kind::InvalidState(value).into()), + _ => Err(Error::invalid_state(value)), } } } diff --git a/modules/src/ics03_connection/error.rs b/modules/src/ics03_connection/error.rs index 2249e21b49..1fb2edb280 100644 --- a/modules/src/ics03_connection/error.rs +++ b/modules/src/ics03_connection/error.rs @@ -1,103 +1,175 @@ -use anomaly::{BoxError, Context}; -use thiserror::Error; - +use crate::ics02_client::error as client_error; +use crate::ics24_host::error::ValidationError; use crate::ics24_host::identifier::{ClientId, ConnectionId}; +use crate::proofs::ProofError; use crate::Height; - -pub type Error = anomaly::Error; - -#[derive(Clone, Debug, Error, Eq, PartialEq)] -pub enum Kind { - #[error("connection state unknown")] - InvalidState(i32), - - #[error("connection exists (was initialized) already: {0}")] - ConnectionExistsAlready(ConnectionId), - - #[error("a different connection exists (was initialized) already for the same connection identifier {0}")] - ConnectionMismatch(ConnectionId), - - #[error("connection end for identifier {0} was never initialized")] - UninitializedConnection(ConnectionId), - - #[error("consensus height claimed by the client on the other party is too advanced: {0} (host chain current height: {1})")] - InvalidConsensusHeight(Height, Height), - - #[error("consensus height claimed by the client on the other party has been pruned: {0} (host chain oldest height: {1})")] - StaleConsensusHeight(Height, Height), - - #[error("identifier error")] - IdentifierError, - - #[error("ConnectionEnd domain object could not be constructed out of empty proto object")] - EmptyProtoConnectionEnd, - - #[error("invalid version")] - InvalidVersion, - - #[error("empty supported versions")] - EmptyVersions, - - #[error("no common version")] - NoCommonVersion, - - #[error("invalid address")] - InvalidAddress, - - #[error("missing consensus proof height")] - MissingProofHeight, - - #[error("missing consensus proof height")] - MissingConsensusHeight, - - #[error("invalid connection proof")] - InvalidProof, - - #[error("invalid signer")] - InvalidSigner, - - #[error("no connection was found for the previous connection id provided {0}")] - ConnectionNotFound(ConnectionId), - - #[error("invalid counterparty")] - InvalidCounterparty, - - #[error("counterparty chosen connection id {0} is different than the connection id {1}")] - ConnectionIdMismatch(ConnectionId, ConnectionId), - - #[error("missing counterparty")] - MissingCounterparty, - - #[error("missing counterparty prefix")] - MissingCounterpartyPrefix, - - #[error("the client id does not match any client state: {0}")] - MissingClient(ClientId), - - #[error("client proof must be present")] - NullClientProof, - - #[error("the client {0} running locally is frozen")] - FrozenClient(ClientId), - - #[error("the connection proof verification failed")] - ConnectionVerificationFailure, - - #[error("the consensus state at height {0} for client id {1} could not be retrieved")] - MissingClientConsensusState(Height, ClientId), - - #[error("the local consensus state could not be retrieved")] - MissingLocalConsensusState, - - #[error("the consensus proof verification failed (height: {0})")] - ConsensusStateVerificationFailure(Height), - - #[error("the client state proof verification failed for client id: {0}")] - ClientStateVerificationFailure(ClientId), -} - -impl Kind { - pub fn context(self, source: impl Into) -> Context { - Context::new(self, Some(source.into())) +use flex_error::define_error; + +define_error! { + Error { + Ics02Client + [ client_error::Error ] + | _ | { "ics02 client error" }, + + InvalidState + { state: i32 } + | e | { format_args!("connection state is unknown: {}", e.state) }, + + ConnectionExistsAlready + { connection_id: ConnectionId } + | e | { + format_args!("connection exists (was initialized) already: {0}", + e.connection_id) + }, + + ConnectionMismatch + { connection_id: ConnectionId } + | e | { + format_args!("connection end for identifier {0} was never initialized", + e.connection_id) + }, + + UninitializedConnection + { connection_id: ConnectionId } + | e | { + format_args!("connection end for identifier {0} was never initialized", + e.connection_id) + }, + + InvalidConsensusHeight + { + target_height: Height, + currrent_height: Height + } + | e | { + format_args!("consensus height claimed by the client on the other party is too advanced: {0} (host chain current height: {1})", + e.target_height, e.currrent_height) + }, + + StaleConsensusHeight + { + target_height: Height, + oldest_height: Height + } + | e | { + format_args!("consensus height claimed by the client on the other party has been pruned: {0} (host chain oldest height: {1})", + e.target_height, e.oldest_height) + }, + + InvalidIdentifier + [ ValidationError ] + | _ | { "identifier error" }, + + EmptyProtoConnectionEnd + | _ | { "ConnectionEnd domain object could not be constructed out of empty proto object" }, + + EmptyVersions + | _ | { "empty supported versions" }, + + EmptyFeatures + | _ | { "empty supported features" }, + + NoCommonVersion + | _ | { "no common version" }, + + InvalidAddress + | _ | { "invalid address" }, + + MissingProofHeight + | _ | { "missing proof height" }, + + MissingConsensusHeight + | _ | { "missing consensus height" }, + + InvalidProof + [ ProofError ] + | _ | { "invalid connection proof" }, + + VerifyConnectionState + [ client_error::Error ] + | _ | { "error verifying connnection state" }, + + InvalidSigner + | _ | { "invalid signer" }, + + ConnectionNotFound + { connection_id: ConnectionId } + | e | { + format_args!("no connection was found for the previous connection id provided {0}", + e.connection_id) + }, + + InvalidCounterparty + | _ | { "invalid signer" }, + + ConnectionIdMismatch + { + connection_id: ConnectionId, + counterparty_connection_id: ConnectionId, + } + | e | { + format_args!("counterparty chosen connection id {0} is different than the connection id {1}", + e.connection_id, e.counterparty_connection_id) + }, + + MissingCounterparty + | _ | { "missing counterparty" }, + + + MissingCounterpartyPrefix + | _ | { "missing counterparty prefix" }, + + MissingClient + { client_id: ClientId } + | e | { + format_args!("the client id does not match any client state: {0}", + e.client_id) + }, + + NullClientProof + | _ | { "client proof must be present" }, + + FrozenClient + { client_id: ClientId } + | e | { + format_args!("the client id does not match any client state: {0}", + e.client_id) + }, + + ConnectionVerificationFailure + | _ | { "the connection proof verification failed" }, + + MissingClientConsensusState + { + height: Height, + client_id: ClientId, + } + | e | { + format_args!("the consensus state at height {0} for client id {1} could not be retrieved", + e.height, e.client_id) + }, + + MissingLocalConsensusState + { height: Height } + | e | { format_args!("the local consensus state could not be retrieved for height {}", e.height) }, + + ConsensusStateVerificationFailure + { height: Height } + [ client_error::Error ] + | e | { + format_args!("the consensus proof verification failed (height: {0})", + e.height) + }, + + // TODO: use more specific error source + ClientStateVerificationFailure + { + client_id: ClientId, + } + [ client_error::Error ] + | e | { + format_args!("the client state proof verification failed for client id {0}", + e.client_id) + }, } } diff --git a/modules/src/ics03_connection/events.rs b/modules/src/ics03_connection/events.rs index 13243d1f25..fd5b1900da 100644 --- a/modules/src/ics03_connection/events.rs +++ b/modules/src/ics03_connection/events.rs @@ -1,9 +1,7 @@ //! Types for the IBC events emitted from Tendermint Websocket by the connection module. -use crate::events::{IbcEvent, RawObject}; +use crate::events::{extract_attribute, maybe_extract_attribute, Error, IbcEvent, RawObject}; use crate::ics02_client::height::Height; use crate::ics24_host::identifier::{ClientId, ConnectionId}; -use crate::{attribute, some_attribute}; -use anomaly::BoxError; use serde_derive::{Deserialize, Serialize}; use std::convert::TryFrom; @@ -69,6 +67,32 @@ pub struct Attributes { pub counterparty_client_id: ClientId, } +fn extract_attributes(object: &RawObject, namespace: &str) -> Result { + Ok(Attributes { + height: object.height, + + connection_id: maybe_extract_attribute(&object, &format!("{}.connection_id", namespace)) + .and_then(|val| val.parse().ok()), + + client_id: extract_attribute(&object, &format!("{}.client_id", namespace))? + .parse() + .map_err(Error::parse)?, + + counterparty_connection_id: maybe_extract_attribute( + &object, + &format!("{}.counterparty_connection_id", namespace), + ) + .and_then(|val| val.parse().ok()), + + counterparty_client_id: extract_attribute( + &object, + &format!("{}.counterparty_client_id", namespace), + )? + .parse() + .map_err(Error::parse)?, + }) +} + impl Default for Attributes { fn default() -> Self { Attributes { @@ -106,18 +130,9 @@ impl From for OpenInit { } impl TryFrom for OpenInit { - type Error = BoxError; + type Error = Error; fn try_from(obj: RawObject) -> Result { - Ok(OpenInit(Attributes { - height: obj.height, - connection_id: some_attribute!(obj, "connection_open_init.connection_id"), - client_id: attribute!(obj, "connection_open_init.client_id"), - counterparty_connection_id: some_attribute!( - obj, - "connection_open_init.counterparty_connection_id" - ), - counterparty_client_id: attribute!(obj, "connection_open_init.counterparty_client_id"), - })) + Ok(OpenInit(extract_attributes(&obj, "connection_open_init")?)) } } @@ -152,18 +167,9 @@ impl From for OpenTry { } impl TryFrom for OpenTry { - type Error = BoxError; + type Error = Error; fn try_from(obj: RawObject) -> Result { - Ok(OpenTry(Attributes { - height: obj.height, - connection_id: some_attribute!(obj, "connection_open_try.connection_id"), - client_id: attribute!(obj, "connection_open_try.client_id"), - counterparty_connection_id: some_attribute!( - obj, - "connection_open_try.counterparty_connection_id" - ), - counterparty_client_id: attribute!(obj, "connection_open_try.counterparty_client_id"), - })) + Ok(OpenTry(extract_attributes(&obj, "connection_open_try")?)) } } @@ -198,18 +204,9 @@ impl From for OpenAck { } impl TryFrom for OpenAck { - type Error = BoxError; + type Error = Error; fn try_from(obj: RawObject) -> Result { - Ok(OpenAck(Attributes { - height: obj.height, - connection_id: some_attribute!(obj, "connection_open_ack.connection_id"), - client_id: attribute!(obj, "connection_open_ack.client_id"), - counterparty_connection_id: some_attribute!( - obj, - "connection_open_ack.counterparty_connection_id" - ), - counterparty_client_id: attribute!(obj, "connection_open_ack.counterparty_client_id"), - })) + Ok(OpenAck(extract_attributes(&obj, "connection_open_ack")?)) } } @@ -244,21 +241,12 @@ impl From for OpenConfirm { } impl TryFrom for OpenConfirm { - type Error = BoxError; + type Error = Error; fn try_from(obj: RawObject) -> Result { - Ok(OpenConfirm(Attributes { - height: obj.height, - connection_id: some_attribute!(obj, "connection_open_confirm.connection_id"), - client_id: attribute!(obj, "connection_open_confirm.client_id"), - counterparty_connection_id: some_attribute!( - obj, - "connection_open_confirm.counterparty_connection_id" - ), - counterparty_client_id: attribute!( - obj, - "connection_open_confirm.counterparty_client_id" - ), - })) + Ok(OpenConfirm(extract_attributes( + &obj, + "connection_open_confirm", + )?)) } } diff --git a/modules/src/ics03_connection/handler/conn_open_ack.rs b/modules/src/ics03_connection/handler/conn_open_ack.rs index d30aa338d7..306980cbe6 100644 --- a/modules/src/ics03_connection/handler/conn_open_ack.rs +++ b/modules/src/ics03_connection/handler/conn_open_ack.rs @@ -4,7 +4,7 @@ use crate::events::IbcEvent; use crate::handler::{HandlerOutput, HandlerResult}; use crate::ics03_connection::connection::{ConnectionEnd, Counterparty, State}; use crate::ics03_connection::context::ConnectionReader; -use crate::ics03_connection::error::{Error, Kind}; +use crate::ics03_connection::error::Error; use crate::ics03_connection::events::Attributes; use crate::ics03_connection::handler::verify::{check_client_consensus_height, verify_proofs}; use crate::ics03_connection::handler::{ConnectionIdState, ConnectionResult}; @@ -44,16 +44,12 @@ pub(crate) fn process( Ok(old_conn_end) } else { // Old connection end is in incorrect state, propagate the error. - Err(Into::::into(Kind::ConnectionMismatch( - msg.connection_id().clone(), - ))) + Err(Error::connection_mismatch(msg.connection_id().clone())) } } None => { // No connection end exists for this conn. identifier. Impossible to continue handshake. - Err(Into::::into(Kind::UninitializedConnection( - msg.connection_id().clone(), - ))) + Err(Error::uninitialized_connection(msg.connection_id().clone())) } }?; @@ -108,7 +104,7 @@ mod tests { use crate::events::IbcEvent; use crate::ics03_connection::connection::{ConnectionEnd, Counterparty, State}; - use crate::ics03_connection::error::Kind; + use crate::ics03_connection::error; use crate::ics03_connection::handler::{dispatch, ConnectionResult}; use crate::ics03_connection::msgs::conn_open_ack::test_util::get_dummy_raw_msg_conn_open_ack; use crate::ics03_connection::msgs::conn_open_ack::MsgConnectionOpenAck; @@ -126,7 +122,7 @@ mod tests { ctx: MockContext, msg: ConnectionMsg, want_pass: bool, - error_kind: Option, + match_error: Box, } let msg_ack = @@ -184,14 +180,28 @@ mod tests { .with_connection(conn_id.clone(), default_conn_end), msg: ConnectionMsg::ConnectionOpenAck(Box::new(msg_ack.clone())), want_pass: true, - error_kind: None, + match_error: Box::new(|_| { + panic!("should not have error") + }), }, Test { name: "Processing fails because the connection does not exist in the context".to_string(), ctx: default_context.clone(), msg: ConnectionMsg::ConnectionOpenAck(Box::new(msg_ack.clone())), want_pass: false, - error_kind: Some(Kind::UninitializedConnection(conn_id.clone())), + match_error: { + let connection_id = conn_id.clone(); + Box::new(move |e| { + match e.detail() { + error::ErrorDetail::UninitializedConnection(e) => { + assert_eq!(e.connection_id, connection_id) + } + _ => { + panic!("Expected UninitializedConnection error"); + } + } + }) + }, }, Test { name: "Processing fails due to connections mismatch (incorrect 'open' state)".to_string(), @@ -201,7 +211,19 @@ mod tests { .with_connection(conn_id.clone(), conn_end_open), msg: ConnectionMsg::ConnectionOpenAck(Box::new(msg_ack.clone())), want_pass: false, - error_kind: Some(Kind::ConnectionMismatch(conn_id.clone())) + match_error: { + let connection_id = conn_id.clone(); + Box::new(move |e| { + match e.detail() { + error::ErrorDetail::ConnectionMismatch(e) => { + assert_eq!(e.connection_id, connection_id); + } + _ => { + panic!("Expected ConnectionMismatch error"); + } + } + }) + }, }, Test { name: "Processing fails: ConsensusStateVerificationFailure due to empty counterparty prefix".to_string(), @@ -210,7 +232,17 @@ mod tests { .with_connection(conn_id, conn_end_prefix), msg: ConnectionMsg::ConnectionOpenAck(Box::new(msg_ack)), want_pass: false, - error_kind: Some(Kind::ConsensusStateVerificationFailure(proof_height)) + match_error: + Box::new(move |e| { + match e.detail() { + error::ErrorDetail::ConsensusStateVerificationFailure(e) => { + assert_eq!(e.height, proof_height) + } + _ => { + panic!("Expected ConsensusStateVerificationFailure error"); + } + } + }), }, /* Test { @@ -259,16 +291,7 @@ mod tests { ); // Verify that the error kind matches - if let Some(expected_kind) = test.error_kind { - assert_eq!( - &expected_kind, - e.kind(), - "conn_open_ack: failed for test: {}\nexpected error kind: {:?}\nfound: {:?}", - test.name, - expected_kind, - e.kind() - ) - } + (test.match_error)(e); } } } diff --git a/modules/src/ics03_connection/handler/conn_open_confirm.rs b/modules/src/ics03_connection/handler/conn_open_confirm.rs index 664b826ef4..5d3ba0c1fe 100644 --- a/modules/src/ics03_connection/handler/conn_open_confirm.rs +++ b/modules/src/ics03_connection/handler/conn_open_confirm.rs @@ -4,7 +4,7 @@ use crate::events::IbcEvent; use crate::handler::{HandlerOutput, HandlerResult}; use crate::ics03_connection::connection::{ConnectionEnd, Counterparty, State}; use crate::ics03_connection::context::ConnectionReader; -use crate::ics03_connection::error::{Error, Kind}; +use crate::ics03_connection::error::Error; use crate::ics03_connection::events::Attributes; use crate::ics03_connection::handler::verify::verify_proofs; use crate::ics03_connection::handler::{ConnectionIdState, ConnectionResult}; @@ -22,18 +22,14 @@ pub(crate) fn process( Some(old_conn_end) => { if !(old_conn_end.state_matches(&State::TryOpen)) { // Old connection end is in incorrect state, propagate the error. - Err(Into::::into(Kind::ConnectionMismatch( - msg.connection_id().clone(), - ))) + Err(Error::connection_mismatch(msg.connection_id().clone())) } else { Ok(old_conn_end) } } None => { // No connection end exists for this conn. identifier. Impossible to continue handshake. - Err(Into::::into(Kind::UninitializedConnection( - msg.connection_id().clone(), - ))) + Err(Error::uninitialized_connection(msg.connection_id().clone())) } }?; diff --git a/modules/src/ics03_connection/handler/conn_open_init.rs b/modules/src/ics03_connection/handler/conn_open_init.rs index ee9fc12b7a..ceb583a072 100644 --- a/modules/src/ics03_connection/handler/conn_open_init.rs +++ b/modules/src/ics03_connection/handler/conn_open_init.rs @@ -4,7 +4,7 @@ use crate::events::IbcEvent; use crate::handler::{HandlerOutput, HandlerResult}; use crate::ics03_connection::connection::{ConnectionEnd, State}; use crate::ics03_connection::context::ConnectionReader; -use crate::ics03_connection::error::{Error, Kind}; +use crate::ics03_connection::error::Error; use crate::ics03_connection::events::Attributes; use crate::ics03_connection::handler::{ConnectionIdState, ConnectionResult}; use crate::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; @@ -18,7 +18,7 @@ pub(crate) fn process( // An IBC client running on the local (host) chain should exist. if ctx.client_state(msg.client_id()).is_none() { - return Err(Kind::MissingClient(msg.client_id().clone()).into()); + return Err(Error::missing_client(msg.client_id().clone())); } let new_connection_end = ConnectionEnd::new( diff --git a/modules/src/ics03_connection/handler/conn_open_try.rs b/modules/src/ics03_connection/handler/conn_open_try.rs index fe05fd6b3a..ed59f58427 100644 --- a/modules/src/ics03_connection/handler/conn_open_try.rs +++ b/modules/src/ics03_connection/handler/conn_open_try.rs @@ -4,7 +4,7 @@ use crate::events::IbcEvent; use crate::handler::{HandlerOutput, HandlerResult}; use crate::ics03_connection::connection::{ConnectionEnd, Counterparty, State}; use crate::ics03_connection::context::ConnectionReader; -use crate::ics03_connection::error::{Error, Kind}; +use crate::ics03_connection::error::Error; use crate::ics03_connection::events::Attributes; use crate::ics03_connection::handler::verify::{check_client_consensus_height, verify_proofs}; use crate::ics03_connection::handler::{ConnectionIdState, ConnectionResult}; @@ -26,7 +26,7 @@ pub(crate) fn process( Some(prev_id) => { let old_connection_end = ctx .connection_end(prev_id) - .ok_or_else(|| Kind::ConnectionNotFound(prev_id.clone()))?; + .ok_or_else(|| Error::connection_not_found(prev_id.clone()))?; // Validate that existing connection end matches with the one we're trying to establish. if old_connection_end.state_matches(&State::Init) @@ -42,9 +42,7 @@ pub(crate) fn process( Ok((old_connection_end, prev_id.clone())) } else { // A ConnectionEnd already exists and validation failed. - Err(Into::::into(Kind::ConnectionMismatch( - prev_id.clone(), - ))) + Err(Error::connection_mismatch(prev_id.clone())) } } // No prev. connection id was supplied, create a new connection end and conn id. @@ -93,7 +91,7 @@ pub(crate) fn process( // Pick the version. new_connection_end.set_version( ctx.pick_version(ctx.get_compatible_versions(), msg.counterparty_versions()) - .ok_or(Kind::NoCommonVersion)?, + .ok_or_else(Error::no_common_version)?, ); assert_eq!(new_connection_end.versions().len(), 1); diff --git a/modules/src/ics03_connection/handler/verify.rs b/modules/src/ics03_connection/handler/verify.rs index 217f6239c0..2e287e6d2d 100644 --- a/modules/src/ics03_connection/handler/verify.rs +++ b/modules/src/ics03_connection/handler/verify.rs @@ -5,7 +5,7 @@ use crate::ics02_client::client_state::{AnyClientState, ClientState}; use crate::ics02_client::{client_def::AnyClient, client_def::ClientDef}; use crate::ics03_connection::connection::ConnectionEnd; use crate::ics03_connection::context::ConnectionReader; -use crate::ics03_connection::error::{Error, Kind}; +use crate::ics03_connection::error::Error; use crate::ics23_commitment::commitment::CommitmentProofBytes; use crate::proofs::{ConsensusProof, Proofs}; use crate::Height; @@ -36,7 +36,7 @@ pub fn verify_proofs( proofs .client_proof() .as_ref() - .ok_or(Kind::NullClientProof)?, + .ok_or_else(Error::null_client_proof)?, )?; } @@ -66,11 +66,11 @@ pub fn verify_connection_proof( // Fetch the client state (IBC client on the local/host chain). let client_state = ctx .client_state(connection_end.client_id()) - .ok_or_else(|| Kind::MissingClient(connection_end.client_id().clone()))?; + .ok_or_else(|| Error::missing_client(connection_end.client_id().clone()))?; // The client must not be frozen. if client_state.is_frozen() { - return Err(Kind::FrozenClient(connection_end.client_id().clone()).into()); + return Err(Error::frozen_client(connection_end.client_id().clone())); } // The client must have the consensus state for the height where this proof was created. @@ -78,11 +78,10 @@ pub fn verify_connection_proof( .client_consensus_state(connection_end.client_id(), proof_height) .is_none() { - return Err(Kind::MissingClientConsensusState( + return Err(Error::missing_client_consensus_state( proof_height, connection_end.client_id().clone(), - ) - .into()); + )); } let client_def = AnyClient::from_client_type(client_state.client_type()); @@ -90,7 +89,7 @@ pub fn verify_connection_proof( // Verify the proof for the connection state against the expected connection end. // A counterparty connection id of None causes `unwrap()` below and indicates an internal // error as this is the connection id on the counterparty chain that must always be present. - Ok(client_def + client_def .verify_connection_state( &client_state, proof_height, @@ -99,7 +98,7 @@ pub fn verify_connection_proof( connection_end.counterparty().connection_id(), expected_conn, ) - .map_err(|_| Kind::InvalidProof)?) + .map_err(Error::verify_connection_state) } /// Verifies the client `proof` from a connection handshake message, typically from a @@ -119,21 +118,21 @@ pub fn verify_client_proof( // Fetch the local client state (IBC client running on the host chain). let client_state = ctx .client_state(connection_end.client_id()) - .ok_or_else(|| Kind::MissingClient(connection_end.client_id().clone()))?; + .ok_or_else(|| Error::missing_client(connection_end.client_id().clone()))?; if client_state.is_frozen() { - return Err(Kind::FrozenClient(connection_end.client_id().clone()).into()); + return Err(Error::frozen_client(connection_end.client_id().clone())); } let consensus_state = ctx .client_consensus_state(connection_end.client_id(), proof_height) .ok_or_else(|| { - Kind::MissingClientConsensusState(proof_height, connection_end.client_id().clone()) + Error::missing_client_consensus_state(proof_height, connection_end.client_id().clone()) })?; let client_def = AnyClient::from_client_type(client_state.client_type()); - Ok(client_def + client_def .verify_client_full_state( &client_state, proof_height, @@ -144,9 +143,8 @@ pub fn verify_client_proof( &expected_client_state, ) .map_err(|e| { - Kind::ClientStateVerificationFailure(connection_end.client_id().clone()) - .context(e.to_string()) - })?) + Error::client_state_verification_failure(connection_end.client_id().clone(), e) + }) } pub fn verify_consensus_proof( @@ -158,20 +156,20 @@ pub fn verify_consensus_proof( // Fetch the client state (IBC client on the local chain). let client_state = ctx .client_state(connection_end.client_id()) - .ok_or_else(|| Kind::MissingClient(connection_end.client_id().clone()))?; + .ok_or_else(|| Error::missing_client(connection_end.client_id().clone()))?; if client_state.is_frozen() { - return Err(Kind::FrozenClient(connection_end.client_id().clone()).into()); + return Err(Error::frozen_client(connection_end.client_id().clone())); } // Fetch the expected consensus state from the historical (local) header data. let expected_consensus = ctx .host_consensus_state(proof.height()) - .ok_or_else(|| Kind::MissingLocalConsensusState.context(proof.height().to_string()))?; + .ok_or_else(|| Error::missing_local_consensus_state(proof.height()))?; let client = AnyClient::from_client_type(client_state.client_type()); - Ok(client + client .verify_client_consensus_state( &client_state, proof_height, @@ -181,9 +179,7 @@ pub fn verify_consensus_proof( proof.height(), &expected_consensus, ) - .map_err(|e| { - Kind::ConsensusStateVerificationFailure(proof.height()).context(e.to_string()) - })?) + .map_err(|e| Error::consensus_state_verification_failure(proof.height(), e)) } /// Checks that `claimed_height` is within normal bounds, i.e., fresh enough so that the chain has @@ -194,12 +190,18 @@ pub fn check_client_consensus_height( ) -> Result<(), Error> { if claimed_height > ctx.host_current_height() { // Fail if the consensus height is too advanced. - return Err(Kind::InvalidConsensusHeight(claimed_height, ctx.host_current_height()).into()); + return Err(Error::invalid_consensus_height( + claimed_height, + ctx.host_current_height(), + )); } if claimed_height < ctx.host_oldest_height() { // Fail if the consensus height is too old (has been pruned). - return Err(Kind::StaleConsensusHeight(claimed_height, ctx.host_oldest_height()).into()); + return Err(Error::stale_consensus_height( + claimed_height, + ctx.host_oldest_height(), + )); } // Height check is within normal bounds, check passes. diff --git a/modules/src/ics03_connection/msgs/conn_open_ack.rs b/modules/src/ics03_connection/msgs/conn_open_ack.rs index 41868d9916..e48f5be1c0 100644 --- a/modules/src/ics03_connection/msgs/conn_open_ack.rs +++ b/modules/src/ics03_connection/msgs/conn_open_ack.rs @@ -5,7 +5,7 @@ use tendermint_proto::Protobuf; use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenAck as RawMsgConnectionOpenAck; use crate::ics02_client::client_state::AnyClientState; -use crate::ics03_connection::error::{Error, Kind}; +use crate::ics03_connection::error::Error; use crate::ics03_connection::version::Version; use crate::ics23_commitment::commitment::CommitmentProofBytes; use crate::ics24_host::identifier::ConnectionId; @@ -79,22 +79,20 @@ impl Msg for MsgConnectionOpenAck { impl Protobuf for MsgConnectionOpenAck {} impl TryFrom for MsgConnectionOpenAck { - type Error = anomaly::Error; + type Error = Error; fn try_from(msg: RawMsgConnectionOpenAck) -> Result { let consensus_height = msg .consensus_height - .ok_or(Kind::MissingConsensusHeight)? - .try_into() // Cast from the raw height type into the domain type. - .map_err(|e| Kind::InvalidProof.context(e))?; + .ok_or_else(Error::missing_consensus_height)? + .into(); let consensus_proof_obj = ConsensusProof::new(msg.proof_consensus.into(), consensus_height) - .map_err(|e| Kind::InvalidProof.context(e))?; + .map_err(Error::invalid_proof)?; let proof_height = msg .proof_height - .ok_or(Kind::MissingProofHeight)? - .try_into() - .map_err(|e| Kind::InvalidProof.context(e))?; + .ok_or_else(Error::missing_proof_height)? + .into(); let client_proof = Some(msg.proof_client) .filter(|x| !x.is_empty()) @@ -104,21 +102,17 @@ impl TryFrom for MsgConnectionOpenAck { connection_id: msg .connection_id .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, + .map_err(Error::invalid_identifier)?, counterparty_connection_id: msg .counterparty_connection_id .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, + .map_err(Error::invalid_identifier)?, client_state: msg .client_state .map(AnyClientState::try_from) .transpose() - .map_err(|e| Kind::InvalidProof.context(e))?, - version: msg - .version - .ok_or(Kind::InvalidVersion)? - .try_into() - .map_err(|e| Kind::InvalidVersion.context(e))?, + .map_err(Error::ics02_client)?, + version: msg.version.ok_or_else(Error::empty_versions)?.try_into()?, proofs: Proofs::new( msg.proof_try.into(), client_proof, @@ -126,7 +120,7 @@ impl TryFrom for MsgConnectionOpenAck { None, proof_height, ) - .map_err(|e| Kind::InvalidProof.context(e))?, + .map_err(Error::invalid_proof)?, signer: msg.signer.into(), }) } diff --git a/modules/src/ics03_connection/msgs/conn_open_confirm.rs b/modules/src/ics03_connection/msgs/conn_open_confirm.rs index 0e8bfd6a04..bdf2ae0279 100644 --- a/modules/src/ics03_connection/msgs/conn_open_confirm.rs +++ b/modules/src/ics03_connection/msgs/conn_open_confirm.rs @@ -1,10 +1,10 @@ -use std::convert::{TryFrom, TryInto}; +use std::convert::TryFrom; use tendermint_proto::Protobuf; use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenConfirm as RawMsgConnectionOpenConfirm; -use crate::ics03_connection::error::{Error, Kind}; +use crate::ics03_connection::error::Error; use crate::ics24_host::identifier::ConnectionId; use crate::proofs::Proofs; use crate::signer::Signer; @@ -50,21 +50,21 @@ impl Msg for MsgConnectionOpenConfirm { impl Protobuf for MsgConnectionOpenConfirm {} impl TryFrom for MsgConnectionOpenConfirm { - type Error = anomaly::Error; + type Error = Error; fn try_from(msg: RawMsgConnectionOpenConfirm) -> Result { let proof_height = msg .proof_height - .ok_or(Kind::MissingProofHeight)? - .try_into() // Cast from the raw height type into the domain type. - .map_err(|e| Kind::InvalidProof.context(e))?; + .ok_or_else(Error::missing_proof_height)? + .into(); + Ok(Self { connection_id: msg .connection_id .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, + .map_err(Error::invalid_identifier)?, proofs: Proofs::new(msg.proof_ack.into(), None, None, None, proof_height) - .map_err(|e| Kind::InvalidProof.context(e))?, + .map_err(Error::invalid_proof)?, signer: msg.signer.into(), }) } diff --git a/modules/src/ics03_connection/msgs/conn_open_init.rs b/modules/src/ics03_connection/msgs/conn_open_init.rs index ae8eb8d515..06a93e2c81 100644 --- a/modules/src/ics03_connection/msgs/conn_open_init.rs +++ b/modules/src/ics03_connection/msgs/conn_open_init.rs @@ -5,7 +5,7 @@ use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenInit as RawMsgConnect use tendermint_proto::Protobuf; use crate::ics03_connection::connection::Counterparty; -use crate::ics03_connection::error::{Error, Kind}; +use crate::ics03_connection::error::Error; use crate::ics03_connection::version::Version; use crate::ics24_host::identifier::ClientId; use crate::signer::Signer; @@ -53,23 +53,16 @@ impl Msg for MsgConnectionOpenInit { impl Protobuf for MsgConnectionOpenInit {} impl TryFrom for MsgConnectionOpenInit { - type Error = anomaly::Error; + type Error = Error; fn try_from(msg: RawMsgConnectionOpenInit) -> Result { Ok(Self { - client_id: msg - .client_id - .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, + client_id: msg.client_id.parse().map_err(Error::invalid_identifier)?, counterparty: msg .counterparty - .ok_or(Kind::MissingCounterparty)? + .ok_or_else(Error::missing_counterparty)? .try_into()?, - version: msg - .version - .ok_or(Kind::InvalidVersion)? - .try_into() - .map_err(|e| Kind::InvalidVersion.context(e))?, + version: msg.version.ok_or_else(Error::empty_versions)?.try_into()?, delay_period: Duration::from_nanos(msg.delay_period), signer: msg.signer.into(), }) diff --git a/modules/src/ics03_connection/msgs/conn_open_try.rs b/modules/src/ics03_connection/msgs/conn_open_try.rs index 7bc9ee6b82..6432948eb0 100644 --- a/modules/src/ics03_connection/msgs/conn_open_try.rs +++ b/modules/src/ics03_connection/msgs/conn_open_try.rs @@ -10,7 +10,7 @@ use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenTry as RawMsgConnecti use crate::ics02_client::client_state::AnyClientState; use crate::ics03_connection::connection::Counterparty; -use crate::ics03_connection::error::{Error, Kind}; +use crate::ics03_connection::error::Error; use crate::ics03_connection::version::Version; use crate::ics23_commitment::commitment::CommitmentProofBytes; use crate::ics24_host::identifier::{ClientId, ConnectionId}; @@ -100,22 +100,20 @@ impl TryFrom for MsgConnectionOpenTry { .filter(|x| !x.is_empty()) .map(|v| FromStr::from_str(v.as_str())) .transpose() - .map_err(|e| Kind::IdentifierError.context(e))?; + .map_err(Error::invalid_identifier)?; let consensus_height = msg .consensus_height - .ok_or(Kind::MissingConsensusHeight)? - .try_into() // Cast from the raw height type into the domain type. - .map_err(|e| Kind::InvalidProof.context(e))?; + .ok_or_else(Error::missing_consensus_height)? + .into(); let consensus_proof_obj = ConsensusProof::new(msg.proof_consensus.into(), consensus_height) - .map_err(|e| Kind::InvalidProof.context(e))?; + .map_err(Error::invalid_proof)?; let proof_height = msg .proof_height - .ok_or(Kind::MissingProofHeight)? - .try_into() - .map_err(|e| Kind::InvalidProof.context(e))?; + .ok_or_else(Error::missing_proof_height)? + .into(); let client_proof = Some(msg.proof_client) .filter(|x| !x.is_empty()) @@ -125,29 +123,23 @@ impl TryFrom for MsgConnectionOpenTry { .counterparty_versions .into_iter() .map(Version::try_from) - .collect::, _>>() - .map_err(|e| Kind::InvalidVersion.context(e))?; + .collect::, _>>()?; if counterparty_versions.is_empty() { - return Err(Kind::EmptyVersions - .context("empty counterparty versions in try message".to_string()) - .into()); + return Err(Error::empty_versions()); } Ok(Self { previous_connection_id, - client_id: msg - .client_id - .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, + client_id: msg.client_id.parse().map_err(Error::invalid_identifier)?, client_state: msg .client_state .map(AnyClientState::try_from) .transpose() - .map_err(|e| Kind::InvalidProof.context(e))?, + .map_err(Error::ics02_client)?, counterparty: msg .counterparty - .ok_or(Kind::MissingCounterparty)? + .ok_or_else(Error::missing_counterparty)? .try_into()?, counterparty_versions, proofs: Proofs::new( @@ -157,7 +149,7 @@ impl TryFrom for MsgConnectionOpenTry { None, proof_height, ) - .map_err(|e| Kind::InvalidProof.context(e))?, + .map_err(Error::invalid_proof)?, delay_period: Duration::from_nanos(msg.delay_period), signer: msg.signer.into(), }) diff --git a/modules/src/ics03_connection/version.rs b/modules/src/ics03_connection/version.rs index d24e8d2f32..8fa897ca9c 100644 --- a/modules/src/ics03_connection/version.rs +++ b/modules/src/ics03_connection/version.rs @@ -5,7 +5,7 @@ use tendermint_proto::Protobuf; use ibc_proto::ibc::core::connection::v1::Version as RawVersion; -use crate::ics03_connection::error::Kind; +use crate::ics03_connection::error::Error; /// Stores the identifier and the features supported by a version #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -26,18 +26,14 @@ impl Version { impl Protobuf for Version {} impl TryFrom for Version { - type Error = anomaly::Error; + type Error = Error; fn try_from(value: RawVersion) -> Result { if value.identifier.trim().is_empty() { - return Err(Kind::InvalidVersion - .context("empty version string".to_string()) - .into()); + return Err(Error::empty_versions()); } for feature in value.features.iter() { if feature.trim().is_empty() { - return Err(Kind::InvalidVersion - .context("empty feature string".to_string()) - .into()); + return Err(Error::empty_features()); } } Ok(Version { diff --git a/modules/src/ics04_channel/channel.rs b/modules/src/ics04_channel/channel.rs index f3370e5db5..1391c45d01 100644 --- a/modules/src/ics04_channel/channel.rs +++ b/modules/src/ics04_channel/channel.rs @@ -2,7 +2,6 @@ use std::convert::{TryFrom, TryInto}; use std::fmt; use std::str::FromStr; -use anomaly::fail; use serde::{Deserialize, Serialize}; use tendermint_proto::Protobuf; @@ -13,10 +12,7 @@ use ibc_proto::ibc::core::channel::v1::{ use crate::events::IbcEventType; use crate::ics02_client::height::Height; -use crate::ics04_channel::{ - error::{self, Error, Kind}, - packet::Sequence, -}; +use crate::ics04_channel::{error::Error, packet::Sequence}; use crate::ics24_host::identifier::{ChannelId, ConnectionId, PortId}; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -39,7 +35,7 @@ impl IdentifiedChannelEnd { impl Protobuf for IdentifiedChannelEnd {} impl TryFrom for IdentifiedChannelEnd { - type Error = anomaly::Error; + type Error = Error; fn try_from(value: RawIdentifiedChannel) -> Result { let raw_channel_end = RawChannel { @@ -51,11 +47,8 @@ impl TryFrom for IdentifiedChannelEnd { }; Ok(IdentifiedChannelEnd { - port_id: value.port_id.parse().map_err(|_| Kind::IdentifierError)?, - channel_id: value - .channel_id - .parse() - .map_err(|_| Kind::IdentifierError)?, + port_id: value.port_id.parse().map_err(Error::identifier)?, + channel_id: value.channel_id.parse().map_err(Error::identifier)?, channel_end: raw_channel_end.try_into()?, }) } @@ -104,7 +97,7 @@ impl Default for ChannelEnd { impl Protobuf for ChannelEnd {} impl TryFrom for ChannelEnd { - type Error = anomaly::Error; + type Error = Error; fn try_from(value: RawChannel) -> Result { let chan_state: State = State::from_i32(value.state)?; @@ -118,7 +111,7 @@ impl TryFrom for ChannelEnd { // Assemble the 'remote' attribute of the Channel, which represents the Counterparty. let remote = value .counterparty - .ok_or(Kind::MissingCounterparty)? + .ok_or_else(Error::missing_counterparty)? .try_into()?; // Parse each item in connection_hops into a ConnectionId. @@ -127,7 +120,7 @@ impl TryFrom for ChannelEnd { .into_iter() .map(|conn_id| ConnectionId::from_str(conn_id.as_str())) .collect::, _>>() - .map_err(|e| Kind::IdentifierError.context(e))?; + .map_err(Error::identifier)?; let version = validate_version(value.version)?; @@ -215,14 +208,13 @@ impl ChannelEnd { pub fn validate_basic(&self) -> Result<(), Error> { if self.connection_hops.len() != 1 { - return Err( - Kind::InvalidConnectionHopsLength(1, self.connection_hops.len()) - .context("validate channel") - .into(), - ); + return Err(Error::invalid_connection_hops_length( + 1, + self.connection_hops.len(), + )); } if self.version().trim() == "" { - return Err(Kind::InvalidVersion.context("empty version string").into()); + return Err(Error::empty_version()); } self.counterparty().validate_basic() } @@ -290,19 +282,16 @@ impl Counterparty { impl Protobuf for Counterparty {} impl TryFrom for Counterparty { - type Error = anomaly::Error; + type Error = Error; fn try_from(value: RawCounterparty) -> Result { let channel_id = Some(value.channel_id) .filter(|x| !x.is_empty()) .map(|v| FromStr::from_str(v.as_str())) .transpose() - .map_err(|e| Kind::IdentifierError.context(e))?; + .map_err(Error::identifier)?; Ok(Counterparty::new( - value - .port_id - .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, + value.port_id.parse().map_err(Error::identifier)?, channel_id, )) } @@ -354,7 +343,7 @@ impl Order { 0 => Ok(Self::None), 1 => Ok(Self::Unordered), 2 => Ok(Self::Ordered), - _ => fail!(error::Kind::UnknownOrderType, nr), + _ => Err(Error::unknown_order_type(nr.to_string())), } } } @@ -367,7 +356,7 @@ impl FromStr for Order { "uninitialized" => Ok(Self::None), "unordered" => Ok(Self::Unordered), "ordered" => Ok(Self::Ordered), - _ => fail!(error::Kind::UnknownOrderType, s), + _ => Err(Error::unknown_order_type(s.to_string())), } } } @@ -393,7 +382,7 @@ impl State { } } - /// Parses the State out from a i32. + // Parses the State out from a i32. pub fn from_i32(s: i32) -> Result { match s { 0 => Ok(Self::Uninitialized), @@ -401,7 +390,7 @@ impl State { 2 => Ok(Self::TryOpen), 3 => Ok(Self::Open), 4 => Ok(Self::Closed), - _ => fail!(error::Kind::UnknownState, s), + _ => Err(Error::unknown_state(s)), } } diff --git a/modules/src/ics04_channel/error.rs b/modules/src/ics04_channel/error.rs index c7c48cca20..cfe6ab3f2b 100644 --- a/modules/src/ics04_channel/error.rs +++ b/modules/src/ics04_channel/error.rs @@ -1,182 +1,321 @@ -use anomaly::{BoxError, Context}; -use thiserror::Error; - -pub type Error = anomaly::Error; - use super::packet::Sequence; +use crate::ics02_client::error as client_error; use crate::ics04_channel::channel::State; +use crate::ics24_host::error::ValidationError; use crate::ics24_host::identifier::{ChannelId, ClientId, ConnectionId, PortId}; +use crate::proofs::ProofError; use crate::timestamp::Timestamp; -use crate::{ics02_client, Height}; - -#[derive(Clone, Debug, Error, Eq, PartialEq)] -pub enum Kind { - #[error("channel state unknown")] - UnknownState, - - #[error("identifier error")] - IdentifierError, - - #[error("channel order type unknown")] - UnknownOrderType, - - #[error("invalid connection hops length: expected {0}; actual {1}")] - InvalidConnectionHopsLength(usize, usize), - - #[error("packet destination port/channel doesn't match the counterparty's port/channel")] - InvalidPacketCounterparty(PortId, ChannelId), - - #[error("invalid version")] - InvalidVersion, - - #[error("invalid signer address")] - InvalidSigner, - - #[error("invalid proof")] - InvalidProof, - - #[error("invalid proof: missing height")] - MissingHeight, - - #[error("Missing sequence number for receiving packets")] - MissingNextRecvSeq, - - #[error("packet sequence cannot be 0")] - ZeroPacketSequence, - - #[error("packet data bytes cannot be empty")] - ZeroPacketData, - - #[error("packet timeout height and packet timeout timestamp cannot both be 0")] - ZeroPacketTimeout, - - #[error("invalid timeout height for the packet")] - InvalidTimeoutHeight, - - #[error("invalid packet")] - InvalidPacket, - - #[error("there is no packet in this message")] - MissingPacket, - - #[error("Packet with the sequence number {0} has been already received")] - PacketAlreadyReceived(Sequence), - - #[error("missing counterparty")] - MissingCounterparty, - #[error("no commong version")] - NoCommonVersion, - - #[error("missing channel end")] - MissingChannel, - - #[error("given connection hop {0} does not exist")] - MissingConnection(ConnectionId), - - #[error("the port {0} has no capability associated")] - NoPortCapability(PortId), - - #[error("the module associated with the port does not have the capability it needs")] - InvalidPortCapability, - - #[error("single version must be negociated on connection before opening channel")] - InvalidVersionLengthConnection, - - #[error("the channel ordering is not supported by connection ")] - ChannelFeatureNotSuportedByConnection, - - #[error("the channel end ({0}, {1}) does not exist")] - ChannelNotFound(PortId, ChannelId), - - #[error( - "a different channel exists (was initialized) already for the same channel identifier {0}" - )] - ChannelMismatch(ChannelId), +use crate::Height; +use flex_error::{define_error, TraceError}; +use tendermint_proto::Error as TendermintError; + +define_error! { + Error { + UnknownState + { state: i32 } + | e | { format_args!("channel state unknown: {}", e.state) }, + + Identifier + [ ValidationError ] + | _ | { "identifier error" }, + + UnknownOrderType + { type_id: String } + | e | { format_args!("channel order type unknown: {}", e.type_id) }, + + InvalidConnectionHopsLength + { expected: usize, actual: usize } + | e | { + format_args!( + "invalid connection hops length: expected {0}; actual {1}", + e.expected, e.actual) + }, + + InvalidPacketCounterparty + { port_id: PortId, channel_id: ChannelId } + | e | { + format_args!( + "packet destination port {} and channel {} doesn't match the counterparty's port/channel", + e.port_id, e.channel_id) + }, + + InvalidVersion + [ TraceError ] + | _ | { "invalid version" }, + + EmptyVersion + | _ | { "empty version string" }, + + InvalidSigner + | _ | { "invalid signer address" }, + + InvalidProof + [ ProofError ] + | _ | { "invalid proof" }, + + MissingHeight + | _ | { "invalid proof: missing height" }, + + MissingNextRecvSeq + | _ | { "Missing sequence number for receiving packets" }, + + ZeroPacketSequence + | _ | { "packet sequence cannot be 0" }, + + ZeroPacketData + | _ | { "packet data bytes cannot be empty" }, + + ZeroPacketTimeout + | _ | { "packet timeout height and packet timeout timestamp cannot both be 0" }, + + InvalidTimeoutHeight + | _ | { "invalid timeout height for the packet" }, + + InvalidPacket + | _ | { "invalid packet" }, + + MissingPacket + | _ | { "there is no packet in this message" }, + + PacketAlreadyReceived + { sequence: Sequence } + | e | { + format_args!( + "Packet with the sequence number {0} has been already received", + e.sequence) + }, + + MissingCounterparty + | _ | { "missing counterparty" }, + + NoCommonVersion + | _ | { "no commong version" }, + + MissingChannel + | _ | { "missing channel end" }, + + MissingConnection + { connection_id: ConnectionId } + | e | { + format_args!( + "given connection hop {0} does not exist", + e.connection_id) + }, + + NoPortCapability + { port_id: PortId } + | e | { + format_args!( + "the port {0} has no capability associated", + e.port_id) + }, + + InvalidPortCapability + | _ | { "the module associated with the port does not have the capability it needs" }, + + InvalidVersionLengthConnection + | _ | { "single version must be negociated on connection before opening channel" }, + + ChannelFeatureNotSuportedByConnection + | _ | { "the channel ordering is not supported by connection" }, + + ChannelNotFound + { port_id: PortId, channel_id: ChannelId } + | e | { + format_args!( + "the channel end ({0}, {1}) does not exist", + e.port_id, e.channel_id) + }, + + ChannelMismatch + { channel_id: ChannelId } + | e | { + format_args!( + "a different channel exists (was initialized) already for the same channel identifier {0}", + e.channel_id) + }, + + ConnectionNotOpen + { connection_id: ConnectionId } + | e | { + format_args!( + "the associated connection {0} is not OPEN", + e.connection_id) + }, + + UndefinedConnectionCounterparty + { connection_id: ConnectionId } + | e | { + format_args!( + "Undefined counterparty connection for {0}", + e.connection_id) + }, + + PacketVerificationFailed + { sequence: Sequence } + [ client_error::Error ] + | e | { + format_args!( + "Verification fails for the packet with the sequence number {0}", + e.sequence) + }, + + VerifyChannelFailed + [ client_error::Error ] + | _ | { + "Error verifying channel state" + }, + + InvalidAcknowledgement + | _ | { "Acknowledgment cannot be empty" }, + + AcknowledgementExists + { sequence: Sequence } + | e | { + format_args!( + "Packet acknowledgement exists for the packet with the sequence {0}", + e.sequence) + }, + + MissingClientState + { client_id: ClientId } + | e | { + format_args!( + "No client state associated with client id {0}", + e.client_id) + }, + + MissingNextSendSeq + | _ | { "Missing sequence number for send packets" }, + + InvalidStringAsSequence + { value: String } + [ TraceError ] + | e | { + format_args!( + "String {0} cannot be converted to packet sequence", + e.value) + }, + + InvalidPacketSequence + { + given_sequence: Sequence, + next_sequence: Sequence + } + | e | { + format_args!( + "Invalid packet sequence {0} ≠ next send sequence {1}", + e.given_sequence, e.next_sequence) + }, + + LowPacketHeight + { + chain_height: Height, + timeout_height: Height + } + | e | { + format_args!( + "Receiving chain block height {0} >= packet timeout height {1}", + e.chain_height, e.timeout_height) + }, + + PacketTimeoutHeightNotReached + { + timeout_height: Height, + chain_height: Height, + } + | e | { + format_args!( + "Packet timeout height {0} > chain height {1}", + e.timeout_height, e.chain_height) + }, + + PacketTimeoutTimestampNotReached + { + timeout_timestamp: Timestamp, + chain_timestamp: Timestamp, + } + | e | { + format_args!( + "Packet timeout timestamp {0} > chain timestamp {1}", + e.timeout_timestamp, e.chain_timestamp) + }, + + LowPacketTimestamp + | _ | { "Receiving chain block timestamp >= packet timeout timestamp" }, + + InvalidPacketTimestamp + [ TraceError ] + | _ | { "Invalid packet timeout timestamp value" }, + + ErrorInvalidConsensusState + | _ | { "Invalid timestamp in consensus state; timestamp must be a positive value" }, + + FrozenClient + { client_id: ClientId } + | e | { + format_args!( + "Client with id {0} is frozen", + e.client_id) + }, + MissingClientConsensusState + { client_id: ClientId, height: Height } + | e | { + format_args!( + "Missing client consensus state for client id {0} at height {1}", + e.client_id, e.height) + }, + + InvalidCounterpartyChannelId + [ ValidationError ] + | _ | { "Invalid channel id in counterparty" }, + + ClientNotFound + | _ | { "Client not found in chan open verification" }, + + InvalidChannelState + { channel_id: ChannelId, state: State } + | e | { + format_args!( + "Channel {0} should not be state {1}", + e.channel_id, e.state) + }, + + ChannelClosed + { channel_id: ChannelId } + | e | { + format_args!( + "Channel {0} is Closed", + e.channel_id) + }, + + ChanOpenAckProofVerification + | _ | { "Handshake proof verification fails at ChannelOpenAck" }, + + PacketCommitmentNotFound + { sequence: Sequence } + | e | { + format_args!( + "Commitment for the packet {0} not found", + e.sequence) + }, + + IncorrectPacketCommitment + { sequence: Sequence } + | e | { + format_args!( + "The stored commitment of the packet {0} is incorrect", + e.sequence) + }, + + MissingNextAckSeq + | _ | { "Missing sequence number for ack packets" }, - #[error("the associated connection {0} is not OPEN ")] - ConnectionNotOpen(ConnectionId), - - #[error("Undefined counterparty connection for {0}")] - UndefinedConnectionCounterparty(ConnectionId), - - #[error("Channel chain verification fails on ChannelOpenTry for ChannelOpenInit")] - FailedChanneOpenTryVerification, - - #[error("Verification fails for the packet with the sequence number {0}")] - PacketVerificationFailed(Sequence), - - #[error("Acknowledgment cannot be empty")] - InvalidAcknowledgement, - - #[error("Packet acknowledgement exists for the packet with the sequence {0}")] - AcknowledgementExists(Sequence), - - #[error("No client state associated with client id {0}")] - MissingClientState(ClientId), - - #[error("Missing sequence number for send packets")] - MissingNextSendSeq, - - #[error("String {0} cannot be converted to packet sequence")] - InvalidStringAsSequence(String), - - #[error("Invalid packet sequence {0} ≠ next send sequence {1}")] - InvalidPacketSequence(Sequence, Sequence), - - #[error("Receiving chain block height {0} >= packet timeout height {1}")] - LowPacketHeight(Height, Height), - - #[error("Packet timeout height {0} > chain height {1}")] - PacketTimeoutHeightNotReached(Height, Height), - - #[error("Packet timeout timestamp {0} > chain timestamp {1}")] - PacketTimeoutTimestampNotReached(Timestamp, Timestamp), - - #[error("Receiving chain block timestamp >= packet timeout timestamp")] - LowPacketTimestamp, - - #[error("Invalid packet timeout timestamp value")] - InvalidPacketTimestamp, - - #[error("Invalid timestamp in consensus state; timestamp must be a positive value")] - ErrorInvalidConsensusState(ics02_client::error::Kind), - - #[error("Client with id {0} is frozen")] - FrozenClient(ClientId), - - #[error("Missing client consensus state for client id {0} at height {1}")] - MissingClientConsensusState(ClientId, Height), - - #[error("Invalid channel id in counterparty")] - InvalidCounterpartyChannelId, - - #[error("Client not found in chan open verification")] - ClientNotFound, - - #[error("Channel {0} should not be state {1}")] - InvalidChannelState(ChannelId, State), - - #[error("Channel {0} is Closed")] - ChannelClosed(ChannelId), - - #[error("Handshake proof verification fails at ChannelOpenAck")] - ChanOpenAckProofVerification, - - #[error("Commitment for the packet {0} not found")] - PacketCommitmentNotFound(Sequence), - - #[error("Handshake proof verification fails at ChannelOpenConfirm")] - ChanOpenConfirmProofVerification, - - #[error("The stored commitment of the packet {0} is incorrect")] - IncorrectPacketCommitment(Sequence), - - #[error("Missing sequence number for ack packets")] - MissingNextAckSeq, + } } -impl Kind { - pub fn context(self, source: impl Into) -> Context { - Context::new(self, Some(source.into())) +impl Error { + pub fn chan_open_confirm_proof_verification(e: Error) -> Error { + e.add_trace(&"Handshake proof verification fails at ChannelOpenConfirm") } } diff --git a/modules/src/ics04_channel/events.rs b/modules/src/ics04_channel/events.rs index 5debf0d2c3..40e0c05e35 100644 --- a/modules/src/ics04_channel/events.rs +++ b/modules/src/ics04_channel/events.rs @@ -1,12 +1,10 @@ //! Types for the IBC events emitted from Tendermint Websocket by the channels module. -use crate::events::{IbcEvent, RawObject}; +use crate::events::{extract_attribute, maybe_extract_attribute, Error, IbcEvent, RawObject}; use crate::ics02_client::height::Height; use crate::ics04_channel::packet::Packet; use crate::ics24_host::identifier::{ChannelId, ConnectionId, PortId}; -use crate::{attribute, some_attribute}; -use anomaly::BoxError; use serde_derive::{Deserialize, Serialize}; -use std::convert::{TryFrom, TryInto}; +use std::convert::TryFrom; /// Channel event types const OPEN_INIT_EVENT_TYPE: &str = "channel_open_init"; @@ -162,6 +160,31 @@ pub struct Attributes { pub counterparty_channel_id: Option, } +fn extract_attributes(object: &RawObject, namespace: &str) -> Result { + Ok(Attributes { + height: object.height, + port_id: extract_attribute(&object, &format!("{}.port_id", namespace))? + .parse() + .map_err(Error::parse)?, + channel_id: maybe_extract_attribute(&object, &format!("{}.channel_id", namespace)) + .and_then(|v| v.parse().ok()), + connection_id: extract_attribute(&object, &format!("{}.connection_id", namespace))? + .parse() + .map_err(Error::parse)?, + counterparty_port_id: extract_attribute( + &object, + &format!("{}.counterparty_port_id", namespace), + )? + .parse() + .map_err(Error::parse)?, + counterparty_channel_id: maybe_extract_attribute( + &object, + &format!("{}.counterparty_channel_id", namespace), + ) + .and_then(|v| v.parse().ok()), + }) +} + impl Attributes { pub fn port_id(&self) -> &PortId { &self.port_id @@ -212,19 +235,9 @@ impl From for OpenInit { } impl TryFrom for OpenInit { - type Error = BoxError; + type Error = Error; fn try_from(obj: RawObject) -> Result { - Ok(OpenInit(Attributes { - height: obj.height, - port_id: attribute!(obj, "channel_open_init.port_id"), - channel_id: some_attribute!(obj, "channel_open_init.channel_id"), - connection_id: attribute!(obj, "channel_open_init.connection_id"), - counterparty_port_id: attribute!(obj, "channel_open_init.counterparty_port_id"), - counterparty_channel_id: some_attribute!( - obj, - "channel_open_init.counterparty_channel_id" - ), - })) + Ok(OpenInit(extract_attributes(&obj, "channel_open_init")?)) } } @@ -262,19 +275,9 @@ impl From for OpenTry { } impl TryFrom for OpenTry { - type Error = BoxError; + type Error = Error; fn try_from(obj: RawObject) -> Result { - Ok(OpenTry(Attributes { - height: obj.height, - port_id: attribute!(obj, "channel_open_try.port_id"), - channel_id: some_attribute!(obj, "channel_open_try.channel_id"), - connection_id: attribute!(obj, "channel_open_try.connection_id"), - counterparty_port_id: attribute!(obj, "channel_open_try.counterparty_port_id"), - counterparty_channel_id: some_attribute!( - obj, - "channel_open_try.counterparty_channel_id" - ), - })) + Ok(OpenTry(extract_attributes(&obj, "channel_open_try")?)) } } @@ -316,19 +319,9 @@ impl From for OpenAck { } impl TryFrom for OpenAck { - type Error = BoxError; + type Error = Error; fn try_from(obj: RawObject) -> Result { - Ok(OpenAck(Attributes { - height: obj.height, - port_id: attribute!(obj, "channel_open_ack.port_id"), - channel_id: some_attribute!(obj, "channel_open_ack.channel_id"), - connection_id: attribute!(obj, "channel_open_ack.connection_id"), - counterparty_port_id: attribute!(obj, "channel_open_ack.counterparty_port_id"), - counterparty_channel_id: some_attribute!( - obj, - "channel_open_ack.counterparty_channel_id" - ), - })) + Ok(OpenAck(extract_attributes(&obj, "channel_open_ack")?)) } } @@ -366,19 +359,12 @@ impl From for OpenConfirm { } impl TryFrom for OpenConfirm { - type Error = BoxError; + type Error = Error; fn try_from(obj: RawObject) -> Result { - Ok(OpenConfirm(Attributes { - height: obj.height, - port_id: attribute!(obj, "channel_open_confirm.port_id"), - channel_id: some_attribute!(obj, "channel_open_confirm.channel_id"), - connection_id: attribute!(obj, "channel_open_confirm.connection_id"), - counterparty_port_id: attribute!(obj, "channel_open_confirm.counterparty_port_id"), - counterparty_channel_id: some_attribute!( - obj, - "channel_open_confirm.counterparty_channel_id" - ), - })) + Ok(OpenConfirm(extract_attributes( + &obj, + "channel_open_confirm", + )?)) } } @@ -428,19 +414,9 @@ impl From for CloseInit { } impl TryFrom for CloseInit { - type Error = BoxError; + type Error = Error; fn try_from(obj: RawObject) -> Result { - Ok(CloseInit(Attributes { - height: obj.height, - port_id: attribute!(obj, "channel_close_init.port_id"), - channel_id: some_attribute!(obj, "channel_close_init.channel_id"), - connection_id: attribute!(obj, "channel_close_init.connection_id"), - counterparty_port_id: attribute!(obj, "channel_close_init.counterparty_port_id"), - counterparty_channel_id: some_attribute!( - obj, - "channel_close_init.counterparty_channel_id" - ), - })) + Ok(CloseInit(extract_attributes(&obj, "channel_close_init")?)) } } @@ -484,19 +460,12 @@ impl From for CloseConfirm { } impl TryFrom for CloseConfirm { - type Error = BoxError; + type Error = Error; fn try_from(obj: RawObject) -> Result { - Ok(CloseConfirm(Attributes { - height: obj.height, - port_id: attribute!(obj, "channel_close_confirm.port_id"), - channel_id: some_attribute!(obj, "channel_close_confirm.channel_id"), - connection_id: attribute!(obj, "channel_close_confirm.connection_id"), - counterparty_port_id: attribute!(obj, "channel_close_confirm.counterparty_port_id"), - counterparty_channel_id: some_attribute!( - obj, - "channel_close_confirm.counterparty_channel_id" - ), - })) + Ok(CloseConfirm(extract_attributes( + &obj, + "channel_close_confirm", + )?)) } } @@ -506,28 +475,41 @@ impl From for IbcEvent { } } -#[macro_export] -macro_rules! p_attribute { - ($a:ident, $b:literal) => {{ - let nb = format!("{}.{}", $a.action, $b); - $a.events.get(&nb).ok_or(nb)?[$a.idx].parse()? - }}; -} - impl TryFrom for Packet { - type Error = BoxError; + type Error = Error; fn try_from(obj: RawObject) -> Result { - let height_str: String = p_attribute!(obj, "packet_timeout_height"); - let sequence: u64 = p_attribute!(obj, "packet_sequence"); Ok(Packet { - sequence: sequence.into(), - source_port: p_attribute!(obj, "packet_src_port"), - source_channel: p_attribute!(obj, "packet_src_channel"), - destination_port: p_attribute!(obj, "packet_dst_port"), - destination_channel: p_attribute!(obj, "packet_dst_channel"), + sequence: extract_attribute(&obj, &format!("{}.packet_sequence", obj.action))? + .parse() + .map_err(Error::channel)?, + source_port: extract_attribute(&obj, &format!("{}.packet_src_port", obj.action))? + .parse() + .map_err(Error::parse)?, + source_channel: extract_attribute(&obj, &format!("{}.packet_src_channel", obj.action))? + .parse() + .map_err(Error::parse)?, + destination_port: extract_attribute(&obj, &format!("{}.packet_dst_port", obj.action))? + .parse() + .map_err(Error::parse)?, + destination_channel: extract_attribute( + &obj, + &format!("{}.packet_dst_channel", obj.action), + )? + .parse() + .map_err(Error::parse)?, data: vec![], - timeout_height: height_str.as_str().try_into()?, - timeout_timestamp: p_attribute!(obj, "packet_timeout_timestamp"), + timeout_height: extract_attribute( + &obj, + &format!("{}.packet_timeout_height", obj.action), + )? + .parse() + .map_err(Error::height)?, + timeout_timestamp: extract_attribute( + &obj, + &format!("{}.packet_timeout_timestamp", obj.action), + )? + .parse() + .map_err(Error::timestamp)?, }) } } @@ -560,12 +542,14 @@ impl SendPacket { } impl TryFrom for SendPacket { - type Error = BoxError; + type Error = Error; fn try_from(obj: RawObject) -> Result { let height = obj.height; - let data_str: String = p_attribute!(obj, "packet_data"); + let data_str: String = extract_attribute(&obj, &format!("{}.packet_data", obj.action))?; + let mut packet = Packet::try_from(obj)?; packet.data = Vec::from(data_str.as_str().as_bytes()); + Ok(SendPacket { height, packet }) } } @@ -610,12 +594,14 @@ impl ReceivePacket { } impl TryFrom for ReceivePacket { - type Error = BoxError; + type Error = Error; fn try_from(obj: RawObject) -> Result { let height = obj.height; - let data_str: String = p_attribute!(obj, "packet_data"); + let data_str: String = extract_attribute(&obj, &format!("{}.packet_data", obj.action))?; + let mut packet = Packet::try_from(obj)?; packet.data = Vec::from(data_str.as_str().as_bytes()); + Ok(ReceivePacket { height, packet }) } } @@ -662,13 +648,17 @@ impl WriteAcknowledgement { } impl TryFrom for WriteAcknowledgement { - type Error = BoxError; + type Error = Error; fn try_from(obj: RawObject) -> Result { let height = obj.height; - let data_str: String = p_attribute!(obj, "packet_data"); - let ack_str: String = p_attribute!(obj, "packet_ack"); + + let data_str: String = extract_attribute(&obj, &format!("{}.packet_data", obj.action))?; + + let ack_str: String = extract_attribute(&obj, &format!("{}.packet_ack", obj.action))?; + let mut packet = Packet::try_from(obj)?; packet.data = Vec::from(data_str.as_str().as_bytes()); + Ok(WriteAcknowledgement { height, packet, @@ -715,7 +705,7 @@ impl AcknowledgePacket { } impl TryFrom for AcknowledgePacket { - type Error = BoxError; + type Error = Error; fn try_from(obj: RawObject) -> Result { let height = obj.height; let packet = Packet::try_from(obj)?; @@ -763,7 +753,7 @@ impl TimeoutPacket { } impl TryFrom for TimeoutPacket { - type Error = BoxError; + type Error = Error; fn try_from(obj: RawObject) -> Result { Ok(TimeoutPacket { height: obj.height, @@ -812,7 +802,7 @@ impl TimeoutOnClosePacket { } impl TryFrom for TimeoutOnClosePacket { - type Error = BoxError; + type Error = Error; fn try_from(obj: RawObject) -> Result { Ok(TimeoutOnClosePacket { height: obj.height, diff --git a/modules/src/ics04_channel/handler/acknowledgement.rs b/modules/src/ics04_channel/handler/acknowledgement.rs index 024b5c1334..5c3d7f9f26 100644 --- a/modules/src/ics04_channel/handler/acknowledgement.rs +++ b/modules/src/ics04_channel/handler/acknowledgement.rs @@ -8,7 +8,7 @@ use crate::ics04_channel::events::AcknowledgePacket; use crate::ics04_channel::handler::verify::verify_packet_acknowledgement_proofs; use crate::ics04_channel::msgs::acknowledgement::MsgAcknowledgement; use crate::ics04_channel::packet::{PacketResult, Sequence}; -use crate::ics04_channel::{context::ChannelReader, error::Error, error::Kind}; +use crate::ics04_channel::{context::ChannelReader, error::Error}; use crate::ics24_host::identifier::{ChannelId, PortId}; #[derive(Clone, Debug)] @@ -30,12 +30,11 @@ pub fn process( let source_channel_end = ctx .channel_end(&(packet.source_port.clone(), packet.source_channel.clone())) .ok_or_else(|| { - Kind::ChannelNotFound(packet.source_port.clone(), packet.source_channel.clone()) - .context(packet.source_channel.to_string()) + Error::channel_not_found(packet.source_port.clone(), packet.source_channel.clone()) })?; if !source_channel_end.state_matches(&State::Open) { - return Err(Kind::ChannelClosed(packet.source_channel.clone()).into()); + return Err(Error::channel_closed(packet.source_channel.clone())); } let _channel_cap = ctx.authenticated_capability(&packet.source_port)?; @@ -46,21 +45,22 @@ pub fn process( ); if !source_channel_end.counterparty_matches(&counterparty) { - return Err(Kind::InvalidPacketCounterparty( + return Err(Error::invalid_packet_counterparty( packet.destination_port.clone(), packet.destination_channel.clone(), - ) - .into()); + )); } let connection_end = ctx .connection_end(&source_channel_end.connection_hops()[0]) - .ok_or_else(|| Kind::MissingConnection(source_channel_end.connection_hops()[0].clone()))?; + .ok_or_else(|| { + Error::missing_connection(source_channel_end.connection_hops()[0].clone()) + })?; if !connection_end.state_matches(&ConnectionState::Open) { - return Err( - Kind::ConnectionNotOpen(source_channel_end.connection_hops()[0].clone()).into(), - ); + return Err(Error::connection_not_open( + source_channel_end.connection_hops()[0].clone(), + )); } let client_id = connection_end.client_id().clone(); @@ -72,7 +72,7 @@ pub fn process( packet.source_channel.clone(), packet.sequence, )) - .ok_or(Kind::PacketCommitmentNotFound(packet.sequence))?; + .ok_or_else(|| Error::packet_commitment_not_found(packet.sequence))?; let input = format!( "{:?},{:?},{:?}", @@ -80,7 +80,7 @@ pub fn process( ); if packet_commitment != ctx.hash(input) { - return Err(Kind::IncorrectPacketCommitment(packet.sequence).into()); + return Err(Error::incorrect_packet_commitment(packet.sequence)); } // Verify the acknowledgement proof @@ -95,10 +95,13 @@ pub fn process( let result = if source_channel_end.order_matches(&Order::Ordered) { let next_seq_ack = ctx .get_next_sequence_ack(&(packet.source_port.clone(), packet.source_channel.clone())) - .ok_or(Kind::MissingNextAckSeq)?; + .ok_or_else(Error::missing_next_ack_seq)?; if packet.sequence != next_seq_ack { - return Err(Kind::InvalidPacketSequence(packet.sequence, next_seq_ack).into()); + return Err(Error::invalid_packet_sequence( + packet.sequence, + next_seq_ack, + )); } PacketResult::Ack(AckPacketResult { diff --git a/modules/src/ics04_channel/handler/chan_close_confirm.rs b/modules/src/ics04_channel/handler/chan_close_confirm.rs index ba4800fd5a..c1ce1c3a69 100644 --- a/modules/src/ics04_channel/handler/chan_close_confirm.rs +++ b/modules/src/ics04_channel/handler/chan_close_confirm.rs @@ -4,7 +4,7 @@ use crate::handler::{HandlerOutput, HandlerResult}; use crate::ics03_connection::connection::State as ConnectionState; use crate::ics04_channel::channel::{ChannelEnd, Counterparty, State}; use crate::ics04_channel::context::ChannelReader; -use crate::ics04_channel::error::{Error, Kind}; +use crate::ics04_channel::error::Error; use crate::ics04_channel::events::Attributes; use crate::ics04_channel::handler::verify::verify_channel_proofs; use crate::ics04_channel::handler::{ChannelIdState, ChannelResult}; @@ -19,11 +19,11 @@ pub(crate) fn process( // Retrieve the old channel end and validate it against the message. let mut channel_end = ctx .channel_end(&(msg.port_id().clone(), msg.channel_id().clone())) - .ok_or_else(|| Kind::ChannelNotFound(msg.port_id.clone(), msg.channel_id().clone()))?; + .ok_or_else(|| Error::channel_not_found(msg.port_id.clone(), msg.channel_id().clone()))?; // Validate that the channel end is in a state where it can be closed. if channel_end.state_matches(&State::Closed) { - return Err(Kind::ChannelClosed(msg.channel_id().clone()).into()); + return Err(Error::channel_closed(msg.channel_id().clone())); } // Channel capabilities @@ -31,15 +31,20 @@ pub(crate) fn process( // An OPEN IBC connection running on the local (host) chain should exist. if channel_end.connection_hops().len() != 1 { - return Err( - Kind::InvalidConnectionHopsLength(1, channel_end.connection_hops().len()).into(), - ); + return Err(Error::invalid_connection_hops_length( + 1, + channel_end.connection_hops().len(), + )); } + let conn = ctx .connection_end(&channel_end.connection_hops()[0]) - .ok_or_else(|| Kind::MissingConnection(channel_end.connection_hops()[0].clone()))?; + .ok_or_else(|| Error::missing_connection(channel_end.connection_hops()[0].clone()))?; + if !conn.state_matches(&ConnectionState::Open) { - return Err(Kind::ConnectionNotOpen(channel_end.connection_hops()[0].clone()).into()); + return Err(Error::connection_not_open( + channel_end.connection_hops()[0].clone(), + )); } // Proof verification in two steps: @@ -50,7 +55,7 @@ pub(crate) fn process( let counterparty = conn.counterparty(); let ccid = counterparty.connection_id().ok_or_else(|| { - Kind::UndefinedConnectionCounterparty(channel_end.connection_hops()[0].clone()) + Error::undefined_connection_counterparty(channel_end.connection_hops()[0].clone()) })?; let expected_connection_hops = vec![ccid.clone()]; @@ -69,8 +74,7 @@ pub(crate) fn process( &conn, &expected_channel_end, msg.proofs(), - ) - .map_err(|e| Kind::FailedChanneOpenTryVerification.context(e))?; + )?; output.log("success: channel close confirm "); diff --git a/modules/src/ics04_channel/handler/chan_close_init.rs b/modules/src/ics04_channel/handler/chan_close_init.rs index ee1c7a2cdd..877ad88094 100644 --- a/modules/src/ics04_channel/handler/chan_close_init.rs +++ b/modules/src/ics04_channel/handler/chan_close_init.rs @@ -4,7 +4,7 @@ use crate::handler::{HandlerOutput, HandlerResult}; use crate::ics03_connection::connection::State as ConnectionState; use crate::ics04_channel::channel::State; use crate::ics04_channel::context::ChannelReader; -use crate::ics04_channel::error::{Error, Kind}; +use crate::ics04_channel::error::Error; use crate::ics04_channel::events::Attributes; use crate::ics04_channel::handler::{ChannelIdState, ChannelResult}; use crate::ics04_channel::msgs::chan_close_init::MsgChannelCloseInit; @@ -18,14 +18,14 @@ pub(crate) fn process( // Unwrap the old channel end and validate it against the message. let mut channel_end = ctx .channel_end(&(msg.port_id().clone(), msg.channel_id().clone())) - .ok_or_else(|| Kind::ChannelNotFound(msg.port_id.clone(), msg.channel_id().clone()))?; + .ok_or_else(|| Error::channel_not_found(msg.port_id.clone(), msg.channel_id().clone()))?; // Validate that the channel end is in a state where it can be closed. if channel_end.state_matches(&State::Closed) { - return Err(Into::::into(Kind::InvalidChannelState( + return Err(Error::invalid_channel_state( msg.channel_id().clone(), channel_end.state, - ))); + )); } // Channel capabilities @@ -33,17 +33,20 @@ pub(crate) fn process( // An OPEN IBC connection running on the local (host) chain should exist. if channel_end.connection_hops().len() != 1 { - return Err( - Kind::InvalidConnectionHopsLength(1, channel_end.connection_hops().len()).into(), - ); + return Err(Error::invalid_connection_hops_length( + 1, + channel_end.connection_hops().len(), + )); } let conn = ctx .connection_end(&channel_end.connection_hops()[0]) - .ok_or_else(|| Kind::MissingConnection(channel_end.connection_hops()[0].clone()))?; + .ok_or_else(|| Error::missing_connection(channel_end.connection_hops()[0].clone()))?; if !conn.state_matches(&ConnectionState::Open) { - return Err(Kind::ConnectionNotOpen(channel_end.connection_hops()[0].clone()).into()); + return Err(Error::connection_not_open( + channel_end.connection_hops()[0].clone(), + )); } output.log("success: channel close init "); diff --git a/modules/src/ics04_channel/handler/chan_open_ack.rs b/modules/src/ics04_channel/handler/chan_open_ack.rs index 58f08958cf..1c5dd27ebf 100644 --- a/modules/src/ics04_channel/handler/chan_open_ack.rs +++ b/modules/src/ics04_channel/handler/chan_open_ack.rs @@ -4,7 +4,7 @@ use crate::handler::{HandlerOutput, HandlerResult}; use crate::ics03_connection::connection::State as ConnectionState; use crate::ics04_channel::channel::{ChannelEnd, Counterparty, State}; use crate::ics04_channel::context::ChannelReader; -use crate::ics04_channel::error::{Error, Kind}; +use crate::ics04_channel::error::Error; use crate::ics04_channel::events::Attributes; use crate::ics04_channel::handler::verify::verify_channel_proofs; use crate::ics04_channel::handler::{ChannelIdState, ChannelResult}; @@ -19,11 +19,14 @@ pub(crate) fn process( // Unwrap the old channel end and validate it against the message. let mut channel_end = ctx .channel_end(&(msg.port_id().clone(), msg.channel_id().clone())) - .ok_or_else(|| Kind::ChannelNotFound(msg.port_id.clone(), msg.channel_id().clone()))?; + .ok_or_else(|| Error::channel_not_found(msg.port_id.clone(), msg.channel_id().clone()))?; // Validate that the channel end is in a state where it can be ack. if !channel_end.state_matches(&State::Init) && !channel_end.state_matches(&State::TryOpen) { - return Err(Kind::InvalidChannelState(msg.channel_id().clone(), channel_end.state).into()); + return Err(Error::invalid_channel_state( + msg.channel_id().clone(), + channel_end.state, + )); } // Channel capabilities @@ -32,17 +35,20 @@ pub(crate) fn process( // An OPEN IBC connection running on the local (host) chain should exist. if channel_end.connection_hops().len() != 1 { - return Err( - Kind::InvalidConnectionHopsLength(1, channel_end.connection_hops().len()).into(), - ); + return Err(Error::invalid_connection_hops_length( + 1, + channel_end.connection_hops().len(), + )); } let conn = ctx .connection_end(&channel_end.connection_hops()[0]) - .ok_or_else(|| Kind::MissingConnection(channel_end.connection_hops()[0].clone()))?; + .ok_or_else(|| Error::missing_connection(channel_end.connection_hops()[0].clone()))?; if !conn.state_matches(&ConnectionState::Open) { - return Err(Kind::ConnectionNotOpen(channel_end.connection_hops()[0].clone()).into()); + return Err(Error::connection_not_open( + channel_end.connection_hops()[0].clone(), + )); } // Proof verification in two steps: @@ -53,7 +59,7 @@ pub(crate) fn process( let counterparty = conn.counterparty(); let ccid = counterparty.connection_id().ok_or_else(|| { - Kind::UndefinedConnectionCounterparty(channel_end.connection_hops()[0].clone()) + Error::undefined_connection_counterparty(channel_end.connection_hops()[0].clone()) })?; let expected_connection_hops = vec![ccid.clone()]; @@ -72,8 +78,7 @@ pub(crate) fn process( &conn, &expected_channel_end, msg.proofs(), - ) - .map_err(|e| Kind::ChanOpenAckProofVerification.context(e))?; + )?; output.log("success: channel open ack "); diff --git a/modules/src/ics04_channel/handler/chan_open_confirm.rs b/modules/src/ics04_channel/handler/chan_open_confirm.rs index d574fc7cd7..61b19c5f24 100644 --- a/modules/src/ics04_channel/handler/chan_open_confirm.rs +++ b/modules/src/ics04_channel/handler/chan_open_confirm.rs @@ -4,7 +4,7 @@ use crate::handler::{HandlerOutput, HandlerResult}; use crate::ics03_connection::connection::State as ConnectionState; use crate::ics04_channel::channel::{ChannelEnd, Counterparty, State}; use crate::ics04_channel::context::ChannelReader; -use crate::ics04_channel::error::{Error, Kind}; +use crate::ics04_channel::error::Error; use crate::ics04_channel::events::Attributes; use crate::ics04_channel::handler::verify::verify_channel_proofs; use crate::ics04_channel::handler::{ChannelIdState, ChannelResult}; @@ -19,11 +19,14 @@ pub(crate) fn process( // Unwrap the old channel end and validate it against the message. let mut channel_end = ctx .channel_end(&(msg.port_id().clone(), msg.channel_id().clone())) - .ok_or_else(|| Kind::ChannelNotFound(msg.port_id.clone(), msg.channel_id().clone()))?; + .ok_or_else(|| Error::channel_not_found(msg.port_id.clone(), msg.channel_id().clone()))?; // Validate that the channel end is in a state where it can be confirmed. if !channel_end.state_matches(&State::TryOpen) { - return Err(Kind::InvalidChannelState(msg.channel_id().clone(), channel_end.state).into()); + return Err(Error::invalid_channel_state( + msg.channel_id().clone(), + channel_end.state, + )); } // Channel capabilities @@ -31,17 +34,20 @@ pub(crate) fn process( // An OPEN IBC connection running on the local (host) chain should exist. if channel_end.connection_hops().len() != 1 { - return Err( - Kind::InvalidConnectionHopsLength(1, channel_end.connection_hops().len()).into(), - ); + return Err(Error::invalid_connection_hops_length( + 1, + channel_end.connection_hops().len(), + )); } let conn = ctx .connection_end(&channel_end.connection_hops()[0]) - .ok_or_else(|| Kind::MissingConnection(channel_end.connection_hops()[0].clone()))?; + .ok_or_else(|| Error::missing_connection(channel_end.connection_hops()[0].clone()))?; if !conn.state_matches(&ConnectionState::Open) { - return Err(Kind::ConnectionNotOpen(channel_end.connection_hops()[0].clone()).into()); + return Err(Error::connection_not_open( + channel_end.connection_hops()[0].clone(), + )); } // Proof verification in two steps: @@ -52,7 +58,7 @@ pub(crate) fn process( let connection_counterparty = conn.counterparty(); let ccid = connection_counterparty.connection_id().ok_or_else(|| { - Kind::UndefinedConnectionCounterparty(channel_end.connection_hops()[0].clone()) + Error::undefined_connection_counterparty(channel_end.connection_hops()[0].clone()) })?; let expected_connection_hops = vec![ccid.clone()]; @@ -72,7 +78,7 @@ pub(crate) fn process( &expected_channel_end, msg.proofs(), ) - .map_err(|e| Kind::ChanOpenConfirmProofVerification.context(e))?; + .map_err(Error::chan_open_confirm_proof_verification)?; output.log("success: channel open confirm "); diff --git a/modules/src/ics04_channel/handler/chan_open_init.rs b/modules/src/ics04_channel/handler/chan_open_init.rs index 64544f244d..c4507d6b84 100644 --- a/modules/src/ics04_channel/handler/chan_open_init.rs +++ b/modules/src/ics04_channel/handler/chan_open_init.rs @@ -4,7 +4,7 @@ use crate::events::IbcEvent; use crate::handler::{HandlerOutput, HandlerResult}; use crate::ics04_channel::channel::{ChannelEnd, State}; use crate::ics04_channel::context::ChannelReader; -use crate::ics04_channel::error::{Error, Kind}; +use crate::ics04_channel::error::Error; use crate::ics04_channel::events::Attributes; use crate::ics04_channel::handler::{ChannelIdState, ChannelResult}; use crate::ics04_channel::msgs::chan_open_init::MsgChannelOpenInit; @@ -20,31 +20,32 @@ pub(crate) fn process( let channel_cap = ctx.authenticated_capability(&msg.port_id().clone())?; if msg.channel().connection_hops().len() != 1 { - return Err( - Kind::InvalidConnectionHopsLength(1, msg.channel().connection_hops().len()).into(), - ); + return Err(Error::invalid_connection_hops_length( + 1, + msg.channel().connection_hops().len(), + )); } // An IBC connection running on the local (host) chain should exist. let connection_end = ctx.connection_end(&msg.channel().connection_hops()[0]); let conn = connection_end - .ok_or_else(|| Kind::MissingConnection(msg.channel().connection_hops()[0].clone()))?; + .ok_or_else(|| Error::missing_connection(msg.channel().connection_hops()[0].clone()))?; let get_versions = conn.versions(); let version = match get_versions.as_slice() { [version] => version, - _ => return Err(Kind::InvalidVersionLengthConnection.into()), + _ => return Err(Error::invalid_version_length_connection()), }; let channel_feature = msg.channel().ordering().to_string(); if !version.is_supported_feature(channel_feature) { - return Err(Kind::ChannelFeatureNotSuportedByConnection.into()); + return Err(Error::channel_feature_not_suported_by_connection()); } // TODO: Check that `version` is non empty but not necessary coherent if msg.channel().version().is_empty() { - return Err(Kind::InvalidVersion.into()); + return Err(Error::empty_version()); } // Channel identifier construction. diff --git a/modules/src/ics04_channel/handler/chan_open_try.rs b/modules/src/ics04_channel/handler/chan_open_try.rs index 3417233498..76579f3f96 100644 --- a/modules/src/ics04_channel/handler/chan_open_try.rs +++ b/modules/src/ics04_channel/handler/chan_open_try.rs @@ -5,7 +5,7 @@ use crate::handler::{HandlerOutput, HandlerResult}; use crate::ics03_connection::connection::State as ConnectionState; use crate::ics04_channel::channel::{ChannelEnd, Counterparty, State}; use crate::ics04_channel::context::ChannelReader; -use crate::ics04_channel::error::{Error, Kind}; +use crate::ics04_channel::error::Error; use crate::ics04_channel::events::Attributes; use crate::ics04_channel::handler::verify::verify_channel_proofs; use crate::ics04_channel::handler::{ChannelIdState, ChannelResult}; @@ -23,7 +23,7 @@ pub(crate) fn process( Some(prev_id) => { let old_channel_end = ctx .channel_end(&(msg.port_id().clone(), prev_id.clone())) - .ok_or_else(|| Kind::ChannelNotFound(msg.port_id.clone(), prev_id.clone()))?; + .ok_or_else(|| Error::channel_not_found(msg.port_id.clone(), prev_id.clone()))?; // Validate that existing channel end matches with the one we're trying to establish. if old_channel_end.state_matches(&State::Init) @@ -36,7 +36,7 @@ pub(crate) fn process( Ok((old_channel_end, prev_id.clone())) } else { // A ConnectionEnd already exists and validation failed. - Err(Into::::into(Kind::ChannelMismatch(prev_id.clone()))) + Err(Error::channel_mismatch(prev_id.clone())) } } // No previous channel id was supplied. Create a new channel end & an identifier. @@ -64,36 +64,39 @@ pub(crate) fn process( // An IBC connection running on the local (host) chain should exist. if msg.channel.connection_hops().len() != 1 { - return Err( - Kind::InvalidConnectionHopsLength(1, msg.channel.connection_hops().len()).into(), - ); + return Err(Error::invalid_connection_hops_length( + 1, + msg.channel.connection_hops().len(), + )); } let connection_end_opt = ctx.connection_end(&msg.channel().connection_hops()[0]); let conn = connection_end_opt - .ok_or_else(|| Kind::MissingConnection(msg.channel().connection_hops()[0].clone()))?; + .ok_or_else(|| Error::missing_connection(msg.channel().connection_hops()[0].clone()))?; if !conn.state_matches(&ConnectionState::Open) { - return Err(Kind::ConnectionNotOpen(msg.channel.connection_hops()[0].clone()).into()); + return Err(Error::connection_not_open( + msg.channel.connection_hops()[0].clone(), + )); } let get_versions = conn.versions(); let version = match get_versions.as_slice() { [version] => version, - _ => return Err(Kind::InvalidVersionLengthConnection.into()), + _ => return Err(Error::invalid_version_length_connection()), }; let channel_feature = msg.channel().ordering().to_string(); if !version.is_supported_feature(channel_feature) { - return Err(Kind::ChannelFeatureNotSuportedByConnection.into()); + return Err(Error::channel_feature_not_suported_by_connection()); } // Channel capabilities let channel_cap = ctx.authenticated_capability(&msg.port_id().clone())?; if msg.channel().version().is_empty() { - return Err(Kind::InvalidVersion.into()); + return Err(Error::empty_version()); } // Proof verification in two steps: @@ -103,7 +106,7 @@ pub(crate) fn process( let expected_counterparty = Counterparty::new(msg.port_id().clone(), None); let counterparty = conn.counterparty(); let ccid = counterparty.connection_id().ok_or_else(|| { - Kind::UndefinedConnectionCounterparty(msg.channel().connection_hops()[0].clone()) + Error::undefined_connection_counterparty(msg.channel().connection_hops()[0].clone()) })?; let expected_connection_hops = vec![ccid.clone()]; @@ -123,8 +126,7 @@ pub(crate) fn process( &conn, &expected_channel_end, msg.proofs(), - ) - .map_err(|e| Kind::FailedChanneOpenTryVerification.context(e))?; + )?; output.log("success: channel open try "); @@ -165,7 +167,7 @@ mod tests { use crate::ics03_connection::msgs::test_util::get_dummy_raw_counterparty; use crate::ics03_connection::version::get_compatible_versions; use crate::ics04_channel::channel::{ChannelEnd, State}; - use crate::ics04_channel::error::Kind; + use crate::ics04_channel::error; use crate::ics04_channel::handler::{channel_dispatch, ChannelResult}; use crate::ics04_channel::msgs::chan_open_try::test_util::get_dummy_raw_msg_chan_open_try; use crate::ics04_channel::msgs::chan_open_try::MsgChannelOpenTry; @@ -182,7 +184,7 @@ mod tests { ctx: MockContext, msg: ChannelMsg, want_pass: bool, - expect_error_kind: Option, + match_error: Box, } // Some general-purpose variable to parametrize the messages and the context. @@ -255,19 +257,36 @@ mod tests { ctx: context.clone(), msg: ChannelMsg::ChannelOpenTry(msg.clone()), want_pass: false, - expect_error_kind: Some(Kind::ChannelNotFound( - msg.port_id.clone(), - chan_id.clone(), - )), + match_error: { + let port_id = msg.port_id.clone(); + let channel_id = chan_id.clone(); + Box::new(move |e| match e { + error::ErrorDetail::ChannelNotFound(e) => { + assert_eq!(e.port_id, port_id); + assert_eq!(e.channel_id, channel_id); + } + _ => { + panic!("Expected ChannelNotFound, instead got {}", e) + } + }) + }, }, Test { name: "Processing fails because no connection exists in the context".to_string(), ctx: context.clone(), msg: ChannelMsg::ChannelOpenTry(msg_vanilla.clone()), want_pass: false, - expect_error_kind: Some(Kind::MissingConnection( - msg.channel().connection_hops()[0].clone(), - )), + match_error: { + let connection_id = msg.channel().connection_hops()[0].clone(); + Box::new(move |e| match e { + error::ErrorDetail::MissingConnection(e) => { + assert_eq!(e.connection_id, connection_id); + } + _ => { + panic!("Expected MissingConnection, instead got {}", e) + } + }) + }, }, Test { name: "Processing fails because the port does not have a capability associated" @@ -277,7 +296,17 @@ mod tests { .with_connection(conn_id.clone(), conn_end.clone()), msg: ChannelMsg::ChannelOpenTry(msg_vanilla.clone()), want_pass: false, - expect_error_kind: Some(Kind::NoPortCapability(msg.port_id.clone())), + match_error: { + let port_id = msg.port_id.clone(); + Box::new(move |e| match e { + error::ErrorDetail::NoPortCapability(e) => { + assert_eq!(e.port_id, port_id); + } + _ => { + panic!("Expected NoPortCapability, instead got {}", e) + } + }) + }, }, Test { name: "Processing fails because of inconsistent version with preexisting channel" @@ -289,7 +318,17 @@ mod tests { .with_channel(msg.port_id.clone(), chan_id.clone(), incorrect_chan_end_ver), msg: ChannelMsg::ChannelOpenTry(msg.clone()), want_pass: false, - expect_error_kind: Some(Kind::ChannelMismatch(chan_id.clone())), + match_error: { + let channel_id = chan_id.clone(); + Box::new(move |e| match e { + error::ErrorDetail::ChannelMismatch(e) => { + assert_eq!(e.channel_id, channel_id); + } + _ => { + panic!("Expected ChannelMismatch, instead got {}", e) + } + }) + }, }, Test { name: "Processing fails because of inconsistent connection hops".to_string(), @@ -304,7 +343,17 @@ mod tests { ), msg: ChannelMsg::ChannelOpenTry(msg.clone()), want_pass: false, - expect_error_kind: Some(Kind::ChannelMismatch(chan_id.clone())), + match_error: { + let channel_id = chan_id.clone(); + Box::new(move |e| match e { + error::ErrorDetail::ChannelMismatch(e) => { + assert_eq!(e.channel_id, channel_id); + } + _ => { + panic!("Expected ChannelMismatch, instead got {}", e) + } + }) + }, }, Test { name: "Processing fails b/c the context has no client state".to_string(), @@ -319,7 +368,12 @@ mod tests { ), msg: ChannelMsg::ChannelOpenTry(msg.clone()), want_pass: false, - expect_error_kind: Some(Kind::FailedChanneOpenTryVerification), + match_error: Box::new(|e| match e { + error::ErrorDetail::MissingClientState(_) => {} + _ => { + panic!("Expected MissingClientState, instead got {}", e) + } + }), }, Test { name: "Processing is successful".to_string(), @@ -331,7 +385,7 @@ mod tests { .with_channel(msg.port_id.clone(), chan_id, correct_chan_end), msg: ChannelMsg::ChannelOpenTry(msg.clone()), want_pass: true, - expect_error_kind: None, + match_error: Box::new(|_| {}), }, Test { name: "Processing is successful against an empty context (no preexisting channel)" @@ -339,10 +393,10 @@ mod tests { ctx: context .with_client(&client_id, Height::new(0, proof_height)) .with_connection(conn_id, conn_end) - .with_port_capability(msg.port_id.clone()), + .with_port_capability(msg.port_id), msg: ChannelMsg::ChannelOpenTry(msg_vanilla), want_pass: true, - expect_error_kind: None, + match_error: Box::new(|_| {}), }, ] .into_iter() @@ -381,17 +435,7 @@ mod tests { e, ); - // An error Kind should be expected. - assert!(test.expect_error_kind.is_some(), - "chan_open_try: test suite was not setup properly: encountered error {:?} but the expected error was not defined!", e.kind()); - - let expected_kind = test.expect_error_kind.unwrap(); - assert_eq!( - e.kind(), - &expected_kind, - "chan_open_try: mismatching error kind for test {:?}", - test.name - ); + (test.match_error)(e.0); } } } diff --git a/modules/src/ics04_channel/handler/recv_packet.rs b/modules/src/ics04_channel/handler/recv_packet.rs index 435d30570f..5a407a075b 100644 --- a/modules/src/ics04_channel/handler/recv_packet.rs +++ b/modules/src/ics04_channel/handler/recv_packet.rs @@ -4,7 +4,7 @@ use crate::ics02_client::height::Height; use crate::ics03_connection::connection::State as ConnectionState; use crate::ics04_channel::channel::{Counterparty, Order, State}; use crate::ics04_channel::context::ChannelReader; -use crate::ics04_channel::error::{Error, Kind}; +use crate::ics04_channel::error::Error; use crate::ics04_channel::events::ReceivePacket; use crate::ics04_channel::handler::verify::verify_packet_recv_proofs; use crate::ics04_channel::msgs::recv_packet::MsgRecvPacket; @@ -32,18 +32,17 @@ pub fn process(ctx: &dyn ChannelReader, msg: MsgRecvPacket) -> HandlerResult HandlerResult HandlerResult HandlerResult HandlerResult return Err(Kind::PacketAlreadyReceived(packet.sequence).into()), + Some(_receipt) => return Err(Error::packet_already_received(packet.sequence)), None => { // store a receipt that does not contain any data PacketResult::Recv(RecvPacketResult { diff --git a/modules/src/ics04_channel/handler/send_packet.rs b/modules/src/ics04_channel/handler/send_packet.rs index 50e3f028c9..3cfd5f39f5 100644 --- a/modules/src/ics04_channel/handler/send_packet.rs +++ b/modules/src/ics04_channel/handler/send_packet.rs @@ -5,7 +5,7 @@ use crate::ics04_channel::channel::Counterparty; use crate::ics04_channel::channel::State; use crate::ics04_channel::events::SendPacket; use crate::ics04_channel::packet::{PacketResult, Sequence}; -use crate::ics04_channel::{context::ChannelReader, error::Error, error::Kind, packet::Packet}; +use crate::ics04_channel::{context::ChannelReader, error::Error, packet::Packet}; use crate::ics24_host::identifier::{ChannelId, PortId}; use crate::timestamp::{Expiry, Timestamp}; use crate::Height; @@ -27,12 +27,11 @@ pub fn send_packet(ctx: &dyn ChannelReader, packet: Packet) -> HandlerResult HandlerResult HandlerResult HandlerResult HandlerResult HandlerResult proof_height { - return Err( - Kind::PacketTimeoutHeightNotReached(packet.timeout_height, proof_height).into(), - ); + return Err(Error::packet_timeout_height_not_reached( + packet.timeout_height, + proof_height, + )); } let consensus_state = ctx .client_consensus_state(&client_id, proof_height) - .ok_or_else(|| Kind::MissingClientConsensusState(client_id.clone(), proof_height))?; + .ok_or_else(|| Error::missing_client_consensus_state(client_id.clone(), proof_height))?; let proof_timestamp = consensus_state.timestamp(); let packet_timestamp = packet.timeout_timestamp; if let Expiry::Expired = packet_timestamp.check_expiry(&proof_timestamp) { - return Err( - Kind::PacketTimeoutTimestampNotReached(packet_timestamp, proof_timestamp).into(), - ); + return Err(Error::packet_timeout_timestamp_not_reached( + packet_timestamp, + proof_timestamp, + )); } //verify packet commitment @@ -87,7 +89,7 @@ pub fn process(ctx: &dyn ChannelReader, msg: MsgTimeout) -> HandlerResult HandlerResult Result<(), Error> { let client_state = ctx .client_state(&client_id) - .ok_or_else(|| Kind::MissingClientState(client_id.clone()))?; + .ok_or_else(|| Error::missing_client_state(client_id.clone()))?; // The client must not be frozen. if client_state.is_frozen() { - return Err(Kind::FrozenClient(client_id).into()); + return Err(Error::frozen_client(client_id)); } if ctx .client_consensus_state(&client_id, proofs.height()) .is_none() { - return Err(Kind::MissingClientConsensusState(client_id, proofs.height()).into()); + return Err(Error::missing_client_consensus_state( + client_id, + proofs.height(), + )); } let client_def = AnyClient::from_client_type(client_state.client_type()); @@ -84,7 +90,7 @@ pub fn verify_packet_recv_proofs( let commitment = ctx.hash(input); // Verify the proof for the packet against the chain store. - Ok(client_def + client_def .verify_packet_data( &client_state, proofs.height(), @@ -94,7 +100,9 @@ pub fn verify_packet_recv_proofs( &packet.sequence, commitment, ) - .map_err(|_| Kind::PacketVerificationFailed(packet.sequence))?) + .map_err(|e| Error::packet_verification_failed(packet.sequence, e))?; + + Ok(()) } /// Entry point for verifying all proofs bundled in an ICS4 packet ack message. @@ -107,17 +115,17 @@ pub fn verify_packet_acknowledgement_proofs( ) -> Result<(), Error> { let client_state = ctx .client_state(&client_id) - .ok_or_else(|| Kind::MissingClientState(client_id.clone()))?; + .ok_or_else(|| Error::missing_client_state(client_id.clone()))?; // The client must not be frozen. if client_state.is_frozen() { - return Err(Kind::FrozenClient(client_id).into()); + return Err(Error::frozen_client(client_id)); } let client_def = AnyClient::from_client_type(client_state.client_type()); // Verify the proof for the packet against the chain store. - Ok(client_def + client_def .verify_packet_acknowledgement( &client_state, proofs.height(), @@ -127,7 +135,9 @@ pub fn verify_packet_acknowledgement_proofs( &packet.sequence, acknowledgement, ) - .map_err(|_| Kind::PacketVerificationFailed(packet.sequence))?) + .map_err(|e| Error::packet_verification_failed(packet.sequence, e))?; + + Ok(()) } /// Entry point for verifying all timeout proofs. @@ -140,17 +150,17 @@ pub fn verify_next_sequence_recv( ) -> Result<(), Error> { let client_state = ctx .client_state(&client_id) - .ok_or_else(|| Kind::MissingClientState(client_id.clone()))?; + .ok_or_else(|| Error::missing_client_state(client_id.clone()))?; // The client must not be frozen. if client_state.is_frozen() { - return Err(Kind::FrozenClient(client_id).into()); + return Err(Error::frozen_client(client_id)); } let client_def = AnyClient::from_client_type(client_state.client_type()); // Verify the proof for the packet against the chain store. - Ok(client_def + client_def .verify_next_sequence_recv( &client_state, proofs.height(), @@ -159,7 +169,9 @@ pub fn verify_next_sequence_recv( &packet.destination_channel, &seq, ) - .map_err(|_| Kind::PacketVerificationFailed(seq))?) + .map_err(|e| Error::packet_verification_failed(seq, e))?; + + Ok(()) } pub fn verify_packet_receipt_absence( @@ -170,17 +182,17 @@ pub fn verify_packet_receipt_absence( ) -> Result<(), Error> { let client_state = ctx .client_state(&client_id) - .ok_or_else(|| Kind::MissingClientState(client_id.clone()))?; + .ok_or_else(|| Error::missing_client_state(client_id.clone()))?; // The client must not be frozen. if client_state.is_frozen() { - return Err(Kind::FrozenClient(client_id).into()); + return Err(Error::frozen_client(client_id)); } let client_def = AnyClient::from_client_type(client_state.client_type()); // Verify the proof for the packet against the chain store. - Ok(client_def + client_def .verify_packet_receipt_absence( &client_state, proofs.height(), @@ -189,5 +201,7 @@ pub fn verify_packet_receipt_absence( &packet.destination_channel, &packet.sequence, ) - .map_err(|_| Kind::PacketVerificationFailed(packet.sequence))?) + .map_err(|e| Error::packet_verification_failed(packet.sequence, e))?; + + Ok(()) } diff --git a/modules/src/ics04_channel/handler/write_acknowledgement.rs b/modules/src/ics04_channel/handler/write_acknowledgement.rs index 423c174c06..84095308d9 100644 --- a/modules/src/ics04_channel/handler/write_acknowledgement.rs +++ b/modules/src/ics04_channel/handler/write_acknowledgement.rs @@ -1,7 +1,7 @@ use crate::ics04_channel::channel::State; use crate::ics04_channel::events::WriteAcknowledgement; use crate::ics04_channel::packet::{Packet, PacketResult, Sequence}; -use crate::ics04_channel::{context::ChannelReader, error::Error, error::Kind}; +use crate::ics04_channel::{context::ChannelReader, error::Error}; use crate::ics24_host::identifier::{ChannelId, PortId}; use crate::{ events::IbcEvent, @@ -29,16 +29,17 @@ pub fn process( packet.destination_channel.clone(), )) .ok_or_else(|| { - Kind::ChannelNotFound( + Error::channel_not_found( packet.destination_port.clone(), packet.destination_channel.clone(), ) })?; if !dest_channel_end.state_matches(&State::Open) { - return Err( - Kind::InvalidChannelState(packet.source_channel, dest_channel_end.state).into(), - ); + return Err(Error::invalid_channel_state( + packet.source_channel, + dest_channel_end.state, + )); } let _channel_cap = ctx.authenticated_capability(&packet.destination_port)?; @@ -54,11 +55,11 @@ pub fn process( )) .is_some() { - return Err(Kind::AcknowledgementExists(packet.sequence).into()); + return Err(Error::acknowledgement_exists(packet.sequence)); } if ack.is_empty() { - return Err(Kind::InvalidAcknowledgement.into()); + return Err(Error::invalid_acknowledgement()); } let result = PacketResult::WriteAck(WriteAckPacketResult { diff --git a/modules/src/ics04_channel/msgs/acknowledgement.rs b/modules/src/ics04_channel/msgs/acknowledgement.rs index 668d899eb4..3ed12d793b 100644 --- a/modules/src/ics04_channel/msgs/acknowledgement.rs +++ b/modules/src/ics04_channel/msgs/acknowledgement.rs @@ -4,7 +4,7 @@ use tendermint_proto::Protobuf; use ibc_proto::ibc::core::channel::v1::MsgAcknowledgement as RawMsgAcknowledgement; -use crate::ics04_channel::error::{Error, Kind}; +use crate::ics04_channel::error::Error; use crate::ics04_channel::packet::Packet; use crate::proofs::Proofs; use crate::signer::Signer; @@ -63,7 +63,7 @@ impl Msg for MsgAcknowledgement { impl Protobuf for MsgAcknowledgement {} impl TryFrom for MsgAcknowledgement { - type Error = anomaly::Error; + type Error = Error; fn try_from(raw_msg: RawMsgAcknowledgement) -> Result { let proofs = Proofs::new( @@ -73,18 +73,16 @@ impl TryFrom for MsgAcknowledgement { None, raw_msg .proof_height - .ok_or(Kind::MissingHeight)? - .try_into() - .map_err(|e| Kind::InvalidProof.context(e))?, + .ok_or_else(Error::missing_height)? + .into(), ) - .map_err(|e| Kind::InvalidProof.context(e))?; + .map_err(Error::invalid_proof)?; Ok(MsgAcknowledgement { packet: raw_msg .packet - .ok_or(Kind::MissingPacket)? - .try_into() - .map_err(|e| Kind::InvalidPacket.context(e))?, + .ok_or_else(Error::missing_packet)? + .try_into()?, acknowledgement: raw_msg.acknowledgement, signer: raw_msg.signer.into(), proofs, diff --git a/modules/src/ics04_channel/msgs/chan_close_confirm.rs b/modules/src/ics04_channel/msgs/chan_close_confirm.rs index e4c83a61be..9aabe2c273 100644 --- a/modules/src/ics04_channel/msgs/chan_close_confirm.rs +++ b/modules/src/ics04_channel/msgs/chan_close_confirm.rs @@ -1,10 +1,10 @@ -use std::convert::{TryFrom, TryInto}; +use std::convert::TryFrom; use tendermint_proto::Protobuf; use ibc_proto::ibc::core::channel::v1::MsgChannelCloseConfirm as RawMsgChannelCloseConfirm; -use crate::ics04_channel::error::{Error, Kind}; +use crate::ics04_channel::error::Error; use crate::ics24_host::identifier::{ChannelId, PortId}; use crate::proofs::Proofs; use crate::signer::Signer; @@ -62,7 +62,7 @@ impl Msg for MsgChannelCloseConfirm { impl Protobuf for MsgChannelCloseConfirm {} impl TryFrom for MsgChannelCloseConfirm { - type Error = anomaly::Error; + type Error = Error; fn try_from(raw_msg: RawMsgChannelCloseConfirm) -> Result { let proofs = Proofs::new( @@ -72,21 +72,14 @@ impl TryFrom for MsgChannelCloseConfirm { None, raw_msg .proof_height - .ok_or(Kind::MissingHeight)? - .try_into() - .map_err(|e| Kind::InvalidProof.context(e))?, + .ok_or_else(Error::missing_height)? + .into(), ) - .map_err(|e| Kind::InvalidProof.context(e))?; + .map_err(Error::invalid_proof)?; Ok(MsgChannelCloseConfirm { - port_id: raw_msg - .port_id - .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, - channel_id: raw_msg - .channel_id - .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, + port_id: raw_msg.port_id.parse().map_err(Error::identifier)?, + channel_id: raw_msg.channel_id.parse().map_err(Error::identifier)?, proofs, signer: raw_msg.signer.into(), }) diff --git a/modules/src/ics04_channel/msgs/chan_close_init.rs b/modules/src/ics04_channel/msgs/chan_close_init.rs index 2af36cfdaa..6d718ea2be 100644 --- a/modules/src/ics04_channel/msgs/chan_close_init.rs +++ b/modules/src/ics04_channel/msgs/chan_close_init.rs @@ -4,7 +4,7 @@ use tendermint_proto::Protobuf; use ibc_proto::ibc::core::channel::v1::MsgChannelCloseInit as RawMsgChannelCloseInit; -use crate::ics04_channel::error::{Error, Kind}; +use crate::ics04_channel::error::Error; use crate::ics24_host::identifier::{ChannelId, PortId}; use crate::signer::Signer; use crate::tx_msg::Msg; @@ -55,18 +55,12 @@ impl Msg for MsgChannelCloseInit { impl Protobuf for MsgChannelCloseInit {} impl TryFrom for MsgChannelCloseInit { - type Error = anomaly::Error; + type Error = Error; fn try_from(raw_msg: RawMsgChannelCloseInit) -> Result { Ok(MsgChannelCloseInit { - port_id: raw_msg - .port_id - .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, - channel_id: raw_msg - .channel_id - .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, + port_id: raw_msg.port_id.parse().map_err(Error::identifier)?, + channel_id: raw_msg.channel_id.parse().map_err(Error::identifier)?, signer: raw_msg.signer.into(), }) } diff --git a/modules/src/ics04_channel/msgs/chan_open_ack.rs b/modules/src/ics04_channel/msgs/chan_open_ack.rs index 436fd546cc..8822abad3d 100644 --- a/modules/src/ics04_channel/msgs/chan_open_ack.rs +++ b/modules/src/ics04_channel/msgs/chan_open_ack.rs @@ -1,5 +1,5 @@ use crate::ics04_channel::channel::validate_version; -use crate::ics04_channel::error::{Error, Kind}; +use crate::ics04_channel::error::Error; use crate::ics24_host::identifier::{ChannelId, PortId}; use crate::proofs::Proofs; use crate::signer::Signer; @@ -8,7 +8,7 @@ use crate::tx_msg::Msg; use ibc_proto::ibc::core::channel::v1::MsgChannelOpenAck as RawMsgChannelOpenAck; use tendermint_proto::Protobuf; -use std::convert::{TryFrom, TryInto}; +use std::convert::TryFrom; pub const TYPE_URL: &str = "/ibc.core.channel.v1.MsgChannelOpenAck"; @@ -81,7 +81,7 @@ impl Msg for MsgChannelOpenAck { impl Protobuf for MsgChannelOpenAck {} impl TryFrom for MsgChannelOpenAck { - type Error = anomaly::Error; + type Error = Error; fn try_from(raw_msg: RawMsgChannelOpenAck) -> Result { let proofs = Proofs::new( @@ -91,25 +91,18 @@ impl TryFrom for MsgChannelOpenAck { None, raw_msg .proof_height - .ok_or(Kind::MissingHeight)? - .try_into() - .map_err(|e| Kind::InvalidProof.context(e))?, + .ok_or_else(Error::missing_height)? + .into(), ) - .map_err(|e| Kind::InvalidProof.context(e))?; + .map_err(Error::invalid_proof)?; Ok(MsgChannelOpenAck { - port_id: raw_msg - .port_id - .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, - channel_id: raw_msg - .channel_id - .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, + port_id: raw_msg.port_id.parse().map_err(Error::identifier)?, + channel_id: raw_msg.channel_id.parse().map_err(Error::identifier)?, counterparty_channel_id: raw_msg .counterparty_channel_id .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, + .map_err(Error::identifier)?, counterparty_version: validate_version(raw_msg.counterparty_version)?, proofs, signer: raw_msg.signer.into(), diff --git a/modules/src/ics04_channel/msgs/chan_open_confirm.rs b/modules/src/ics04_channel/msgs/chan_open_confirm.rs index 95024446d0..4c9fe464dc 100644 --- a/modules/src/ics04_channel/msgs/chan_open_confirm.rs +++ b/modules/src/ics04_channel/msgs/chan_open_confirm.rs @@ -1,4 +1,4 @@ -use crate::ics04_channel::error::{Error, Kind}; +use crate::ics04_channel::error::Error; use crate::ics24_host::identifier::{ChannelId, PortId}; use crate::proofs::Proofs; use crate::signer::Signer; @@ -7,7 +7,7 @@ use crate::tx_msg::Msg; use ibc_proto::ibc::core::channel::v1::MsgChannelOpenConfirm as RawMsgChannelOpenConfirm; use tendermint_proto::Protobuf; -use std::convert::{TryFrom, TryInto}; +use std::convert::TryFrom; pub const TYPE_URL: &str = "/ibc.core.channel.v1.MsgChannelOpenConfirm"; @@ -63,7 +63,7 @@ impl Msg for MsgChannelOpenConfirm { impl Protobuf for MsgChannelOpenConfirm {} impl TryFrom for MsgChannelOpenConfirm { - type Error = anomaly::Error; + type Error = Error; fn try_from(raw_msg: RawMsgChannelOpenConfirm) -> Result { let proofs = Proofs::new( @@ -73,21 +73,14 @@ impl TryFrom for MsgChannelOpenConfirm { None, raw_msg .proof_height - .ok_or(Kind::MissingHeight)? - .try_into() - .map_err(|e| Kind::InvalidProof.context(e))?, + .ok_or_else(Error::missing_height)? + .into(), ) - .map_err(|e| Kind::InvalidProof.context(e))?; + .map_err(Error::invalid_proof)?; Ok(MsgChannelOpenConfirm { - port_id: raw_msg - .port_id - .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, - channel_id: raw_msg - .channel_id - .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, + port_id: raw_msg.port_id.parse().map_err(Error::identifier)?, + channel_id: raw_msg.channel_id.parse().map_err(Error::identifier)?, proofs, signer: raw_msg.signer.into(), }) diff --git a/modules/src/ics04_channel/msgs/chan_open_init.rs b/modules/src/ics04_channel/msgs/chan_open_init.rs index 68afb9c4db..7c9765837c 100644 --- a/modules/src/ics04_channel/msgs/chan_open_init.rs +++ b/modules/src/ics04_channel/msgs/chan_open_init.rs @@ -1,5 +1,5 @@ use crate::ics04_channel::channel::ChannelEnd; -use crate::ics04_channel::error::{Error, Kind}; +use crate::ics04_channel::error::Error; use crate::ics24_host::identifier::PortId; use crate::signer::Signer; use crate::tx_msg::Msg; @@ -57,15 +57,15 @@ impl Msg for MsgChannelOpenInit { impl Protobuf for MsgChannelOpenInit {} impl TryFrom for MsgChannelOpenInit { - type Error = anomaly::Error; + type Error = Error; fn try_from(raw_msg: RawMsgChannelOpenInit) -> Result { Ok(MsgChannelOpenInit { - port_id: raw_msg - .port_id - .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, - channel: raw_msg.channel.ok_or(Kind::MissingChannel)?.try_into()?, + port_id: raw_msg.port_id.parse().map_err(Error::identifier)?, + channel: raw_msg + .channel + .ok_or_else(Error::missing_channel)? + .try_into()?, signer: raw_msg.signer.into(), }) } diff --git a/modules/src/ics04_channel/msgs/chan_open_try.rs b/modules/src/ics04_channel/msgs/chan_open_try.rs index 816bc68725..9046e35d46 100644 --- a/modules/src/ics04_channel/msgs/chan_open_try.rs +++ b/modules/src/ics04_channel/msgs/chan_open_try.rs @@ -1,7 +1,6 @@ use crate::ics04_channel::channel::{validate_version, ChannelEnd}; -use crate::ics04_channel::error::{Error, Kind}; +use crate::ics04_channel::error::Error as ChannelError; use crate::ics24_host::error::ValidationError; -use crate::ics24_host::error::ValidationKind; use crate::ics24_host::identifier::{ChannelId, PortId}; use crate::proofs::Proofs; use crate::signer::Signer; @@ -65,7 +64,7 @@ impl MsgChannelOpenTry { } } impl Msg for MsgChannelOpenTry { - type ValidationError = Error; + type ValidationError = ChannelError; type Raw = RawMsgChannelOpenTry; fn route(&self) -> String { @@ -78,7 +77,7 @@ impl Msg for MsgChannelOpenTry { fn validate_basic(&self) -> Result<(), ValidationError> { match self.channel().counterparty().channel_id() { - None => Err(ValidationKind::InvalidCounterpartyChannelId.into()), + None => Err(ValidationError::invalid_counterparty_channel_id()), Some(_c) => Ok(()), } } @@ -87,7 +86,7 @@ impl Msg for MsgChannelOpenTry { impl Protobuf for MsgChannelOpenTry {} impl TryFrom for MsgChannelOpenTry { - type Error = anomaly::Error; + type Error = ChannelError; fn try_from(raw_msg: RawMsgChannelOpenTry) -> Result { let proofs = Proofs::new( @@ -97,34 +96,33 @@ impl TryFrom for MsgChannelOpenTry { None, raw_msg .proof_height - .ok_or(Kind::MissingHeight)? - .try_into() - .map_err(|e| Kind::InvalidProof.context(e))?, + .ok_or_else(ChannelError::missing_height)? + .into(), ) - .map_err(|e| Kind::InvalidProof.context(e))?; + .map_err(ChannelError::invalid_proof)?; let previous_channel_id = Some(raw_msg.previous_channel_id) .filter(|x| !x.is_empty()) .map(|v| FromStr::from_str(v.as_str())) .transpose() - .map_err(|e| Kind::IdentifierError.context(e))?; + .map_err(ChannelError::identifier)?; let msg = MsgChannelOpenTry { - port_id: raw_msg - .port_id - .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, + port_id: raw_msg.port_id.parse().map_err(ChannelError::identifier)?, previous_channel_id, - channel: raw_msg.channel.ok_or(Kind::MissingChannel)?.try_into()?, + channel: raw_msg + .channel + .ok_or_else(ChannelError::missing_channel)? + .try_into()?, counterparty_version: validate_version(raw_msg.counterparty_version)?, proofs, signer: raw_msg.signer.into(), }; - match msg.validate_basic() { - Err(_e) => Err(Kind::InvalidCounterpartyChannelId.into()), - Ok(()) => Ok(msg), - } + msg.validate_basic() + .map_err(ChannelError::invalid_counterparty_channel_id)?; + + Ok(msg) } } diff --git a/modules/src/ics04_channel/msgs/recv_packet.rs b/modules/src/ics04_channel/msgs/recv_packet.rs index b8adefd46f..f5f952f2e1 100644 --- a/modules/src/ics04_channel/msgs/recv_packet.rs +++ b/modules/src/ics04_channel/msgs/recv_packet.rs @@ -4,7 +4,7 @@ use tendermint_proto::Protobuf; use ibc_proto::ibc::core::channel::v1::MsgRecvPacket as RawMsgRecvPacket; -use crate::ics04_channel::error::{Error, Kind}; +use crate::ics04_channel::error::Error; use crate::ics04_channel::packet::Packet; use crate::proofs::Proofs; use crate::signer::Signer; @@ -48,7 +48,7 @@ impl Msg for MsgRecvPacket { impl Protobuf for MsgRecvPacket {} impl TryFrom for MsgRecvPacket { - type Error = anomaly::Error; + type Error = Error; fn try_from(raw_msg: RawMsgRecvPacket) -> Result { let proofs = Proofs::new( @@ -58,18 +58,16 @@ impl TryFrom for MsgRecvPacket { None, raw_msg .proof_height - .ok_or(Kind::MissingHeight)? - .try_into() - .map_err(|e| Kind::InvalidProof.context(e))?, + .ok_or_else(Error::missing_height)? + .into(), ) - .map_err(|e| Kind::InvalidProof.context(e))?; + .map_err(Error::invalid_proof)?; Ok(MsgRecvPacket { packet: raw_msg .packet - .ok_or(Kind::MissingPacket)? - .try_into() - .map_err(|e| Kind::InvalidPacket.context(e))?, + .ok_or_else(Error::missing_packet)? + .try_into()?, proofs, signer: raw_msg.signer.into(), }) diff --git a/modules/src/ics04_channel/msgs/timeout.rs b/modules/src/ics04_channel/msgs/timeout.rs index ba43692aee..088bbc4445 100644 --- a/modules/src/ics04_channel/msgs/timeout.rs +++ b/modules/src/ics04_channel/msgs/timeout.rs @@ -4,7 +4,7 @@ use tendermint_proto::Protobuf; use ibc_proto::ibc::core::channel::v1::MsgTimeout as RawMsgTimeout; -use crate::ics04_channel::error::{Error, Kind}; +use crate::ics04_channel::error::Error; use crate::ics04_channel::packet::{Packet, Sequence}; use crate::proofs::Proofs; use crate::signer::Signer; @@ -55,7 +55,7 @@ impl Msg for MsgTimeout { impl Protobuf for MsgTimeout {} impl TryFrom for MsgTimeout { - type Error = anomaly::Error; + type Error = Error; fn try_from(raw_msg: RawMsgTimeout) -> Result { let proofs = Proofs::new( @@ -65,20 +65,18 @@ impl TryFrom for MsgTimeout { None, raw_msg .proof_height - .ok_or(Kind::MissingHeight)? - .try_into() - .map_err(|e| Kind::InvalidProof.context(e))?, + .ok_or_else(Error::missing_height)? + .into(), ) - .map_err(|e| Kind::InvalidProof.context(e))?; + .map_err(Error::invalid_proof)?; // TODO: Domain type verification for the next sequence: this should probably be > 0. Ok(MsgTimeout { packet: raw_msg .packet - .ok_or(Kind::MissingPacket)? - .try_into() - .map_err(|e| Kind::InvalidPacket.context(e))?, + .ok_or_else(Error::missing_packet)? + .try_into()?, next_sequence_recv: Sequence::from(raw_msg.next_sequence_recv), signer: raw_msg.signer.into(), proofs, diff --git a/modules/src/ics04_channel/msgs/timeout_on_close.rs b/modules/src/ics04_channel/msgs/timeout_on_close.rs index ace86cb977..c181ab0772 100644 --- a/modules/src/ics04_channel/msgs/timeout_on_close.rs +++ b/modules/src/ics04_channel/msgs/timeout_on_close.rs @@ -4,7 +4,7 @@ use tendermint_proto::Protobuf; use ibc_proto::ibc::core::channel::v1::MsgTimeoutOnClose as RawMsgTimeoutOnClose; -use crate::ics04_channel::error::{Error, Kind}; +use crate::ics04_channel::error::Error; use crate::ics04_channel::packet::{Packet, Sequence}; use crate::proofs::Proofs; use crate::signer::Signer; @@ -55,7 +55,7 @@ impl Msg for MsgTimeoutOnClose { impl Protobuf for MsgTimeoutOnClose {} impl TryFrom for MsgTimeoutOnClose { - type Error = anomaly::Error; + type Error = Error; fn try_from(raw_msg: RawMsgTimeoutOnClose) -> Result { let proofs = Proofs::new( @@ -65,20 +65,18 @@ impl TryFrom for MsgTimeoutOnClose { None, raw_msg .proof_height - .ok_or(Kind::MissingHeight)? - .try_into() - .map_err(|e| Kind::InvalidProof.context(e))?, + .ok_or_else(Error::missing_height)? + .into(), ) - .map_err(|e| Kind::InvalidProof.context(e))?; + .map_err(Error::invalid_proof)?; // TODO: Domain type verification for the next sequence: this should probably be > 0. Ok(MsgTimeoutOnClose { packet: raw_msg .packet - .ok_or(Kind::MissingPacket)? - .try_into() - .map_err(|e| Kind::InvalidPacket.context(e))?, + .ok_or_else(Error::missing_packet)? + .try_into()?, next_sequence_recv: Sequence::from(raw_msg.next_sequence_recv), signer: raw_msg.signer.into(), proofs, diff --git a/modules/src/ics04_channel/packet.rs b/modules/src/ics04_channel/packet.rs index 672d777449..9f32c5863e 100644 --- a/modules/src/ics04_channel/packet.rs +++ b/modules/src/ics04_channel/packet.rs @@ -1,11 +1,11 @@ -use std::convert::{TryFrom, TryInto}; +use std::convert::TryFrom; use std::str::FromStr; use serde_derive::{Deserialize, Serialize}; use ibc_proto::ibc::core::channel::v1::Packet as RawPacket; -use crate::ics04_channel::error::Kind; +use crate::ics04_channel::error::Error; use crate::ics24_host::identifier::{ChannelId, PortId}; use crate::timestamp::{Expiry::Expired, Timestamp}; use crate::Height; @@ -62,11 +62,11 @@ impl Default for Sequence { } impl FromStr for Sequence { - type Err = anomaly::Error; + type Err = Error; fn from_str(s: &str) -> Result { - Ok(Self::from(s.parse::().map_err(|_e| { - Kind::InvalidStringAsSequence(s.to_string()) + Ok(Self::from(s.parse::().map_err(|e| { + Error::invalid_string_as_sequence(s.to_string(), e) })?)) } } @@ -163,46 +163,39 @@ impl Default for Packet { } impl TryFrom for Packet { - type Error = anomaly::Error; + type Error = Error; fn try_from(raw_pkt: RawPacket) -> Result { if Sequence::from(raw_pkt.sequence).is_zero() { - return Err(Kind::ZeroPacketSequence.into()); + return Err(Error::zero_packet_sequence()); } let packet_timeout_height: Height = raw_pkt .timeout_height - .ok_or(Kind::MissingHeight)? - .try_into() - .map_err(|e| Kind::InvalidTimeoutHeight.context(e))?; + .ok_or_else(Error::missing_height)? + .into(); if packet_timeout_height.is_zero() && raw_pkt.timeout_timestamp == 0 { - return Err(Kind::ZeroPacketTimeout.into()); + return Err(Error::zero_packet_timeout()); } if raw_pkt.data.is_empty() { - return Err(Kind::ZeroPacketData.into()); + return Err(Error::zero_packet_data()); } let timeout_timestamp = Timestamp::from_nanoseconds(raw_pkt.timeout_timestamp) - .map_err(|_| Kind::InvalidPacketTimestamp)?; + .map_err(Error::invalid_packet_timestamp)?; Ok(Packet { sequence: Sequence::from(raw_pkt.sequence), - source_port: raw_pkt - .source_port - .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, - source_channel: raw_pkt - .source_channel - .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, + source_port: raw_pkt.source_port.parse().map_err(Error::identifier)?, + source_channel: raw_pkt.source_channel.parse().map_err(Error::identifier)?, destination_port: raw_pkt .destination_port .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, + .map_err(Error::identifier)?, destination_channel: raw_pkt .destination_channel .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, + .map_err(Error::identifier)?, data: raw_pkt.data, timeout_height: packet_timeout_height, timeout_timestamp, diff --git a/modules/src/ics04_channel/version.rs b/modules/src/ics04_channel/version.rs index 29a1ff6238..2356194a9c 100644 --- a/modules/src/ics04_channel/version.rs +++ b/modules/src/ics04_channel/version.rs @@ -3,7 +3,7 @@ use std::convert::TryFrom; use ibc_proto::ibc::core::connection::v1::Version as RawVersion; use tendermint_proto::Protobuf; -use crate::ics04_channel::error::{Error, Kind}; +use crate::ics04_channel::error::Error; use std::str::FromStr; #[derive(Clone, Debug, PartialEq, Eq)] @@ -17,7 +17,7 @@ pub struct Version { impl Protobuf for Version {} impl TryFrom for Version { - type Error = anomaly::Error; + type Error = Error; fn try_from(value: RawVersion) -> Result { Ok(Version { identifier: value.identifier, @@ -48,7 +48,7 @@ impl FromStr for Version { type Err = Error; fn from_str(s: &str) -> Result { - Ok(Version::decode(s.as_bytes()).map_err(|e| Kind::InvalidVersion.context(e))?) + Version::decode(s.as_bytes()).map_err(Error::invalid_version) } } @@ -76,8 +76,7 @@ pub fn pick_version( ) -> Result { let mut intersection: Vec = vec![]; for s in supported_versions.iter() { - let supported_version = - Version::decode(s.as_bytes()).map_err(|e| Kind::InvalidVersion.context(e))?; + let supported_version = Version::decode(s.as_bytes()).map_err(Error::invalid_version)?; for c in counterparty_versions.iter() { let counterparty_version = Version::from_str(c.as_str())?; if supported_version.identifier != counterparty_version.identifier { @@ -89,16 +88,14 @@ pub fn pick_version( } intersection.sort_by(|a, b| a.identifier.cmp(&b.identifier)); if intersection.is_empty() { - return Err(Kind::NoCommonVersion.into()); + return Err(Error::no_common_version()); } Ok(intersection[0].to_string()) } pub fn validate_versions(versions: Vec) -> Result, Error> { if versions.is_empty() { - return Err(Kind::InvalidVersion - .context("no versions".to_string()) - .into()); + return Err(Error::empty_version()); } for version_str in versions.iter() { validate_version(version_str.clone())?; @@ -107,19 +104,14 @@ pub fn validate_versions(versions: Vec) -> Result, Error> { } pub fn validate_version(raw_version: String) -> Result { - let version = - Version::from_str(raw_version.as_ref()).map_err(|e| Kind::InvalidVersion.context(e))?; + let version = Version::from_str(raw_version.as_ref())?; if version.identifier.trim().is_empty() { - return Err(Kind::InvalidVersion - .context("empty version string".to_string()) - .into()); + return Err(Error::empty_version()); } for feature in version.features { if feature.trim().is_empty() { - return Err(Kind::InvalidVersion - .context("empty feature string".to_string()) - .into()); + return Err(Error::empty_version()); } } Ok(raw_version) diff --git a/modules/src/ics05_port/error.rs b/modules/src/ics05_port/error.rs index 51ae2bd614..9c94b0d6c5 100644 --- a/modules/src/ics05_port/error.rs +++ b/modules/src/ics05_port/error.rs @@ -1,16 +1,8 @@ -use anomaly::{BoxError, Context}; -use thiserror::Error; +use flex_error::define_error; -pub type Error = anomaly::Error; - -#[derive(Clone, Debug, Error)] -pub enum Kind { - #[error("port unknown")] - UnknownPort, -} - -impl Kind { - pub fn context(self, source: impl Into) -> Context { - Context::new(self, Some(source.into())) +define_error! { + Error { + UnknownPort + | _ | { format_args!("port unknown") } } } diff --git a/modules/src/ics07_tendermint/client_def.rs b/modules/src/ics07_tendermint/client_def.rs index b6246c093b..8646574a91 100644 --- a/modules/src/ics07_tendermint/client_def.rs +++ b/modules/src/ics07_tendermint/client_def.rs @@ -3,6 +3,7 @@ use ibc_proto::ibc::core::commitment::v1::MerkleProof; use crate::ics02_client::client_consensus::AnyConsensusState; use crate::ics02_client::client_def::ClientDef; use crate::ics02_client::client_state::AnyClientState; +use crate::ics02_client::error::Error; use crate::ics03_connection::connection::ConnectionEnd; use crate::ics04_channel::channel::ChannelEnd; use crate::ics04_channel::packet::Sequence; @@ -26,12 +27,12 @@ impl ClientDef for TendermintClient { &self, client_state: Self::ClientState, header: Self::Header, - ) -> Result<(Self::ClientState, Self::ConsensusState), Box> { + ) -> Result<(Self::ClientState, Self::ConsensusState), Error> { if client_state.latest_height() >= header.height() { - return Err( - format!("received header height ({:?}) is lower than (or equal to) client latest height ({:?})", - header.height(), client_state.latest_height).into(), - ); + return Err(Error::low_header_height( + header.height(), + client_state.latest_height(), + )); } // TODO: Additional verifications should be implemented here. @@ -51,7 +52,7 @@ impl ClientDef for TendermintClient { _client_id: &ClientId, _consensus_height: Height, _expected_consensus_state: &AnyConsensusState, - ) -> Result<(), Box> { + ) -> Result<(), Error> { todo!() } @@ -63,7 +64,7 @@ impl ClientDef for TendermintClient { _proof: &CommitmentProofBytes, _connection_id: Option<&ConnectionId>, _expected_connection_end: &ConnectionEnd, - ) -> Result<(), Box> { + ) -> Result<(), Error> { todo!() } @@ -76,7 +77,7 @@ impl ClientDef for TendermintClient { _port_id: &PortId, _channel_id: &ChannelId, _expected_channel_end: &ChannelEnd, - ) -> Result<(), Box> { + ) -> Result<(), Error> { todo!() } @@ -89,7 +90,7 @@ impl ClientDef for TendermintClient { _client_id: &ClientId, _proof: &CommitmentProofBytes, _expected_client_state: &AnyClientState, - ) -> Result<(), Box> { + ) -> Result<(), Error> { unimplemented!() } @@ -102,7 +103,7 @@ impl ClientDef for TendermintClient { _channel_id: &ChannelId, _seq: &Sequence, _data: String, - ) -> Result<(), Box> { + ) -> Result<(), Error> { todo!() } @@ -115,7 +116,7 @@ impl ClientDef for TendermintClient { _channel_id: &ChannelId, _seq: &Sequence, _data: Vec, - ) -> Result<(), Box> { + ) -> Result<(), Error> { todo!() } @@ -127,7 +128,7 @@ impl ClientDef for TendermintClient { _port_id: &PortId, _channel_id: &ChannelId, _seq: &Sequence, - ) -> Result<(), Box> { + ) -> Result<(), Error> { todo!() } @@ -139,7 +140,7 @@ impl ClientDef for TendermintClient { _port_id: &PortId, _channel_id: &ChannelId, _seq: &Sequence, - ) -> Result<(), Box> { + ) -> Result<(), Error> { todo!() } @@ -149,7 +150,7 @@ impl ClientDef for TendermintClient { _consensus_state: &Self::ConsensusState, _proof_upgrade_client: MerkleProof, _proof_upgrade_consensus_state: MerkleProof, - ) -> Result<(Self::ClientState, Self::ConsensusState), Box> { + ) -> Result<(Self::ClientState, Self::ConsensusState), Error> { todo!() } } diff --git a/modules/src/ics07_tendermint/client_state.rs b/modules/src/ics07_tendermint/client_state.rs index c4ab1b4efb..f2da51299f 100644 --- a/modules/src/ics07_tendermint/client_state.rs +++ b/modules/src/ics07_tendermint/client_state.rs @@ -12,7 +12,7 @@ use ibc_proto::ibc::lightclients::tendermint::v1::{ClientState as RawClientState use crate::ics02_client::client_state::AnyClientState; use crate::ics02_client::client_type::ClientType; -use crate::ics07_tendermint::error::{Error, Kind}; +use crate::ics07_tendermint::error::Error; use crate::ics07_tendermint::header::Header; use crate::ics23_commitment::specs::ProofSpecs; use crate::ics24_host::identifier::ChainId; @@ -56,32 +56,32 @@ impl ClientState { ) -> Result { // Basic validation of trusting period and unbonding period: each should be non-zero. if trusting_period <= Duration::new(0, 0) { - return Err(Kind::InvalidTrustingPeriod - .context("ClientState trusting period must be greater than zero") - .into()); + return Err(Error::invalid_trusting_period( + "ClientState trusting period must be greater than zero".to_string(), + )); } if unbonding_period <= Duration::new(0, 0) { - return Err(Kind::InvalidUnboundingPeriod - .context("ClientState unbonding period must be greater than zero") - .into()); + return Err(Error::invalid_unbounding_period( + "ClientState unbonding period must be greater than zero".to_string(), + )); } if trusting_period >= unbonding_period { - return Err(Kind::InvalidUnboundingPeriod - .context("ClientState trusting period must be smaller than unbonding period") - .into()); + return Err(Error::invalid_unbounding_period( + "ClientState trusting period must be smaller than unbonding period".to_string(), + )); } // Basic validation for the frozen_height parameter. if !frozen_height.is_zero() { - return Err(Kind::ValidationError - .context("ClientState cannot be frozen at creation time") - .into()); + return Err(Error::validation( + "ClientState cannot be frozen at creation time".to_string(), + )); } // Basic validation for the latest_height parameter. if latest_height <= Height::zero() { - return Err(Kind::ValidationError - .context("ClientState latest height cannot be smaller or equal than zero") - .into()); + return Err(Error::validation( + "ClientState latest height cannot be smaller or equal than zero".to_string(), + )); } Ok(Self { @@ -165,38 +165,36 @@ impl TryFrom for ClientState { let trust_level = raw .trust_level .clone() - .ok_or_else(|| Kind::InvalidRawClientState.context("missing trusting period"))?; + .ok_or_else(Error::missing_trusting_period)?; Ok(Self { chain_id: ChainId::from_str(raw.chain_id.as_str()) - .map_err(|_| Kind::InvalidRawClientState.context("Invalid chain identifier"))?, + .map_err(Error::invalid_chain_identifier)?, trust_level: TrustThreshold::new(trust_level.numerator, trust_level.denominator) - .map_err(|e| Kind::InvalidTrustThreshold.context(e))?, + .map_err(|e| Error::invalid_trust_threshold(format!("{}", e)))?, trusting_period: raw .trusting_period - .ok_or_else(|| Kind::InvalidRawClientState.context("missing trusting period"))? + .ok_or_else(Error::missing_trusting_period)? .try_into() - .map_err(|_| Kind::InvalidRawClientState.context("negative trusting period"))?, + .map_err(|_| Error::negative_trusting_period())?, unbonding_period: raw .unbonding_period - .ok_or_else(|| Kind::InvalidRawClientState.context("missing unbonding period"))? + .ok_or_else(Error::missing_unbonding_period)? .try_into() - .map_err(|_| Kind::InvalidRawClientState.context("negative unbonding period"))?, + .map_err(|_| Error::negative_unbonding_period())?, max_clock_drift: raw .max_clock_drift - .ok_or_else(|| Kind::InvalidRawClientState.context("missing max clock drift"))? + .ok_or_else(Error::missing_max_clock_drift)? .try_into() - .map_err(|_| Kind::InvalidRawClientState.context("negative max clock drift"))?, + .map_err(|_| Error::negative_max_clock_drift())?, latest_height: raw .latest_height - .ok_or_else(|| Kind::InvalidRawClientState.context("missing latest height"))? - .try_into() - .map_err(|_| Kind::InvalidRawHeight)?, + .ok_or_else(Error::missing_latest_height)? + .into(), frozen_height: raw .frozen_height - .ok_or_else(|| Kind::InvalidRawClientState.context("missing frozen height"))? - .try_into() - .map_err(|_| Kind::InvalidRawHeight)?, + .ok_or_else(Error::missing_frozen_height)? + .into(), upgrade_path: raw.upgrade_path, allow_update: AllowUpdate { after_expiry: raw.allow_update_after_expiry, diff --git a/modules/src/ics07_tendermint/consensus_state.rs b/modules/src/ics07_tendermint/consensus_state.rs index b7510dd14e..521636fe9f 100644 --- a/modules/src/ics07_tendermint/consensus_state.rs +++ b/modules/src/ics07_tendermint/consensus_state.rs @@ -1,3 +1,4 @@ +use std::convert::Infallible; use std::convert::TryFrom; use std::time::SystemTime; @@ -11,7 +12,7 @@ use ibc_proto::ibc::lightclients::tendermint::v1::ConsensusState as RawConsensus use crate::ics02_client::client_consensus::AnyConsensusState; use crate::ics02_client::client_type::ClientType; -use crate::ics07_tendermint::error::{Error, Kind}; +use crate::ics07_tendermint::error::Error; use crate::ics07_tendermint::header::Header; use crate::ics23_commitment::commitment::CommitmentRoot; @@ -33,6 +34,8 @@ impl ConsensusState { } impl crate::ics02_client::client_consensus::ConsensusState for ConsensusState { + type Error = Infallible; + fn client_type(&self) -> ClientType { ClientType::Tendermint } @@ -41,7 +44,7 @@ impl crate::ics02_client::client_consensus::ConsensusState for ConsensusState { &self.root } - fn validate_basic(&self) -> Result<(), Box> { + fn validate_basic(&self) -> Result<(), Infallible> { unimplemented!() } @@ -58,19 +61,21 @@ impl TryFrom for ConsensusState { fn try_from(raw: RawConsensusState) -> Result { let proto_timestamp = raw .timestamp - .ok_or_else(|| Kind::InvalidRawConsensusState.context("missing timestamp"))?; + .ok_or_else(|| Error::invalid_raw_consensus_state("missing timestamp".into()))?; Ok(Self { root: raw .root - .ok_or_else(|| Kind::InvalidRawConsensusState.context("missing commitment root"))? + .ok_or_else(|| { + Error::invalid_raw_consensus_state("missing commitment root".into()) + })? .hash .into(), timestamp: Utc .timestamp(proto_timestamp.seconds, proto_timestamp.nanos as u32) .into(), next_validators_hash: Hash::from_bytes(Algorithm::Sha256, &raw.next_validators_hash) - .map_err(|e| Kind::InvalidRawConsensusState.context(e.to_string()))?, + .map_err(|e| Error::invalid_raw_consensus_state(e.to_string()))?, }) } } diff --git a/modules/src/ics07_tendermint/error.rs b/modules/src/ics07_tendermint/error.rs index 4f372a5a99..949238cca5 100644 --- a/modules/src/ics07_tendermint/error.rs +++ b/modules/src/ics07_tendermint/error.rs @@ -1,51 +1,102 @@ -use anomaly::{BoxError, Context}; -use thiserror::Error; +use crate::ics24_host::error::ValidationError; +use flex_error::{define_error, DisplayOnly, TraceError}; -use crate::ics24_host::error::ValidationKind; +define_error! { + Error { + InvalidTrustingPeriod + { reason: String } + | _ | { "invalid trusting period" }, -pub type Error = anomaly::Error; + InvalidUnboundingPeriod + { reason: String } + | _ | { "invalid unbonding period" }, -#[derive(Clone, Debug, Error)] -pub enum Kind { - #[error("invalid trusting period")] - InvalidTrustingPeriod, + InvalidAddress + | _ | { "invalid address" }, - #[error("invalid client state trust threshold")] - InvalidTrustThreshold, + InvalidHeader + { reason: String } + [ DisplayOnly> ] + | _ | { "invalid header, failed basic validation" }, - #[error("invalid unbonding period")] - InvalidUnboundingPeriod, + InvalidTrustThreshold + { reason: String } + | e | { + format_args!("invalid client state trust threshold: {}", + e.reason) + }, - #[error("invalid address")] - InvalidAddress, + MissingSignedHeader + | _ | { "missing signed header" }, - #[error("invalid header, failed basic validation")] - InvalidHeader, + Validation + { reason: String } + | _ | { "invalid header, failed basic validation" }, - #[error("validation error")] - ValidationError, + InvalidRawClientState + { reason: String } + | _ | { "invalid raw client state" }, - #[error("invalid raw client state")] - InvalidRawClientState, + MissingValidatorSet + | _ | { "missing validator set" }, - #[error("invalid chain identifier: raw value {0} with underlying validation error: {1}")] - InvalidChainId(String, ValidationKind), + MissingTrustedValidatorSet + | _ | { "missing trusted validator set" }, - #[error("invalid raw height")] - InvalidRawHeight, + MissingTrustedHeight + | _ | { "missing trusted height" }, - #[error("invalid raw client consensus state")] - InvalidRawConsensusState, + MissingTrustingPeriod + | _ | { "missing trusting period" }, - #[error("invalid raw header")] - InvalidRawHeader, + MissingUnbondingPeriod + | _ | { "missing unbonding period" }, - #[error("invalid raw misbehaviour")] - InvalidRawMisbehaviour, -} + InvalidChainIdentifier + [ ValidationError ] + | _ | { "Invalid chain identifier" }, + + NegativeTrustingPeriod + | _ | { "negative trusting period" }, + + NegativeUnbondingPeriod + | _ | { "negative unbonding period" }, + + MissingMaxClockDrift + | _ | { "missing max clock drift" }, + + NegativeMaxClockDrift + | _ | { "negative max clock drift" }, + + MissingLatestHeight + | _ | { "missing latest height" }, + + MissingFrozenHeight + | _ | { "missing frozen height" }, + + InvalidChainId + { raw_value: String } + [ ValidationError ] + | e | { format_args!("invalid chain identifier: raw value {0}", e.raw_value) }, + + InvalidRawHeight + | _ | { "invalid raw height" }, + + InvalidRawConsensusState + { reason: String } + | _ | { "invalid raw client consensus state" }, + + InvalidRawHeader + [ DisplayOnly> ] + | _ | { "invalid raw header" }, + + InvalidRawMisbehaviour + { reason: String } + | _ | { "invalid raw misbehaviour" }, + + Decode + [ TraceError ] + | _ | { "decode error" }, -impl Kind { - pub fn context(self, source: impl Into) -> Context { - Context::new(self, Some(source.into())) } } diff --git a/modules/src/ics07_tendermint/header.rs b/modules/src/ics07_tendermint/header.rs index 5c0a21b0cb..152ca4607f 100644 --- a/modules/src/ics07_tendermint/header.rs +++ b/modules/src/ics07_tendermint/header.rs @@ -1,5 +1,7 @@ use std::convert::{TryFrom, TryInto}; +use bytes::Buf; +use prost::Message; use serde_derive::{Deserialize, Serialize}; use tendermint::block::signed_header::SignedHeader; use tendermint::validator::Set as ValidatorSet; @@ -10,7 +12,7 @@ use ibc_proto::ibc::lightclients::tendermint::v1::Header as RawHeader; use crate::ics02_client::client_type::ClientType; use crate::ics02_client::header::AnyHeader; -use crate::ics07_tendermint::error::{Error, Kind}; +use crate::ics07_tendermint::error::Error; use crate::ics24_host::identifier::ChainId; use crate::Height; use std::cmp::Ordering; @@ -90,28 +92,31 @@ impl TryFrom for Header { Ok(Self { signed_header: raw .signed_header - .ok_or_else(|| Kind::InvalidRawHeader.context("missing signed header"))? + .ok_or_else(Error::missing_signed_header)? .try_into() - .map_err(|_| Kind::InvalidHeader.context("signed header conversion"))?, + .map_err(|e| Error::invalid_header("signed header conversion".to_string(), e))?, validator_set: raw .validator_set - .ok_or_else(|| Kind::InvalidRawHeader.context("missing validator set"))? + .ok_or_else(Error::missing_validator_set)? .try_into() - .map_err(|e| Kind::InvalidRawHeader.context(e))?, + .map_err(Error::invalid_raw_header)?, trusted_height: raw .trusted_height - .ok_or_else(|| Kind::InvalidRawHeader.context("missing height"))? - .try_into() - .map_err(|e| Kind::InvalidRawHeight.context(e))?, + .ok_or_else(Error::missing_trusted_height)? + .into(), trusted_validator_set: raw .trusted_validators - .ok_or_else(|| Kind::InvalidRawHeader.context("missing trusted validator set"))? + .ok_or_else(Error::missing_trusted_validator_set)? .try_into() - .map_err(|e| Kind::InvalidRawHeader.context(e))?, + .map_err(Error::invalid_raw_header)?, }) } } +pub fn decode_header(buf: B) -> Result { + RawHeader::decode(buf).map_err(Error::decode)?.try_into() +} + impl From
for RawHeader { fn from(value: Header) -> Self { RawHeader { diff --git a/modules/src/ics07_tendermint/misbehaviour.rs b/modules/src/ics07_tendermint/misbehaviour.rs index 8061c082df..10bd7c1084 100644 --- a/modules/src/ics07_tendermint/misbehaviour.rs +++ b/modules/src/ics07_tendermint/misbehaviour.rs @@ -5,7 +5,7 @@ use tendermint_proto::Protobuf; use ibc_proto::ibc::lightclients::tendermint::v1::Misbehaviour as RawMisbehaviour; use crate::ics02_client::misbehaviour::AnyMisbehaviour; -use crate::ics07_tendermint::error::{Error, Kind}; +use crate::ics07_tendermint::error::Error; use crate::ics07_tendermint::header::Header; use crate::ics24_host::identifier::ClientId; use crate::Height; @@ -41,11 +41,11 @@ impl TryFrom for Misbehaviour { client_id: Default::default(), header1: raw .header_1 - .ok_or_else(|| Kind::InvalidRawMisbehaviour.context("missing header1"))? + .ok_or_else(|| Error::invalid_raw_misbehaviour("missing header1".into()))? .try_into()?, header2: raw .header_2 - .ok_or_else(|| Kind::InvalidRawMisbehaviour.context("missing header2"))? + .ok_or_else(|| Error::invalid_raw_misbehaviour("missing header2".into()))? .try_into()?, }) } diff --git a/modules/src/ics18_relayer/error.rs b/modules/src/ics18_relayer/error.rs index d1cbefbfce..eb75c7aa20 100644 --- a/modules/src/ics18_relayer/error.rs +++ b/modules/src/ics18_relayer/error.rs @@ -1,27 +1,38 @@ use crate::ics24_host::identifier::ClientId; +use crate::ics26_routing::error::Error as RoutingError; use crate::Height; -use anomaly::{BoxError, Context}; -use thiserror::Error; +use flex_error::define_error; -pub type Error = anomaly::Error; +define_error! { + Error { + ClientStateNotFound + { client_id: ClientId } + | e | { format_args!("client state on destination chain not found, (client id: {0})", e.client_id) }, -#[derive(Clone, Debug, Error, PartialEq, Eq)] -pub enum Kind { - #[error("client state on destination chain not found, (client id: {0})")] - ClientStateNotFound(ClientId), + ClientAlreadyUpToDate + { + client_id: ClientId, + source_height: Height, + destination_height: Height, + } + | e | { + format_args!("the client on destination chain is already up-to-date (client id: {0}, source height: {1}, dest height: {2})", + e.client_id, e.source_height, e.destination_height) + }, - #[error("the client on destination chain is already up-to-date (client id: {0}, source height: {1}, dest height: {2})")] - ClientAlreadyUpToDate(ClientId, Height, Height), + ClientAtHigherHeight + { + client_id: ClientId, + source_height: Height, + destination_height: Height, + } + | e | { + format_args!("the client on destination chain is at a higher height (client id: {0}, source height: {1}, dest height: {2})", + e.client_id, e.source_height, e.destination_height) + }, - #[error("the client on destination chain is at a higher height (client id: {0}, source height: {1}, dest height: {2})")] - ClientAtHigherHeight(ClientId, Height, Height), - - #[error("transaction processing by modules failed")] - TransactionFailed, -} - -impl Kind { - pub fn context(self, source: impl Into) -> Context { - Context::new(self, Some(source.into())) + TransactionFailed + [ RoutingError ] + | _ | { "transaction processing by modules failed" }, } } diff --git a/modules/src/ics18_relayer/utils.rs b/modules/src/ics18_relayer/utils.rs index fc74c29057..fd4bca4ba0 100644 --- a/modules/src/ics18_relayer/utils.rs +++ b/modules/src/ics18_relayer/utils.rs @@ -2,7 +2,7 @@ use crate::ics02_client::header::{AnyHeader, Header}; use crate::ics02_client::msgs::update_client::MsgUpdateAnyClient; use crate::ics02_client::msgs::ClientMsg; use crate::ics18_relayer::context::Ics18Context; -use crate::ics18_relayer::error::{Error, Kind}; +use crate::ics18_relayer::error::Error; use crate::ics24_host::identifier::ClientId; /// Builds a `ClientMsg::UpdateClient` for a client with id `client_id` running on the `dest` @@ -19,26 +19,24 @@ where // - query client state on destination chain let dest_client_state = dest .query_client_full_state(client_id) - .ok_or_else(|| Kind::ClientStateNotFound(client_id.clone()))?; + .ok_or_else(|| Error::client_state_not_found(client_id.clone()))?; let dest_client_latest_height = dest_client_state.latest_height(); if src_header.height() == dest_client_latest_height { - return Err(Kind::ClientAlreadyUpToDate( + return Err(Error::client_already_up_to_date( client_id.clone(), src_header.height(), dest_client_latest_height, - ) - .into()); + )); }; if dest_client_latest_height > src_header.height() { - return Err(Kind::ClientAtHigherHeight( + return Err(Error::client_at_higher_height( client_id.clone(), src_header.height(), dest_client_latest_height, - ) - .into()); + )); }; // Client on destination chain can be updated. diff --git a/modules/src/ics23_commitment/commitment.rs b/modules/src/ics23_commitment/commitment.rs index 4b0faac1e0..05dd099914 100644 --- a/modules/src/ics23_commitment/commitment.rs +++ b/modules/src/ics23_commitment/commitment.rs @@ -89,7 +89,7 @@ impl TryFrom for RawMerkleProof { fn try_from(value: CommitmentProofBytes) -> Result { let value: Vec = value.into(); let res: RawMerkleProof = - prost::Message::decode(value.as_ref()).map_err(Error::InvalidRawMerkleProof)?; + prost::Message::decode(value.as_ref()).map_err(Error::invalid_raw_merkle_proof)?; Ok(res) } } diff --git a/modules/src/ics23_commitment/error.rs b/modules/src/ics23_commitment/error.rs index d5c6a68c6c..bdcb8cd575 100644 --- a/modules/src/ics23_commitment/error.rs +++ b/modules/src/ics23_commitment/error.rs @@ -1,11 +1,15 @@ +use flex_error::{define_error, TraceError}; use prost::DecodeError; -use thiserror::Error; -#[derive(Clone, Debug, Error, PartialEq, Eq)] -pub enum Error { - #[error("invalid raw merkle proof")] - InvalidRawMerkleProof(DecodeError), +define_error! { + #[derive(Debug, Clone)] + Error { + InvalidRawMerkleProof + [ TraceError ] + |_| { "invalid raw merkle proof" }, - #[error("failed to decode commitment proof")] - CommitmentProofDecodingFailed(DecodeError), + CommitmentProofDecodingFailed + [ TraceError ] + |_| { "failed to decode commitment proof" }, + } } diff --git a/modules/src/ics23_commitment/merkle.rs b/modules/src/ics23_commitment/merkle.rs index 68fd7f183d..9103da5455 100644 --- a/modules/src/ics23_commitment/merkle.rs +++ b/modules/src/ics23_commitment/merkle.rs @@ -6,12 +6,15 @@ use ibc_proto::ibc::core::commitment::v1::MerkleProof as RawMerkleProof; use crate::ics23_commitment::commitment::{CommitmentPrefix, CommitmentProofBytes}; use crate::ics23_commitment::error::Error; +#[derive(Clone, Debug, PartialEq)] +pub struct EmptyPrefixError; + pub fn apply_prefix( prefix: &CommitmentPrefix, mut path: Vec, -) -> Result> { +) -> Result { if prefix.is_empty() { - return Err("empty prefix".into()); + return Err(EmptyPrefixError); } let mut result: Vec = vec![format!("{:?}", prefix)]; @@ -83,7 +86,7 @@ pub fn convert_tm_to_ics_merkle_proof(tm_proof: &Proof) -> Result; - -#[derive(Clone, Debug, Error, PartialEq, Eq)] -pub enum ValidationKind { - #[error("identifier {id} cannot contain separator '/'")] - ContainsSeparator { id: String }, - - #[error("identifier {id} has invalid length {length} must be between {min}-{max} characters")] - InvalidLength { - id: String, - length: usize, - min: usize, - max: usize, - }, - - #[error("identifier {id} must only contain alphanumeric characters or `.`, `_`, `+`, `-`, `#`, - `[`, `]`, `<`, `>`")] - InvalidCharacter { id: String }, - - #[error("identifier cannot be empty")] - Empty, - - #[error("chain identifiers are expected to be in epoch format {id}")] - ChainIdInvalidFormat { id: String }, - - #[error("Invalid channel id in counterparty")] - InvalidCounterpartyChannelId, -} - -impl ValidationKind { - pub fn contains_separator(id: String) -> Self { - Self::ContainsSeparator { id } - } - - pub fn invalid_length(id: String, length: usize, min: usize, max: usize) -> Self { - Self::InvalidLength { - id, - length, - min, - max, - } - } - - pub fn invalid_character(id: String) -> Self { - Self::InvalidCharacter { id } - } - - pub fn empty() -> Self { - Self::Empty - } - - pub fn chain_id_invalid_format(id: String) -> Self { - Self::ChainIdInvalidFormat { id } - } - - pub fn context(self, source: impl Into) -> Context { - Context::new(self, Some(source.into())) +use flex_error::define_error; + +define_error! { + #[derive(Debug, PartialEq, Eq)] + ValidationError { + ContainSeparator + { id : String } + | e | { format_args!("identifier {0} cannot contain separator '/'", e.id) }, + + InvalidLength + { + id: String, + length: usize, + min: usize, + max: usize, + } + | e | { format_args!("identifier {0} has invalid length {1} must be between {2}-{3} characters", e.id, e.length, e.min, e.max) }, + + InvalidCharacter + { id: String } + | e | { format_args!("identifier {0} must only contain alphanumeric characters or `.`, `_`, `+`, `-`, `#`, - `[`, `]`, `<`, `>`", e.id) }, + + Empty + | _ | { "identifier cannot be empty" }, + + ChainIdInvalidFormat + { id: String } + | e | { format_args!("chain identifiers are expected to be in epoch format {0}", e.id) }, + + InvalidCounterpartyChannelId + |_| { "Invalid channel id in counterparty" } } } diff --git a/modules/src/ics24_host/identifier.rs b/modules/src/ics24_host/identifier.rs index 53aa3ca2eb..a89ba20b30 100644 --- a/modules/src/ics24_host/identifier.rs +++ b/modules/src/ics24_host/identifier.rs @@ -4,16 +4,15 @@ use std::str::FromStr; use serde::{Deserialize, Serialize}; use crate::ics02_client::client_type::ClientType; -use crate::ics24_host::error::ValidationKind; +use crate::ics24_host::error::ValidationError; -use super::error::ValidationError; use super::validate::*; /// This type is subject to future changes. /// /// TODO: ChainId validation is not standardized yet. /// `is_epoch_format` will most likely be replaced by validate_chain_id()-style function. -/// See: https://github.com/informalsystems/ibc-rs/pull/304#discussion_r503917283. +/// See: . /// /// Also, contrast with tendermint-rs `ChainId` type. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] @@ -130,10 +129,10 @@ impl Default for ChainId { } impl TryFrom for ChainId { - type Error = ValidationKind; + type Error = ValidationError; fn try_from(value: String) -> Result { - Self::from_str(value.as_str()).map_err(|e| e.kind().clone()) + Self::from_str(value.as_str()) } } diff --git a/modules/src/ics24_host/validate.rs b/modules/src/ics24_host/validate.rs index 2650f732c9..c335203740 100644 --- a/modules/src/ics24_host/validate.rs +++ b/modules/src/ics24_host/validate.rs @@ -1,11 +1,4 @@ -use super::error::{ValidationError, ValidationKind}; - -/// Bails from the current function with the given error kind. -macro_rules! bail { - ($kind:expr) => { - return Err($kind.into()); - }; -} +use super::error::ValidationError as Error; /// Path separator (ie. forward slash '/') const PATH_SEPARATOR: char = '/'; @@ -15,27 +8,22 @@ const VALID_SPECIAL_CHARS: &str = "._+-#[]<>"; /// /// A valid identifier only contain lowercase alphabetic characters, and be of a given min and max /// length. -pub fn validate_identifier(id: &str, min: usize, max: usize) -> Result<(), ValidationError> { +pub fn validate_identifier(id: &str, min: usize, max: usize) -> Result<(), Error> { assert!(max >= min); // Check identifier is not empty if id.is_empty() { - bail!(ValidationKind::empty()); + return Err(Error::empty()); } // Check identifier does not contain path separators if id.contains(PATH_SEPARATOR) { - bail!(ValidationKind::contains_separator(id.to_string())); + return Err(Error::contain_separator(id.to_string())); } // Check identifier length is between given min/max if id.len() < min || id.len() > max { - bail!(ValidationKind::invalid_length( - id.to_string(), - id.len(), - min, - max - )); + return Err(Error::invalid_length(id.to_string(), id.len(), min, max)); } // Check that the identifier comprises only valid characters: @@ -46,7 +34,7 @@ pub fn validate_identifier(id: &str, min: usize, max: usize) -> Result<(), Valid .chars() .all(|c| c.is_alphanumeric() || VALID_SPECIAL_CHARS.contains(c)) { - bail!(ValidationKind::invalid_character(id.to_string())); + return Err(Error::invalid_character(id.to_string())); } // All good! @@ -57,7 +45,7 @@ pub fn validate_identifier(id: &str, min: usize, max: usize) -> Result<(), Valid /// /// A valid identifier must be between 9-64 characters and only contain lowercase /// alphabetic characters, -pub fn validate_client_identifier(id: &str) -> Result<(), ValidationError> { +pub fn validate_client_identifier(id: &str) -> Result<(), Error> { validate_identifier(id, 9, 64) } @@ -65,7 +53,7 @@ pub fn validate_client_identifier(id: &str) -> Result<(), ValidationError> { /// /// A valid Identifier must be between 10-64 characters and only contain lowercase /// alphabetic characters, -pub fn validate_connection_identifier(id: &str) -> Result<(), ValidationError> { +pub fn validate_connection_identifier(id: &str) -> Result<(), Error> { validate_identifier(id, 10, 64) } @@ -73,7 +61,7 @@ pub fn validate_connection_identifier(id: &str) -> Result<(), ValidationError> { /// /// A valid Identifier must be between 2-64 characters and only contain lowercase /// alphabetic characters, -pub fn validate_port_identifier(id: &str) -> Result<(), ValidationError> { +pub fn validate_port_identifier(id: &str) -> Result<(), Error> { validate_identifier(id, 2, 64) } @@ -81,7 +69,7 @@ pub fn validate_port_identifier(id: &str) -> Result<(), ValidationError> { /// /// A valid Identifier must be between 10-64 characters and only contain lowercase /// alphabetic characters, -pub fn validate_channel_identifier(id: &str) -> Result<(), ValidationError> { +pub fn validate_channel_identifier(id: &str) -> Result<(), Error> { validate_identifier(id, 8, 64) } diff --git a/modules/src/ics26_routing/error.rs b/modules/src/ics26_routing/error.rs index 868d9a1f9e..af64a047d2 100644 --- a/modules/src/ics26_routing/error.rs +++ b/modules/src/ics26_routing/error.rs @@ -1,25 +1,33 @@ -use anomaly::{BoxError, Context}; -use thiserror::Error; +use crate::application::ics20_fungible_token_transfer; +use crate::ics02_client; +use crate::ics03_connection; +use crate::ics04_channel; +use flex_error::{define_error, TraceError}; -pub type Error = anomaly::Error; +define_error! { + Error { + Ics02Client + [ ics02_client::error::Error ] + | _ | { "ICS02 client error" }, -#[derive(Clone, Debug, Error, PartialEq, Eq)] -pub enum Kind { - #[error("error raised by message handler")] - HandlerRaisedError, + Ics03Connection + [ ics03_connection::error::Error ] + | _ | { "ICS03 connection error" }, - #[error("error raised by the keeper functionality in message handler")] - KeeperRaisedError, + Ics04Channel + [ ics04_channel::error::Error ] + | _ | { "ICS04 channel error" }, - #[error("unknown type URL {0}")] - UnknownMessageTypeUrl(String), + Ics20FungibleTokenTransfer + [ ics20_fungible_token_transfer::error::Error ] + | _ | { "ICS20 fungible token transfer error" }, - #[error("the message is malformed and cannot be decoded")] - MalformedMessageBytes, -} + UnknownMessageTypeUrl + { url: String } + | e | { format_args!("unknown type URL {0}", e.url) }, -impl Kind { - pub fn context(self, source: impl Into) -> Context { - Context::new(self, Some(source.into())) + MalformedMessageBytes + [ TraceError ] + | _ | { "the message is malformed and cannot be decoded" }, } } diff --git a/modules/src/ics26_routing/handler.rs b/modules/src/ics26_routing/handler.rs index caea813c8b..65e9673a68 100644 --- a/modules/src/ics26_routing/handler.rs +++ b/modules/src/ics26_routing/handler.rs @@ -7,7 +7,7 @@ use crate::ics03_connection::handler::dispatch as ics3_msg_dispatcher; use crate::ics04_channel::handler::channel_dispatch as ics4_msg_dispatcher; use crate::ics04_channel::handler::packet_dispatch as ics04_packet_msg_dispatcher; use crate::ics26_routing::context::Ics26Context; -use crate::ics26_routing::error::{Error, Kind}; +use crate::ics26_routing::error::Error; use crate::ics26_routing::msgs::Ics26Envelope::{ self, Ics20Msg, Ics2Msg, Ics3Msg, Ics4ChannelMsg, Ics4PacketMsg, }; @@ -16,8 +16,6 @@ use crate::{events::IbcEvent, handler::HandlerOutput}; /// Mimics the DeliverTx ABCI interface, but a slightly lower level. No need for authentication /// info or signature checks here. /// Returns a vector of all events that got generated as a byproduct of processing `messages`. -/// -/// See pub fn deliver(ctx: &mut Ctx, messages: Vec) -> Result, Error> where Ctx: Ics26Context, @@ -57,12 +55,11 @@ where { let output = match msg { Ics2Msg(msg) => { - let handler_output = - ics2_msg_dispatcher(ctx, msg).map_err(|e| Kind::HandlerRaisedError.context(e))?; + let handler_output = ics2_msg_dispatcher(ctx, msg).map_err(Error::ics02_client)?; // Apply the result to the context (host chain store). ctx.store_client_result(handler_output.result) - .map_err(|e| Kind::KeeperRaisedError.context(e))?; + .map_err(Error::ics02_client)?; HandlerOutput::builder() .with_log(handler_output.log) @@ -71,12 +68,11 @@ where } Ics3Msg(msg) => { - let handler_output = - ics3_msg_dispatcher(ctx, msg).map_err(|e| Kind::HandlerRaisedError.context(e))?; + let handler_output = ics3_msg_dispatcher(ctx, msg).map_err(Error::ics03_connection)?; // Apply any results to the host chain store. ctx.store_connection_result(handler_output.result) - .map_err(|e| Kind::KeeperRaisedError.context(e))?; + .map_err(Error::ics03_connection)?; HandlerOutput::builder() .with_log(handler_output.log) @@ -85,12 +81,11 @@ where } Ics4ChannelMsg(msg) => { - let handler_output = - ics4_msg_dispatcher(ctx, msg).map_err(|e| Kind::HandlerRaisedError.context(e))?; + let handler_output = ics4_msg_dispatcher(ctx, msg).map_err(Error::ics04_channel)?; // Apply any results to the host chain store. ctx.store_channel_result(handler_output.result) - .map_err(|e| Kind::KeeperRaisedError.context(e))?; + .map_err(Error::ics04_channel)?; HandlerOutput::builder() .with_log(handler_output.log) @@ -100,11 +95,11 @@ where Ics20Msg(msg) => { let handler_output = - ics20_msg_dispatcher(ctx, msg).map_err(|e| Kind::HandlerRaisedError.context(e))?; + ics20_msg_dispatcher(ctx, msg).map_err(Error::ics20_fungible_token_transfer)?; // Apply any results to the host chain store. ctx.store_packet_result(handler_output.result) - .map_err(|e| Kind::KeeperRaisedError.context(e))?; + .map_err(Error::ics04_channel)?; HandlerOutput::builder() .with_log(handler_output.log) @@ -113,12 +108,12 @@ where } Ics4PacketMsg(msg) => { - let handler_output = ics04_packet_msg_dispatcher(ctx, msg) - .map_err(|e| Kind::HandlerRaisedError.context(e))?; + let handler_output = + ics04_packet_msg_dispatcher(ctx, msg).map_err(Error::ics04_channel)?; // Apply any results to the host chain store. ctx.store_packet_result(handler_output.result) - .map_err(|e| Kind::KeeperRaisedError.context(e))?; + .map_err(Error::ics04_channel)?; HandlerOutput::builder() .with_log(handler_output.log) diff --git a/modules/src/ics26_routing/msgs.rs b/modules/src/ics26_routing/msgs.rs index bd8bc2a7e1..1ea271fa73 100644 --- a/modules/src/ics26_routing/msgs.rs +++ b/modules/src/ics26_routing/msgs.rs @@ -10,7 +10,7 @@ use crate::ics04_channel::msgs::{ acknowledgement, chan_close_confirm, chan_close_init, chan_open_ack, chan_open_confirm, chan_open_init, chan_open_try, recv_packet, timeout, timeout_on_close, ChannelMsg, PacketMsg, }; -use crate::ics26_routing::error::{Error, Kind}; +use crate::ics26_routing::error::Error; use tendermint_proto::Protobuf; /// Enumeration of all messages that the local ICS26 module is capable of routing. @@ -32,38 +32,38 @@ impl TryFrom for Ics26Envelope { create_client::TYPE_URL => { // Pop out the message and then wrap it in the corresponding type. let domain_msg = create_client::MsgCreateAnyClient::decode_vec(&any_msg.value) - .map_err(|e| Kind::MalformedMessageBytes.context(e))?; + .map_err(Error::malformed_message_bytes)?; Ok(Ics26Envelope::Ics2Msg(ClientMsg::CreateClient(domain_msg))) } update_client::TYPE_URL => { let domain_msg = update_client::MsgUpdateAnyClient::decode_vec(&any_msg.value) - .map_err(|e| Kind::MalformedMessageBytes.context(e))?; + .map_err(Error::malformed_message_bytes)?; Ok(Ics26Envelope::Ics2Msg(ClientMsg::UpdateClient(domain_msg))) } upgrade_client::TYPE_URL => { let domain_msg = upgrade_client::MsgUpgradeAnyClient::decode_vec(&any_msg.value) - .map_err(|e| Kind::MalformedMessageBytes.context(e))?; + .map_err(Error::malformed_message_bytes)?; Ok(Ics26Envelope::Ics2Msg(ClientMsg::UpgradeClient(domain_msg))) } // ICS03 conn_open_init::TYPE_URL => { let domain_msg = conn_open_init::MsgConnectionOpenInit::decode_vec(&any_msg.value) - .map_err(|e| Kind::MalformedMessageBytes.context(e))?; + .map_err(Error::malformed_message_bytes)?; Ok(Ics26Envelope::Ics3Msg(ConnectionMsg::ConnectionOpenInit( domain_msg, ))) } conn_open_try::TYPE_URL => { let domain_msg = conn_open_try::MsgConnectionOpenTry::decode_vec(&any_msg.value) - .map_err(|e| Kind::MalformedMessageBytes.context(e))?; + .map_err(Error::malformed_message_bytes)?; Ok(Ics26Envelope::Ics3Msg(ConnectionMsg::ConnectionOpenTry( Box::new(domain_msg), ))) } conn_open_ack::TYPE_URL => { let domain_msg = conn_open_ack::MsgConnectionOpenAck::decode_vec(&any_msg.value) - .map_err(|e| Kind::MalformedMessageBytes.context(e))?; + .map_err(Error::malformed_message_bytes)?; Ok(Ics26Envelope::Ics3Msg(ConnectionMsg::ConnectionOpenAck( Box::new(domain_msg), ))) @@ -71,7 +71,7 @@ impl TryFrom for Ics26Envelope { conn_open_confirm::TYPE_URL => { let domain_msg = conn_open_confirm::MsgConnectionOpenConfirm::decode_vec(&any_msg.value) - .map_err(|e| Kind::MalformedMessageBytes.context(e))?; + .map_err(Error::malformed_message_bytes)?; Ok(Ics26Envelope::Ics3Msg( ConnectionMsg::ConnectionOpenConfirm(domain_msg), )) @@ -80,21 +80,21 @@ impl TryFrom for Ics26Envelope { // ICS04 channel messages chan_open_init::TYPE_URL => { let domain_msg = chan_open_init::MsgChannelOpenInit::decode_vec(&any_msg.value) - .map_err(|e| Kind::MalformedMessageBytes.context(e))?; + .map_err(Error::malformed_message_bytes)?; Ok(Ics26Envelope::Ics4ChannelMsg(ChannelMsg::ChannelOpenInit( domain_msg, ))) } chan_open_try::TYPE_URL => { let domain_msg = chan_open_try::MsgChannelOpenTry::decode_vec(&any_msg.value) - .map_err(|e| Kind::MalformedMessageBytes.context(e))?; + .map_err(Error::malformed_message_bytes)?; Ok(Ics26Envelope::Ics4ChannelMsg(ChannelMsg::ChannelOpenTry( domain_msg, ))) } chan_open_ack::TYPE_URL => { let domain_msg = chan_open_ack::MsgChannelOpenAck::decode_vec(&any_msg.value) - .map_err(|e| Kind::MalformedMessageBytes.context(e))?; + .map_err(Error::malformed_message_bytes)?; Ok(Ics26Envelope::Ics4ChannelMsg(ChannelMsg::ChannelOpenAck( domain_msg, ))) @@ -102,14 +102,14 @@ impl TryFrom for Ics26Envelope { chan_open_confirm::TYPE_URL => { let domain_msg = chan_open_confirm::MsgChannelOpenConfirm::decode_vec(&any_msg.value) - .map_err(|e| Kind::MalformedMessageBytes.context(e))?; + .map_err(Error::malformed_message_bytes)?; Ok(Ics26Envelope::Ics4ChannelMsg( ChannelMsg::ChannelOpenConfirm(domain_msg), )) } chan_close_init::TYPE_URL => { let domain_msg = chan_close_init::MsgChannelCloseInit::decode_vec(&any_msg.value) - .map_err(|e| Kind::MalformedMessageBytes.context(e))?; + .map_err(Error::malformed_message_bytes)?; Ok(Ics26Envelope::Ics4ChannelMsg(ChannelMsg::ChannelCloseInit( domain_msg, ))) @@ -117,7 +117,7 @@ impl TryFrom for Ics26Envelope { chan_close_confirm::TYPE_URL => { let domain_msg = chan_close_confirm::MsgChannelCloseConfirm::decode_vec(&any_msg.value) - .map_err(|e| Kind::MalformedMessageBytes.context(e))?; + .map_err(Error::malformed_message_bytes)?; Ok(Ics26Envelope::Ics4ChannelMsg( ChannelMsg::ChannelCloseConfirm(domain_msg), )) @@ -125,40 +125,39 @@ impl TryFrom for Ics26Envelope { // ICS20 - 04 - Send packet transfer::TYPE_URL => { let domain_msg = transfer::MsgTransfer::decode_vec(&any_msg.value) - .map_err(|e| Kind::MalformedMessageBytes.context(e))?; + .map_err(Error::malformed_message_bytes)?; Ok(Ics26Envelope::Ics20Msg(domain_msg)) } // ICS04 packet messages recv_packet::TYPE_URL => { let domain_msg = recv_packet::MsgRecvPacket::decode_vec(&any_msg.value) - .map_err(|e| Kind::MalformedMessageBytes.context(e))?; + .map_err(Error::malformed_message_bytes)?; Ok(Ics26Envelope::Ics4PacketMsg(PacketMsg::RecvPacket( domain_msg, ))) } acknowledgement::TYPE_URL => { let domain_msg = acknowledgement::MsgAcknowledgement::decode_vec(&any_msg.value) - .map_err(|e| Kind::MalformedMessageBytes.context(e))?; + .map_err(Error::malformed_message_bytes)?; Ok(Ics26Envelope::Ics4PacketMsg(PacketMsg::AckPacket( domain_msg, ))) } timeout::TYPE_URL => { let domain_msg = timeout::MsgTimeout::decode_vec(&any_msg.value) - .map_err(|e| Kind::MalformedMessageBytes.context(e))?; + .map_err(Error::malformed_message_bytes)?; Ok(Ics26Envelope::Ics4PacketMsg(PacketMsg::ToPacket( domain_msg, ))) } timeout_on_close::TYPE_URL => { let domain_msg = timeout_on_close::MsgTimeoutOnClose::decode_vec(&any_msg.value) - .map_err(|e| Kind::MalformedMessageBytes.context(e))?; + .map_err(Error::malformed_message_bytes)?; Ok(Ics26Envelope::Ics4PacketMsg(PacketMsg::ToClosePacket( domain_msg, ))) } - - _ => Err(Kind::UnknownMessageTypeUrl(any_msg.type_url).into()), + _ => Err(Error::unknown_message_type_url(any_msg.type_url)), } } } diff --git a/modules/src/lib.rs b/modules/src/lib.rs index e1cfdc510c..6feec7056e 100644 --- a/modules/src/lib.rs +++ b/modules/src/lib.rs @@ -37,7 +37,6 @@ pub mod query; pub mod signer; pub mod timestamp; pub mod tx_msg; -pub mod utils; pub mod ics02_client; pub mod ics03_connection; diff --git a/modules/src/mock/client_def.rs b/modules/src/mock/client_def.rs index 4f4befa430..d33fe4da8b 100644 --- a/modules/src/mock/client_def.rs +++ b/modules/src/mock/client_def.rs @@ -3,6 +3,7 @@ use ibc_proto::ibc::core::commitment::v1::MerkleProof; use crate::ics02_client::client_consensus::AnyConsensusState; use crate::ics02_client::client_def::ClientDef; use crate::ics02_client::client_state::AnyClientState; +use crate::ics02_client::error::Error; use crate::ics03_connection::connection::ConnectionEnd; use crate::ics04_channel::channel::ChannelEnd; use crate::ics04_channel::packet::Sequence; @@ -26,11 +27,12 @@ impl ClientDef for MockClient { &self, client_state: Self::ClientState, header: Self::Header, - ) -> Result<(Self::ClientState, Self::ConsensusState), Box> { + ) -> Result<(Self::ClientState, Self::ConsensusState), Error> { if client_state.latest_height() >= header.height() { - return Err( - "received header height is lower than (or equal to) client latest height".into(), - ); + return Err(Error::low_header_height( + header.height(), + client_state.latest_height(), + )); } Ok((MockClientState(header), MockConsensusState::new(header))) } @@ -44,7 +46,7 @@ impl ClientDef for MockClient { client_id: &ClientId, _consensus_height: Height, _expected_consensus_state: &AnyConsensusState, - ) -> Result<(), Box> { + ) -> Result<(), Error> { let client_prefixed_path = Path::ClientConsensusState { client_id: client_id.clone(), epoch: height.revision_number, @@ -52,7 +54,8 @@ impl ClientDef for MockClient { } .to_string(); - let _path = apply_prefix(prefix, vec![client_prefixed_path])?; + let _path = + apply_prefix(prefix, vec![client_prefixed_path]).map_err(Error::empty_prefix)?; // TODO - add ctx to all client verification functions // let cs = ctx.fetch_self_consensus_state(height); @@ -70,7 +73,7 @@ impl ClientDef for MockClient { _proof: &CommitmentProofBytes, _connection_id: Option<&ConnectionId>, _expected_connection_end: &ConnectionEnd, - ) -> Result<(), Box> { + ) -> Result<(), Error> { Ok(()) } @@ -83,7 +86,7 @@ impl ClientDef for MockClient { _port_id: &PortId, _channel_id: &ChannelId, _expected_channel_end: &ChannelEnd, - ) -> Result<(), Box> { + ) -> Result<(), Error> { Ok(()) } @@ -96,7 +99,7 @@ impl ClientDef for MockClient { _client_id: &ClientId, _proof: &CommitmentProofBytes, _expected_client_state: &AnyClientState, - ) -> Result<(), Box> { + ) -> Result<(), Error> { Ok(()) } @@ -109,7 +112,7 @@ impl ClientDef for MockClient { _channel_id: &ChannelId, _seq: &Sequence, _data: String, - ) -> Result<(), Box> { + ) -> Result<(), Error> { Ok(()) } @@ -122,7 +125,7 @@ impl ClientDef for MockClient { _channel_id: &ChannelId, _seq: &Sequence, _data: Vec, - ) -> Result<(), Box> { + ) -> Result<(), Error> { Ok(()) } @@ -134,7 +137,7 @@ impl ClientDef for MockClient { _port_id: &PortId, _channel_id: &ChannelId, _seq: &Sequence, - ) -> Result<(), Box> { + ) -> Result<(), Error> { Ok(()) } @@ -146,7 +149,7 @@ impl ClientDef for MockClient { _port_id: &PortId, _channel_id: &ChannelId, _seq: &Sequence, - ) -> Result<(), Box> { + ) -> Result<(), Error> { Ok(()) } fn verify_upgrade_and_update_state( @@ -155,7 +158,7 @@ impl ClientDef for MockClient { consensus_state: &Self::ConsensusState, _proof_upgrade_client: MerkleProof, _proof_upgrade_consensus_state: MerkleProof, - ) -> Result<(Self::ClientState, Self::ConsensusState), Box> { + ) -> Result<(Self::ClientState, Self::ConsensusState), Error> { Ok((*client_state, consensus_state.clone())) } } diff --git a/modules/src/mock/client_state.rs b/modules/src/mock/client_state.rs index 5c86f9259a..2f2cc35451 100644 --- a/modules/src/mock/client_state.rs +++ b/modules/src/mock/client_state.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::convert::Infallible; use std::convert::{TryFrom, TryInto}; use std::time::Duration; @@ -12,7 +13,6 @@ use crate::ics02_client::client_consensus::{AnyConsensusState, ConsensusState}; use crate::ics02_client::client_state::{AnyClientState, ClientState}; use crate::ics02_client::client_type::ClientType; use crate::ics02_client::error::Error; -use crate::ics02_client::error::Kind as ClientKind; use crate::ics23_commitment::commitment::CommitmentRoot; use crate::ics24_host::identifier::ChainId; use crate::mock::header::MockHeader; @@ -133,9 +133,7 @@ impl TryFrom for MockConsensusState { type Error = Error; fn try_from(raw: RawMockConsensusState) -> Result { - let raw_header = raw - .header - .ok_or_else(|| ClientKind::InvalidRawConsensusState.context("missing header"))?; + let raw_header = raw.header.ok_or_else(Error::missing_raw_consensus_state)?; Ok(Self { header: MockHeader::try_from(raw_header)?, @@ -162,6 +160,8 @@ impl From for AnyConsensusState { } impl ConsensusState for MockConsensusState { + type Error = Infallible; + fn client_type(&self) -> ClientType { ClientType::Mock } @@ -170,7 +170,7 @@ impl ConsensusState for MockConsensusState { &self.root } - fn validate_basic(&self) -> Result<(), Box> { + fn validate_basic(&self) -> Result<(), Infallible> { Ok(()) } diff --git a/modules/src/mock/context.rs b/modules/src/mock/context.rs index 3713b86f6a..c7003bfbe6 100644 --- a/modules/src/mock/context.rs +++ b/modules/src/mock/context.rs @@ -2,7 +2,6 @@ use std::cmp::min; use std::collections::HashMap; -use std::error::Error; use prost_types::Any; use sha2::Digest; @@ -20,13 +19,13 @@ use crate::ics03_connection::context::{ConnectionKeeper, ConnectionReader}; use crate::ics03_connection::error::Error as Ics3Error; use crate::ics04_channel::channel::ChannelEnd; use crate::ics04_channel::context::{ChannelKeeper, ChannelReader}; -use crate::ics04_channel::error::{Error as Ics4Error, Kind as Ics4Kind}; +use crate::ics04_channel::error::Error as ChannelError; use crate::ics04_channel::packet::{Receipt, Sequence}; use crate::ics05_port::capabilities::Capability; use crate::ics05_port::context::PortReader; use crate::ics07_tendermint::client_state::test_util::get_dummy_tendermint_client_state; use crate::ics18_relayer::context::Ics18Context; -use crate::ics18_relayer::error::{Error as Ics18Error, Kind as Ics18ErrorKind}; +use crate::ics18_relayer::error::Error as Ics18Error; use crate::ics23_commitment::commitment::CommitmentPrefix; use crate::ics24_host::identifier::{ChainId, ChannelId, ClientId, ConnectionId, PortId}; use crate::ics26_routing::context::Ics26Context; @@ -382,17 +381,17 @@ impl MockContext { /// Alternative method to `Ics18Context::send` that does not exercise any serialization. /// Used in testing the Ics18 algorithms, hence this may return a Ics18Error. pub fn deliver(&mut self, msg: Ics26Envelope) -> Result<(), Ics18Error> { - dispatch(self, msg).map_err(|e| Ics18ErrorKind::TransactionFailed.context(e))?; + dispatch(self, msg).map_err(Ics18Error::transaction_failed)?; // Create a new block. self.advance_host_chain_height(); Ok(()) } /// Validates this context. Should be called after the context is mutated by a test. - pub fn validate(&self) -> Result<(), Box> { + pub fn validate(&self) -> Result<(), String> { // Check that the number of entries is not higher than window size. if self.history.len() > self.max_history_size { - return Err("too many entries".to_string().into()); + return Err("too many entries".to_string()); } // Check the content of the history. @@ -401,7 +400,7 @@ impl MockContext { let lh = &self.history[self.history.len() - 1]; // Check latest is properly updated with highest header height. if lh.height() != self.latest_height { - return Err("latest height is not updated".to_string().into()); + return Err("latest height is not updated".to_string()); } } @@ -410,7 +409,7 @@ impl MockContext { let ph = &self.history[i - 1]; let h = &self.history[i]; if ph.height().increment() != h.height() { - return Err("headers in history not sequential".to_string().into()); + return Err("headers in history not sequential".to_string()); } } Ok(()) @@ -471,17 +470,17 @@ impl ChannelReader for MockContext { ClientReader::consensus_state(self, client_id, height) } - fn authenticated_capability(&self, port_id: &PortId) -> Result { + fn authenticated_capability(&self, port_id: &PortId) -> Result { let cap = PortReader::lookup_module_by_port(self, port_id); match cap { Some(key) => { if !PortReader::authenticate(self, &key, port_id) { - Err(Ics4Kind::InvalidPortCapability.into()) + Err(ChannelError::invalid_port_capability()) } else { Ok(key) } } - None => Err(Ics4Kind::NoPortCapability(port_id.clone()).into()), + None => Err(ChannelError::no_port_capability(port_id.clone())), } } @@ -534,7 +533,7 @@ impl ChannelKeeper for MockContext { timeout_timestamp: Timestamp, timeout_height: Height, data: Vec, - ) -> Result<(), Ics4Error> { + ) -> Result<(), ChannelError> { let input = format!("{:?},{:?},{:?}", timeout_timestamp, timeout_height, data); self.packet_commitment .insert(key, ChannelReader::hash(self, input)); @@ -545,7 +544,7 @@ impl ChannelKeeper for MockContext { &mut self, key: (PortId, ChannelId, Sequence), ack: Vec, - ) -> Result<(), Ics4Error> { + ) -> Result<(), ChannelError> { let input = format!("{:?}", ack); self.packet_acknowledgement .insert(key, ChannelReader::hash(self, input)); @@ -555,7 +554,7 @@ impl ChannelKeeper for MockContext { fn delete_packet_acknowledgement( &mut self, key: (PortId, ChannelId, Sequence), - ) -> Result<(), Ics4Error> { + ) -> Result<(), ChannelError> { self.packet_acknowledgement.remove(&key); Ok(()) } @@ -564,7 +563,7 @@ impl ChannelKeeper for MockContext { &mut self, cid: ConnectionId, port_channel_id: &(PortId, ChannelId), - ) -> Result<(), Ics4Error> { + ) -> Result<(), ChannelError> { self.connection_channels .entry(cid) .or_insert_with(Vec::new) @@ -576,7 +575,7 @@ impl ChannelKeeper for MockContext { &mut self, port_channel_id: (PortId, ChannelId), channel_end: &ChannelEnd, - ) -> Result<(), Ics4Error> { + ) -> Result<(), ChannelError> { self.channels.insert(port_channel_id, channel_end.clone()); Ok(()) } @@ -585,7 +584,7 @@ impl ChannelKeeper for MockContext { &mut self, port_channel_id: (PortId, ChannelId), seq: Sequence, - ) -> Result<(), Ics4Error> { + ) -> Result<(), ChannelError> { self.next_sequence_send.insert(port_channel_id, seq); Ok(()) } @@ -594,7 +593,7 @@ impl ChannelKeeper for MockContext { &mut self, port_channel_id: (PortId, ChannelId), seq: Sequence, - ) -> Result<(), Ics4Error> { + ) -> Result<(), ChannelError> { self.next_sequence_recv.insert(port_channel_id, seq); Ok(()) } @@ -603,7 +602,7 @@ impl ChannelKeeper for MockContext { &mut self, port_channel_id: (PortId, ChannelId), seq: Sequence, - ) -> Result<(), Ics4Error> { + ) -> Result<(), ChannelError> { self.next_sequence_ack.insert(port_channel_id, seq); Ok(()) } @@ -615,7 +614,7 @@ impl ChannelKeeper for MockContext { fn delete_packet_commitment( &mut self, key: (PortId, ChannelId, Sequence), - ) -> Result<(), Ics4Error> { + ) -> Result<(), ChannelError> { self.packet_commitment.remove(&key); Ok(()) } @@ -624,7 +623,7 @@ impl ChannelKeeper for MockContext { &mut self, key: (PortId, ChannelId, Sequence), receipt: Receipt, - ) -> Result<(), Ics4Error> { + ) -> Result<(), ChannelError> { self.packet_receipt.insert(key, receipt); Ok(()) } @@ -799,8 +798,7 @@ impl Ics18Context for MockContext { fn send(&mut self, msgs: Vec) -> Result, Ics18Error> { // Forward call to Ics26 delivery method. - let events = - deliver(self, msgs).map_err(|e| Ics18ErrorKind::TransactionFailed.context(e))?; + let events = deliver(self, msgs).map_err(Ics18Error::transaction_failed)?; self.advance_host_chain_height(); // Advance chain height Ok(events) diff --git a/modules/src/mock/header.rs b/modules/src/mock/header.rs index 04269a2946..cd926383a2 100644 --- a/modules/src/mock/header.rs +++ b/modules/src/mock/header.rs @@ -1,4 +1,4 @@ -use std::convert::{TryFrom, TryInto}; +use std::convert::TryFrom; use serde_derive::{Deserialize, Serialize}; use tendermint_proto::Protobuf; @@ -7,7 +7,7 @@ use ibc_proto::ibc::mock::Header as RawMockHeader; use crate::ics02_client::client_consensus::AnyConsensusState; use crate::ics02_client::client_type::ClientType; -use crate::ics02_client::error::{self, Error}; +use crate::ics02_client::error::Error; use crate::ics02_client::header::AnyHeader; use crate::ics02_client::header::Header; use crate::mock::client_state::MockConsensusState; @@ -27,13 +27,10 @@ impl TryFrom for MockHeader { fn try_from(raw: RawMockHeader) -> Result { Ok(MockHeader { - height: raw - .height - .ok_or_else(|| error::Kind::InvalidRawHeader.context("missing height in header"))? - .try_into() - .map_err(|e| error::Kind::InvalidRawHeader.context(e))?, + height: raw.height.ok_or_else(Error::missing_raw_header)?.into(), + timestamp: Timestamp::from_nanoseconds(raw.timestamp) - .map_err(|_| error::Kind::InvalidPacketTimestamp)?, + .map_err(Error::invalid_packet_timestamp)?, }) } } diff --git a/modules/src/mock/misbehaviour.rs b/modules/src/mock/misbehaviour.rs index edc8e5f6e2..d35fbf6cea 100644 --- a/modules/src/mock/misbehaviour.rs +++ b/modules/src/mock/misbehaviour.rs @@ -4,7 +4,7 @@ use tendermint_proto::Protobuf; use ibc_proto::ibc::mock::Misbehaviour as RawMisbehaviour; -use crate::ics02_client::error::{self, Error}; +use crate::ics02_client::error::Error; use crate::ics02_client::misbehaviour::AnyMisbehaviour; use crate::ics24_host::identifier::ClientId; use crate::mock::header::MockHeader; @@ -41,11 +41,11 @@ impl TryFrom for Misbehaviour { client_id: Default::default(), header1: raw .header1 - .ok_or_else(|| error::Kind::InvalidRawMisbehaviour.context("missing header1"))? + .ok_or_else(Error::missing_raw_misbehaviour)? .try_into()?, header2: raw .header2 - .ok_or_else(|| error::Kind::InvalidRawMisbehaviour.context("missing header2"))? + .ok_or_else(Error::missing_raw_misbehaviour)? .try_into()?, }) } diff --git a/modules/src/proofs.rs b/modules/src/proofs.rs index d0fbfbbb28..5163f9cc85 100644 --- a/modules/src/proofs.rs +++ b/modules/src/proofs.rs @@ -2,6 +2,17 @@ use serde::Serialize; use crate::ics23_commitment::commitment::CommitmentProofBytes; use crate::Height; +use flex_error::define_error; + +define_error! { + #[derive(Debug, PartialEq, Eq)] + ProofError { + ZeroHeight + | _ | { format_args!("proof height cannot be zero") }, + EmptyProof + | _ | { format_args!("proof cannot be empty") }, + } +} /// Structure comprising proofs in a message. Proofs are typically present in messages for /// handshake protocols, e.g., ICS3 connection (open) handshake or ICS4 channel (open and close) @@ -25,13 +36,13 @@ impl Proofs { consensus_proof: Option, other_proof: Option, height: Height, - ) -> Result { + ) -> Result { if height.is_zero() { - return Err("Proofs height cannot be zero".to_string()); + return Err(ProofError::zero_height()); } if object_proof.is_empty() { - return Err("Object proof cannot be empty".to_string()); + return Err(ProofError::empty_proof()); } Ok(Self { @@ -76,12 +87,12 @@ impl ConsensusProof { pub fn new( consensus_proof: CommitmentProofBytes, consensus_height: Height, - ) -> Result { + ) -> Result { if consensus_height.is_zero() { - return Err("Consensus height cannot be zero".to_string()); + return Err(ProofError::zero_height()); } if consensus_proof.is_empty() { - return Err("Proof cannot be empty".to_string()); + return Err(ProofError::empty_proof()); } Ok(Self { diff --git a/modules/src/timestamp.rs b/modules/src/timestamp.rs index 773eda3890..35487b09e4 100644 --- a/modules/src/timestamp.rs +++ b/modules/src/timestamp.rs @@ -6,8 +6,8 @@ use std::str::FromStr; use std::time::Duration; use chrono::{offset::Utc, DateTime, TimeZone}; +use flex_error::{define_error, TraceError}; use serde_derive::{Deserialize, Serialize}; -use thiserror::Error; pub const ZERO_DURATION: Duration = Duration::from_secs(0); @@ -125,9 +125,12 @@ impl Display for Timestamp { } } -#[derive(Clone, Debug, Error, PartialEq, Eq)] -#[error("Timestamp overflow when modifying with duration")] -pub struct TimestampOverflowError; +define_error! { + TimestampOverflowError { + TimestampOverflow + |_| { "Timestamp overflow when modifying with duration" } + } +} impl Add for Timestamp { type Output = Result; @@ -135,8 +138,8 @@ impl Add for Timestamp { fn add(self, duration: Duration) -> Result { match self.as_datetime() { Some(datetime) => { - let duration2 = - chrono::Duration::from_std(duration).map_err(|_| TimestampOverflowError)?; + let duration2 = chrono::Duration::from_std(duration) + .map_err(|_| TimestampOverflowError::timestamp_overflow())?; Ok(Self::from_datetime(datetime + duration2)) } None => Ok(self), @@ -150,8 +153,8 @@ impl Sub for Timestamp { fn sub(self, duration: Duration) -> Result { match self.as_datetime() { Some(datetime) => { - let duration2 = - chrono::Duration::from_std(duration).map_err(|_| TimestampOverflowError)?; + let duration2 = chrono::Duration::from_std(duration) + .map_err(|_| TimestampOverflowError::timestamp_overflow())?; Ok(Self::from_datetime(datetime - duration2)) } None => Ok(self), @@ -159,25 +162,25 @@ impl Sub for Timestamp { } } -pub type ParseTimestampError = anomaly::Error; +define_error! { + ParseTimestampError { + ParseInt + [ TraceError ] + | _ | { "error parsing integer from string"}, -#[derive(Clone, Debug, Error, PartialEq, Eq)] -pub enum ParseTimestampErrorKind { - #[error("Error parsing integer from string: {0}")] - ParseIntError(ParseIntError), - - #[error("Error converting from u64 to i64: {0}")] - TryFromIntError(TryFromIntError), + TryFromInt + [ TraceError ] + | _ | { "error converting from u64 to i64" }, + } } impl FromStr for Timestamp { type Err = ParseTimestampError; fn from_str(s: &str) -> Result { - let seconds = u64::from_str(s).map_err(ParseTimestampErrorKind::ParseIntError)?; + let seconds = u64::from_str(s).map_err(ParseTimestampError::parse_int)?; - Timestamp::from_nanoseconds(seconds) - .map_err(|err| ParseTimestampErrorKind::TryFromIntError(err).into()) + Timestamp::from_nanoseconds(seconds).map_err(ParseTimestampError::try_from_int) } } diff --git a/modules/src/utils.rs b/modules/src/utils.rs deleted file mode 100644 index 4eb1a6ff69..0000000000 --- a/modules/src/utils.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! In-house alternative to [`unwrap-infallible`]. -//! -//! Enables the following pattern in our codebase: -//! -//! ``` -//! use std::convert::TryInto; -//! use ibc_proto::ibc::core::client::v1::Height as ProtoHeight; -//! use ibc::Height; -//! use ibc::utils::UnwrapInfallible; -//! -//! let ph = ProtoHeight { revision_height: 1, revision_number: 54 }; -//! let h: Height = ph -//! .try_into() // returns a `Result` -//! .unwrap_infallible(); // converts safely into `Height` -//! ``` -//! -//! [`unwrap-infallible`]: [https://crates.io/crates/unwrap-infallible - -use std::convert::Infallible; - -// TODO: Remove this trait and its associated impl once `into_ok` stabilizes: -// https://github.com/rust-lang/rust/issues/61695 -pub trait UnwrapInfallible { - type Output; - - fn unwrap_infallible(self) -> Self::Output; -} - -impl UnwrapInfallible for Result { - type Output = A; - - fn unwrap_infallible(self) -> Self::Output { - match self { - Ok(a) => a, - Err(_) => unreachable!(), - } - } -} - -#[test] -fn test() { - let x: Result = Ok(42); - assert_eq!(x.unwrap_infallible(), 42); -} diff --git a/modules/tests/mbt.rs b/modules/tests/mbt.rs index d93ca72b30..97903618f7 100644 --- a/modules/tests/mbt.rs +++ b/modules/tests/mbt.rs @@ -1,5 +1,8 @@ mod runner; +use modelator::{run, TestError}; +use runner::{step::Step, IbcTestRunner}; + #[test] fn mbt() { // we should be able to just return the `Result` once the following @@ -9,12 +12,12 @@ fn mbt() { } } -fn run_tests() -> Result<(), Box> { +fn run_tests() -> Result<(), TestError> { // run the test let tla_tests_file = "tests/support/model_based/IBCTests.tla"; let tla_config_file = "tests/support/model_based/IBCTests.cfg"; - let runner = runner::IbcTestRunner::new(); - modelator::run(tla_tests_file, tla_config_file, runner)?; + let runner = IbcTestRunner::new(); + run(tla_tests_file, tla_config_file, runner)?; Ok(()) } diff --git a/modules/tests/runner/mod.rs b/modules/tests/runner/mod.rs index 4fbdc41f84..0571b8807f 100644 --- a/modules/tests/runner/mod.rs +++ b/modules/tests/runner/mod.rs @@ -1,21 +1,20 @@ pub mod step; use std::collections::HashMap; -use std::error::Error; -use std::fmt::{Debug, Display}; +use std::fmt::Debug; use std::time::Duration; use ibc::ics02_client::client_consensus::AnyConsensusState; use ibc::ics02_client::client_state::AnyClientState; use ibc::ics02_client::client_type::ClientType; use ibc::ics02_client::context::ClientReader; -use ibc::ics02_client::error::Kind as Ics02ErrorKind; +use ibc::ics02_client::error as client_error; use ibc::ics02_client::header::AnyHeader; use ibc::ics02_client::msgs::create_client::MsgCreateAnyClient; use ibc::ics02_client::msgs::update_client::MsgUpdateAnyClient; use ibc::ics02_client::msgs::ClientMsg; use ibc::ics03_connection::connection::{Counterparty, State as ConnectionState}; -use ibc::ics03_connection::error::Kind as Ics03ErrorKind; +use ibc::ics03_connection::error as connection_error; use ibc::ics03_connection::msgs::conn_open_ack::MsgConnectionOpenAck; use ibc::ics03_connection::msgs::conn_open_confirm::MsgConnectionOpenConfirm; use ibc::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; @@ -24,10 +23,10 @@ use ibc::ics03_connection::msgs::ConnectionMsg; use ibc::ics03_connection::version::Version; use ibc::ics04_channel::context::ChannelReader; use ibc::ics18_relayer::context::Ics18Context; -use ibc::ics18_relayer::error::{Error as Ics18Error, Kind as Ics18ErrorKind}; +use ibc::ics18_relayer::error as relayer_error; use ibc::ics23_commitment::commitment::{CommitmentPrefix, CommitmentProofBytes}; use ibc::ics24_host::identifier::{ChainId, ClientId, ConnectionId}; -use ibc::ics26_routing::error::{Error as Ics26Error, Kind as Ics26ErrorKind}; +use ibc::ics26_routing::error as routing_error; use ibc::ics26_routing::msgs::Ics26Envelope; use ibc::mock::client_state::{MockClientState, MockConsensusState}; use ibc::mock::context::MockContext; @@ -83,31 +82,39 @@ impl IbcTestRunner { .expect("chain context should have been initialized") } - pub fn extract_handler_error_kind(ics18_result: Result<(), Ics18Error>) -> K - where - K: Clone + Debug + Display + Into + 'static, - { + pub fn extract_ics02_error_kind( + ics18_result: Result<(), relayer_error::Error>, + ) -> client_error::ErrorDetail { let ics18_error = ics18_result.expect_err("ICS18 error expected"); - assert!(matches!( - ics18_error.kind(), - Ics18ErrorKind::TransactionFailed - )); - let ics26_error = ics18_error - .source() - .expect("expected source in ICS18 error") - .downcast_ref::() - .expect("ICS18 source should be an ICS26 error"); - assert!(matches!( - ics26_error.kind(), - Ics26ErrorKind::HandlerRaisedError, - )); - ics26_error - .source() - .expect("expected source in ICS26 error") - .downcast_ref::>() - .expect("ICS26 source should be an handler error") - .kind() - .clone() + match ics18_error.0 { + relayer_error::ErrorDetail::TransactionFailed(e) => match e.source { + routing_error::ErrorDetail::Ics02Client(e) => e.source, + e => { + panic!("Expected Ics02Client error, instead got {:?}", e); + } + }, + e => { + panic!("Expected TransactionFailed error, instead got {:?}", e); + } + } + } + + pub fn extract_ics03_error_kind( + ics18_result: Result<(), relayer_error::Error>, + ) -> connection_error::ErrorDetail { + let ics18_error = ics18_result.expect_err("ICS18 error expected"); + + match ics18_error.0 { + relayer_error::ErrorDetail::TransactionFailed(e) => match e.source { + routing_error::ErrorDetail::Ics03Connection(e) => e.source, + e => { + panic!("Expected Ics02Client error, instead got {:?}", e); + } + }, + e => { + panic!("Expected TransactionFailed error, instead got {:?}", e); + } + } } pub fn chain_id(chain_id: String) -> ChainId { @@ -294,7 +301,7 @@ impl IbcTestRunner { }) } - pub fn apply(&mut self, action: Action) -> Result<(), Ics18Error> { + pub fn apply(&mut self, action: Action) -> Result<(), relayer_error::Error> { match action { Action::None => panic!("unexpected action type"), Action::Ics02CreateClient { @@ -451,43 +458,43 @@ impl modelator::runner::TestRunner for IbcTestRunner { ActionOutcome::Ics02CreateOk => result.is_ok(), ActionOutcome::Ics02UpdateOk => result.is_ok(), ActionOutcome::Ics02ClientNotFound => matches!( - Self::extract_handler_error_kind::(result), - Ics02ErrorKind::ClientNotFound(_) + Self::extract_ics02_error_kind(result), + client_error::ErrorDetail::ClientNotFound(_) ), ActionOutcome::Ics02HeaderVerificationFailure => matches!( - Self::extract_handler_error_kind::(result), - Ics02ErrorKind::HeaderVerificationFailure + Self::extract_ics02_error_kind(result), + client_error::ErrorDetail::HeaderVerificationFailure(_) ), ActionOutcome::Ics03ConnectionOpenInitOk => result.is_ok(), ActionOutcome::Ics03MissingClient => matches!( - Self::extract_handler_error_kind::(result), - Ics03ErrorKind::MissingClient(_) + Self::extract_ics03_error_kind(result), + connection_error::ErrorDetail::MissingClient(_) ), ActionOutcome::Ics03ConnectionOpenTryOk => result.is_ok(), ActionOutcome::Ics03InvalidConsensusHeight => matches!( - Self::extract_handler_error_kind::(result), - Ics03ErrorKind::InvalidConsensusHeight(_, _) + Self::extract_ics03_error_kind(result), + connection_error::ErrorDetail::InvalidConsensusHeight(_) ), ActionOutcome::Ics03ConnectionNotFound => matches!( - Self::extract_handler_error_kind::(result), - Ics03ErrorKind::ConnectionNotFound(_) + Self::extract_ics03_error_kind(result), + connection_error::ErrorDetail::ConnectionNotFound(_) ), ActionOutcome::Ics03ConnectionMismatch => matches!( - Self::extract_handler_error_kind::(result), - Ics03ErrorKind::ConnectionMismatch(_) + Self::extract_ics03_error_kind(result), + connection_error::ErrorDetail::ConnectionMismatch(_) ), ActionOutcome::Ics03MissingClientConsensusState => matches!( - Self::extract_handler_error_kind::(result), - Ics03ErrorKind::MissingClientConsensusState(_, _) + Self::extract_ics03_error_kind(result), + connection_error::ErrorDetail::MissingClientConsensusState(_) ), ActionOutcome::Ics03InvalidProof => matches!( - Self::extract_handler_error_kind::(result), - Ics03ErrorKind::InvalidProof + Self::extract_ics03_error_kind(result), + connection_error::ErrorDetail::InvalidProof(_) ), ActionOutcome::Ics03ConnectionOpenAckOk => result.is_ok(), ActionOutcome::Ics03UninitializedConnection => matches!( - Self::extract_handler_error_kind::(result), - Ics03ErrorKind::UninitializedConnection(_) + Self::extract_ics03_error_kind(result), + connection_error::ErrorDetail::UninitializedConnection(_) ), ActionOutcome::Ics03ConnectionOpenConfirmOk => result.is_ok(), }; diff --git a/proto/Cargo.toml b/proto/Cargo.toml index e54d1f352e..9dd746d762 100644 --- a/proto/Cargo.toml +++ b/proto/Cargo.toml @@ -24,9 +24,7 @@ all-features = true [dependencies] prost = "0.7" prost-types = "0.7" -anomaly = "0.2" bytes = "1.0" -thiserror = "1.0" tonic = "0.4" [dependencies.tendermint-proto] diff --git a/proto/src/lib.rs b/proto/src/lib.rs index 167ba3d922..f7d49ff64f 100644 --- a/proto/src/lib.rs +++ b/proto/src/lib.rs @@ -5,6 +5,7 @@ #![deny(warnings, trivial_casts, trivial_numeric_casts, unused_import_braces)] #![allow(clippy::large_enum_variant)] +#![allow(rustdoc::bare_urls)] #![forbid(unsafe_code)] #![doc(html_root_url = "https://docs.rs/ibc-proto/0.7.0")] diff --git a/relayer-cli/Cargo.toml b/relayer-cli/Cargo.toml index 2208e74664..22f990abb1 100644 --- a/relayer-cli/Cargo.toml +++ b/relayer-cli/Cargo.toml @@ -17,7 +17,11 @@ description = """ name = "hermes" [features] -default = ["telemetry"] +default = ["telemetry", "std", "eyre_tracer"] +std = [ + "flex-error/std" +] +eyre_tracer = ["flex-error/eyre_tracer"] profiling = ["ibc-relayer/profiling"] telemetry = ["ibc-relayer/telemetry", "ibc-telemetry"] @@ -27,10 +31,8 @@ ibc-relayer = { version = "0.6.1", path = "../relayer" } ibc-proto = { version = "0.9.0", path = "../proto" } ibc-telemetry = { version = "0.6.1", path = "../telemetry", optional = true } -anomaly = "0.2.0" gumdrop = { version = "0.7", features = ["default_expr"] } serde = { version = "1", features = ["serde_derive"] } -thiserror = "1" tokio = { version = "1.0", features = ["full"] } tracing = "0.1.26" tracing-subscriber = "0.2.19" @@ -47,6 +49,7 @@ subtle-encoding = "0.5" dirs-next = "2.0.0" itertools = "0.10.1" atty = "0.2.14" +flex-error = { version = "0.4.1", default-features = false } signal-hook = "0.3.9" [dependencies.tendermint-proto] diff --git a/relayer-cli/src/application.rs b/relayer-cli/src/application.rs index aad7b51598..6519ef8cb0 100644 --- a/relayer-cli/src/application.rs +++ b/relayer-cli/src/application.rs @@ -8,11 +8,12 @@ use abscissa_core::{ component::Component, config, Application, Configurable, FrameworkError, FrameworkErrorKind, StandardPaths, }; +use ibc_relayer::config::Config; use crate::{ commands::CliCmd, components::{JsonTracing, PrettyTracing}, - config::{validate_config, Config}, + config::validate_config, entry::EntryPoint, }; @@ -125,8 +126,9 @@ impl Application for CliApp { // Configure components self.state.components.after_config(&config)?; - validate_config(&config) - .map_err(|validation_err| FrameworkErrorKind::ConfigError.context(validation_err))?; + validate_config(&config).map_err(|validation_err| { + FrameworkErrorKind::ConfigError.context(format!("{}", validation_err)) + })?; self.config = Some(config); diff --git a/relayer-cli/src/cli_utils.rs b/relayer-cli/src/cli_utils.rs index 17e88d24f1..599dfe3337 100644 --- a/relayer-cli/src/cli_utils.rs +++ b/relayer-cli/src/cli_utils.rs @@ -7,7 +7,7 @@ use ibc_relayer::{ config::Config, }; -use crate::error::{Error, Kind}; +use crate::error::Error; #[derive(Clone, Debug)] /// Pair of chain handles that are used by most CLIs. @@ -42,12 +42,10 @@ pub fn spawn_chain_runtime( let chain_config = config .find_chain(chain_id) .cloned() - .ok_or_else(|| format!("missing chain for id ({}) in configuration file", chain_id)) - .map_err(|e| Kind::Config.context(e))?; + .ok_or_else(|| Error::missing_config(chain_id.clone()))?; let rt = Arc::new(TokioRuntime::new().unwrap()); - let handle = ChainRuntime::::spawn(chain_config, rt) - .map_err(|e| Kind::Runtime.context(e))?; + let handle = ChainRuntime::::spawn(chain_config, rt).map_err(Error::relayer)?; Ok(handle) } diff --git a/relayer-cli/src/commands.rs b/relayer-cli/src/commands.rs index db2d62f4ca..43259bf2bf 100644 --- a/relayer-cli/src/commands.rs +++ b/relayer-cli/src/commands.rs @@ -12,8 +12,8 @@ use abscissa_core::{ }; use tracing::{error, info}; -use crate::config::Config; use crate::DEFAULT_CONFIG_PATH; +use ibc_relayer::config::Config; use self::{ config::ConfigCmd, create::CreateCmds, keys::KeysCmd, listen::ListenCmd, diff --git a/relayer-cli/src/commands/keys/add.rs b/relayer-cli/src/commands/keys/add.rs index a877bd572c..abb2a4f26b 100644 --- a/relayer-cli/src/commands/keys/add.rs +++ b/relayer-cli/src/commands/keys/add.rs @@ -5,7 +5,6 @@ use std::{ }; use abscissa_core::{Command, Options, Runnable}; -use anomaly::BoxError; use ibc::ics24_host::identifier::ChainId; use ibc_relayer::{ @@ -39,7 +38,7 @@ pub struct KeysAddCmd { } impl KeysAddCmd { - fn options(&self, config: &Config) -> Result { + fn options(&self, config: &Config) -> Result> { let chain_config = config .find_chain(&self.chain_id) .ok_or_else(|| format!("chain '{}' not found in configuration file", self.chain_id))?; @@ -96,7 +95,7 @@ pub fn add_key( key_name: &str, file: &Path, hd_path: &HDPath, -) -> Result { +) -> Result> { let mut keyring = KeyRing::new(Store::Test, &config.account_prefix, &config.id)?; let key_contents = fs::read_to_string(file).map_err(|_| "error reading the key file")?; diff --git a/relayer-cli/src/commands/keys/list.rs b/relayer-cli/src/commands/keys/list.rs index 7b272d9572..efee41c40b 100644 --- a/relayer-cli/src/commands/keys/list.rs +++ b/relayer-cli/src/commands/keys/list.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; use abscissa_core::{Command, Options, Runnable}; -use anomaly::BoxError; use ibc::ics24_host::identifier::ChainId; use ibc_relayer::{ @@ -61,7 +60,9 @@ pub struct KeysListOptions { pub chain_config: ChainConfig, } -pub fn list_keys(config: ChainConfig) -> Result, BoxError> { +pub fn list_keys( + config: ChainConfig, +) -> Result, Box> { let keyring = KeyRing::new(Store::Test, &config.account_prefix, &config.id)?; let keys = keyring.keys()?; Ok(keys) diff --git a/relayer-cli/src/commands/keys/restore.rs b/relayer-cli/src/commands/keys/restore.rs index 45295bc489..ae2f0208c2 100644 --- a/relayer-cli/src/commands/keys/restore.rs +++ b/relayer-cli/src/commands/keys/restore.rs @@ -1,7 +1,6 @@ use std::str::FromStr; use abscissa_core::{Command, Options, Runnable}; -use anomaly::BoxError; use ibc::ics24_host::identifier::ChainId; use ibc_relayer::{ @@ -92,7 +91,7 @@ pub fn restore_key( key_name: &str, hdpath: &HDPath, config: &ChainConfig, -) -> Result { +) -> Result> { let mut keyring = KeyRing::new(Store::Test, &config.account_prefix, &config.id)?; let key_entry = keyring.key_from_mnemonic(mnemonic, hdpath)?; diff --git a/relayer-cli/src/commands/listen.rs b/relayer-cli/src/commands/listen.rs index fd3b0047d9..3e87fbdd68 100644 --- a/relayer-cli/src/commands/listen.rs +++ b/relayer-cli/src/commands/listen.rs @@ -1,6 +1,6 @@ use std::{fmt, ops::Deref, str::FromStr, sync::Arc, thread}; -use abscissa_core::{application::fatal_error, error::BoxError, Command, Options, Runnable}; +use abscissa_core::{application::fatal_error, Command, Options, Runnable}; use itertools::Itertools; use tokio::runtime::Runtime as TokioRuntime; use tracing::{error, info}; @@ -44,7 +44,7 @@ impl fmt::Display for EventFilter { } impl FromStr for EventFilter { - type Err = BoxError; + type Err = Box; fn from_str(s: &str) -> Result { match s { @@ -67,7 +67,7 @@ pub struct ListenCmd { } impl ListenCmd { - fn cmd(&self) -> Result<(), BoxError> { + fn cmd(&self) -> Result<(), Box> { let config = app_config(); let chain_config = config @@ -92,7 +92,10 @@ impl Runnable for ListenCmd { } /// Listen to events -pub fn listen(config: &ChainConfig, filters: &[EventFilter]) -> Result<(), BoxError> { +pub fn listen( + config: &ChainConfig, + filters: &[EventFilter], +) -> Result<(), Box> { info!( "listening for events `{}` on '{}'...", filters.iter().format(", "), @@ -139,7 +142,7 @@ fn event_match(event: &IbcEvent, filters: &[EventFilter]) -> bool { fn subscribe( chain_config: &ChainConfig, rt: Arc, -) -> Result<(EventMonitor, EventReceiver), BoxError> { +) -> Result<(EventMonitor, EventReceiver), Box> { let (mut event_monitor, rx, _) = EventMonitor::new( chain_config.id.clone(), chain_config.websocket_addr.clone(), diff --git a/relayer-cli/src/commands/misbehaviour.rs b/relayer-cli/src/commands/misbehaviour.rs index 5ea708a119..6cba6d85b7 100644 --- a/relayer-cli/src/commands/misbehaviour.rs +++ b/relayer-cli/src/commands/misbehaviour.rs @@ -1,11 +1,11 @@ -use abscissa_core::{config, error::BoxError, Command, Options, Runnable}; +use abscissa_core::{config, Command, Options, Runnable}; use ibc::events::IbcEvent; use ibc::ics02_client::events::UpdateClient; use ibc::ics02_client::height::Height; use ibc::ics24_host::identifier::{ChainId, ClientId}; use ibc_relayer::chain::handle::ChainHandle; -use ibc_relayer::event::monitor::UnwrapOrClone; use ibc_relayer::foreign_client::{ForeignClient, MisbehaviourResults}; +use std::ops::Deref; use crate::application::CliApp; use crate::cli_utils::spawn_chain_runtime; @@ -46,7 +46,7 @@ pub fn monitor_misbehaviour( chain_id: &ChainId, client_id: &ClientId, config: &config::Reader, -) -> Result, BoxError> { +) -> Result, Box> { let chain = spawn_chain_runtime(config, chain_id) .map_err(|e| format!("could not spawn the chain runtime for {}: {}", chain_id, e))?; @@ -57,10 +57,9 @@ pub fn monitor_misbehaviour( // process update client events while let Ok(event_batch) = subscription.recv() { - let event_batch = event_batch.unwrap_or_clone(); - match event_batch { + match event_batch.deref() { Ok(event_batch) => { - for event in event_batch.events { + for event in &event_batch.events { match event { IbcEvent::UpdateClient(update) => { debug!("{:?}", update); @@ -68,7 +67,7 @@ pub fn monitor_misbehaviour( chain.clone(), config, update.client_id().clone(), - Some(update), + Some(update.clone()), )?; } @@ -78,7 +77,7 @@ pub fn monitor_misbehaviour( IbcEvent::ClientMisbehaviour(ref _misbehaviour) => { // TODO - submit misbehaviour to the witnesses (our full node) - return Ok(Some(event)); + return Ok(Some(event.clone())); } _ => {} @@ -99,7 +98,7 @@ fn misbehaviour_handling( config: &config::Reader, client_id: ClientId, update: Option, -) -> Result<(), BoxError> { +) -> Result<(), Box> { let client_state = chain .query_client_state(&client_id, Height::zero()) .map_err(|e| format!("could not query client state for {}: {}", client_id, e))?; diff --git a/relayer-cli/src/commands/query/clients.rs b/relayer-cli/src/commands/query/clients.rs index b192cb95eb..2d582211a9 100644 --- a/relayer-cli/src/commands/query/clients.rs +++ b/relayer-cli/src/commands/query/clients.rs @@ -10,7 +10,7 @@ use ibc_proto::ibc::core::client::v1::QueryClientStatesRequest; use ibc_relayer::chain::{Chain, CosmosSdkChain}; use crate::conclude::Output; -use crate::error::{Error, Kind}; +use crate::error::Error; use crate::prelude::*; /// Query clients command @@ -39,7 +39,7 @@ struct ClientChain { } /// Command for querying all clients. -/// hermes -c cfg.toml query clients ibc-1 +/// hermes -c cfg.toml query clients ibc-1 impl Runnable for QueryAllClientsCmd { fn run(&self) { let config = app_config(); @@ -64,9 +64,7 @@ impl Runnable for QueryAllClientsCmd { pagination: ibc_proto::cosmos::base::query::pagination::all(), }; - let res: Result<_, Error> = chain - .query_clients(req) - .map_err(|e| Kind::Query.context(e).into()); + let res: Result<_, Error> = chain.query_clients(req).map_err(Error::relayer); match res { Ok(clients) => { diff --git a/relayer-cli/src/commands/query/connection.rs b/relayer-cli/src/commands/query/connection.rs index e549fa2f62..9fcc5c2d9e 100644 --- a/relayer-cli/src/commands/query/connection.rs +++ b/relayer-cli/src/commands/query/connection.rs @@ -12,7 +12,7 @@ use ibc_proto::ibc::core::channel::v1::QueryConnectionChannelsRequest; use ibc_relayer::chain::{Chain, CosmosSdkChain}; use crate::conclude::Output; -use crate::error::{Error, Kind}; +use crate::error::Error; use crate::prelude::*; #[derive(Clone, Command, Debug, Options)] @@ -104,9 +104,7 @@ impl Runnable for QueryConnectionChannelsCmd { pagination: ibc_proto::cosmos::base::query::pagination::all(), }; - let res: Result<_, Error> = chain - .query_connection_channels(req) - .map_err(|e| Kind::Query.context(e).into()); + let res: Result<_, Error> = chain.query_connection_channels(req).map_err(Error::relayer); match res { Ok(channels) => { diff --git a/relayer-cli/src/commands/query/packet/ack.rs b/relayer-cli/src/commands/query/packet/ack.rs index aaf5658b37..86ef5762f0 100644 --- a/relayer-cli/src/commands/query/packet/ack.rs +++ b/relayer-cli/src/commands/query/packet/ack.rs @@ -7,7 +7,7 @@ use ibc::Height; use crate::cli_utils::spawn_chain_runtime; use crate::conclude::Output; -use crate::error::{Error, Kind}; +use crate::error::Error; use crate::prelude::*; #[derive(Clone, Command, Debug, Options)] @@ -44,7 +44,7 @@ impl QueryPacketAcknowledgmentCmd { self.sequence, Height::new(chain.id().version(), self.height.unwrap_or(0_u64)), ) - .map_err(|e| Kind::Query.context(e).into()) + .map_err(Error::relayer) .map(|(b, _)| b) .map(|bytes| { Hex::upper_case() diff --git a/relayer-cli/src/commands/query/packet/acks.rs b/relayer-cli/src/commands/query/packet/acks.rs index 09c7547ceb..0e9164d033 100644 --- a/relayer-cli/src/commands/query/packet/acks.rs +++ b/relayer-cli/src/commands/query/packet/acks.rs @@ -7,7 +7,7 @@ use ibc_proto::ibc::core::channel::v1::QueryPacketAcknowledgementsRequest; use crate::cli_utils::spawn_chain_runtime; use crate::conclude::Output; -use crate::error::{Error, Kind}; +use crate::error::Error; use crate::prelude::*; #[derive(Serialize, Debug)] @@ -45,7 +45,7 @@ impl QueryPacketAcknowledgementsCmd { // Transform the list fo raw packet state into the list of sequence numbers chain .query_packet_acknowledgements(grpc_request) - .map_err(|e| Kind::Query.context(e).into()) + .map_err(Error::relayer) .map(|(packet, height)| PacketSeqs { seqs: packet.iter().map(|p| p.sequence).collect(), height, diff --git a/relayer-cli/src/commands/query/packet/commitment.rs b/relayer-cli/src/commands/query/packet/commitment.rs index 51f3e63bfa..d3df3717a6 100644 --- a/relayer-cli/src/commands/query/packet/commitment.rs +++ b/relayer-cli/src/commands/query/packet/commitment.rs @@ -8,7 +8,7 @@ use ibc::Height; use crate::cli_utils::spawn_chain_runtime; use crate::conclude::Output; -use crate::error::{Error, Kind}; +use crate::error::Error; use crate::prelude::*; #[derive(Serialize, Debug)] @@ -52,7 +52,7 @@ impl QueryPacketCommitmentCmd { Height::new(chain.id().version(), self.height.unwrap_or(0_u64)), ) .map(|(bytes, _)| bytes) - .map_err(|e| Kind::Query.context(e))?; + .map_err(Error::relayer)?; if bytes.is_empty() { Ok("None".to_owned()) diff --git a/relayer-cli/src/commands/query/packet/commitments.rs b/relayer-cli/src/commands/query/packet/commitments.rs index 0073e30a8d..0ad5a2b196 100644 --- a/relayer-cli/src/commands/query/packet/commitments.rs +++ b/relayer-cli/src/commands/query/packet/commitments.rs @@ -7,7 +7,7 @@ use ibc_proto::ibc::core::channel::v1::QueryPacketCommitmentsRequest; use crate::cli_utils::spawn_chain_runtime; use crate::conclude::Output; -use crate::error::{Error, Kind}; +use crate::error::Error; use crate::prelude::*; #[derive(Serialize, Debug)] @@ -44,7 +44,7 @@ impl QueryPacketCommitmentsCmd { chain .query_packet_commitments(grpc_request) - .map_err(|e| Kind::Query.context(e).into()) + .map_err(Error::relayer) // Transform the raw packet commitm. state into the list of sequence numbers .map(|(ps_vec, height)| (ps_vec.iter().map(|ps| ps.sequence).collect(), height)) // Assemble into a coherent result diff --git a/relayer-cli/src/commands/query/packet/unreceived_acks.rs b/relayer-cli/src/commands/query/packet/unreceived_acks.rs index a921bfe51f..3923ff6f83 100644 --- a/relayer-cli/src/commands/query/packet/unreceived_acks.rs +++ b/relayer-cli/src/commands/query/packet/unreceived_acks.rs @@ -9,7 +9,7 @@ use ibc_relayer::chain::counterparty::channel_connection_client; use crate::cli_utils::spawn_chain_runtime; use crate::conclude::Output; -use crate::error::{Error, Kind}; +use crate::error::Error; use crate::prelude::*; /// This command does the following: @@ -42,7 +42,7 @@ impl QueryUnreceivedAcknowledgementCmd { let channel_connection_client = channel_connection_client(chain.as_ref(), &self.port_id, &self.channel_id) - .map_err(|e| Kind::Query.context(e))?; + .map_err(Error::supervisor)?; let channel = channel_connection_client.channel; debug!( @@ -54,12 +54,7 @@ impl QueryUnreceivedAcknowledgementCmd { .counterparty() .channel_id .as_ref() - .ok_or_else(|| { - Kind::Query.context(format!( - "the channel {:?} has no counterparty channel id", - channel - )) - })? + .ok_or_else(|| Error::missing_counterparty_channel_id(channel.clone()))? .to_string(); let counterparty_chain_id = channel_connection_client.client.client_state.chain_id(); @@ -74,7 +69,7 @@ impl QueryUnreceivedAcknowledgementCmd { let sequences: Vec = counterparty_chain .query_packet_acknowledgements(acks_request) - .map_err(|e| Kind::Query.context(e)) + .map_err(Error::relayer) // extract the sequences .map(|(packet_state, _)| packet_state.into_iter().map(|v| v.sequence).collect())?; @@ -86,7 +81,7 @@ impl QueryUnreceivedAcknowledgementCmd { chain .query_unreceived_acknowledgement(request) - .map_err(|e| Kind::Query.context(e).into()) + .map_err(Error::relayer) } } diff --git a/relayer-cli/src/commands/query/packet/unreceived_packets.rs b/relayer-cli/src/commands/query/packet/unreceived_packets.rs index e565b4add5..94a50d501a 100644 --- a/relayer-cli/src/commands/query/packet/unreceived_packets.rs +++ b/relayer-cli/src/commands/query/packet/unreceived_packets.rs @@ -11,7 +11,7 @@ use ibc_relayer::chain::counterparty::channel_connection_client; use crate::cli_utils::spawn_chain_runtime; use crate::conclude::Output; -use crate::error::{Error, Kind}; +use crate::error::Error; use crate::prelude::*; #[derive(Serialize, Debug)] @@ -50,7 +50,7 @@ impl QueryUnreceivedPacketsCmd { let channel_connection_client = channel_connection_client(chain.as_ref(), &self.port_id, &self.channel_id) - .map_err(|e| Kind::Query.context(e))?; + .map_err(Error::supervisor)?; let channel = channel_connection_client.channel; debug!( @@ -62,12 +62,7 @@ impl QueryUnreceivedPacketsCmd { .counterparty() .channel_id .as_ref() - .ok_or_else(|| { - Kind::Query.context(format!( - "the channel {:?} counterparty has no channel id", - channel - )) - })? + .ok_or_else(|| Error::missing_counterparty_channel_id(channel.clone()))? .to_string(); let counterparty_chain_id = channel_connection_client.client.client_state.chain_id(); @@ -82,7 +77,7 @@ impl QueryUnreceivedPacketsCmd { let commitments = counterparty_chain .query_packet_commitments(commitments_request) - .map_err(|e| Kind::Query.context(e))?; + .map_err(Error::relayer)?; // extract the sequences let sequences: Vec = commitments.0.into_iter().map(|v| v.sequence).collect(); @@ -101,7 +96,7 @@ impl QueryUnreceivedPacketsCmd { chain .query_unreceived_packets(request) - .map_err(|e| Kind::Query.context(e).into()) + .map_err(Error::relayer) } } diff --git a/relayer-cli/src/commands/query/tx/events.rs b/relayer-cli/src/commands/query/tx/events.rs index 7850e0617b..4f8c5f5661 100644 --- a/relayer-cli/src/commands/query/tx/events.rs +++ b/relayer-cli/src/commands/query/tx/events.rs @@ -14,7 +14,7 @@ use ibc_relayer::chain::runtime::ChainRuntime; use ibc_relayer::chain::CosmosSdkChain; use crate::conclude::Output; -use crate::error::Kind; +use crate::error::Error; use crate::prelude::app_config; /// Query the events emitted by transaction @@ -46,14 +46,11 @@ impl Runnable for QueryTxEventsCmd { let chain = ChainRuntime::::spawn(chain_config.clone(), rt).unwrap(); let res = Hash::from_str(self.hash.as_str()) - .map_err(|e| { - Kind::CliArg(format!("could not parse '{}' into a valid hash", self.hash)) - .context(e) - }) + .map_err(|e| Error::invalid_hash(self.hash.clone(), e)) .and_then(|h| { chain .query_txs(QueryTxRequest::Transaction(QueryTxHash(h))) - .map_err(|e| Kind::Query.context(e)) + .map_err(Error::relayer) }); match res { diff --git a/relayer-cli/src/commands/tx/channel.rs b/relayer-cli/src/commands/tx/channel.rs index 47476a45f6..39060015ed 100644 --- a/relayer-cli/src/commands/tx/channel.rs +++ b/relayer-cli/src/commands/tx/channel.rs @@ -9,7 +9,7 @@ use ibc_relayer::channel::{Channel, ChannelSide}; use crate::cli_utils::ChainHandlePair; use crate::conclude::Output; -use crate::error::{Error, Kind}; +use crate::error::Error; use crate::prelude::*; macro_rules! tx_chan_cmd { @@ -35,7 +35,7 @@ macro_rules! tx_chan_cmd { info!("Message {}: {:?}", $dbg_string, channel); - let res: Result = channel.$func().map_err(|e| Kind::Tx.context(e).into()); + let res: Result = channel.$func().map_err(Error::channel); match res { Ok(receipt) => Output::success(receipt).exit(), @@ -67,32 +67,52 @@ pub struct TxRawChanOpenInitCmd { impl Runnable for TxRawChanOpenInitCmd { fn run(&self) { - tx_chan_cmd!( - "ChanOpenInit", - build_chan_open_init_and_send, - self, - |chains: ChainHandlePair, dst_connection: ConnectionEnd| { - Channel { - connection_delay: Default::default(), - ordering: self.order, - a_side: ChannelSide::new( - chains.src, - ClientId::default(), - ConnectionId::default(), - self.src_port_id.clone(), - None, - ), - b_side: ChannelSide::new( - chains.dst.clone(), - dst_connection.client_id().clone(), - self.dst_conn_id.clone(), - self.dst_port_id.clone(), - None, - ), - version: None, - } - } - ); + let config = app_config(); + + let chains = match ChainHandlePair::spawn(&config, &self.src_chain_id, &self.dst_chain_id) { + Ok(chains) => chains, + Err(e) => return Output::error(format!("{}", e)).exit(), + }; + + // Retrieve the connection + let dst_connection = match chains + .dst + .query_connection(&self.dst_conn_id, Height::default()) + { + Ok(connection) => connection, + Err(e) => return Output::error(format!("{}", e)).exit(), + }; + + let channel = Channel { + connection_delay: Default::default(), + ordering: self.order, + a_side: ChannelSide::new( + chains.src, + ClientId::default(), + ConnectionId::default(), + self.src_port_id.clone(), + None, + ), + b_side: ChannelSide::new( + chains.dst.clone(), + dst_connection.client_id().clone(), + self.dst_conn_id.clone(), + self.dst_port_id.clone(), + None, + ), + version: None, + }; + + info!("Message ChanOpenInit: {:?}", channel); + + let res: Result = channel + .build_chan_open_init_and_send() + .map_err(Error::channel); + + match res { + Ok(receipt) => Output::success(receipt).exit(), + Err(e) => Output::error(format!("{}", e)).exit(), + } } } @@ -131,6 +151,53 @@ pub struct TxRawChanOpenTryCmd { impl Runnable for TxRawChanOpenTryCmd { fn run(&self) { + let config = app_config(); + + let chains = match ChainHandlePair::spawn(&config, &self.src_chain_id, &self.dst_chain_id) { + Ok(chains) => chains, + Err(e) => return Output::error(format!("{}", e)).exit(), + }; + + // Retrieve the connection + let dst_connection = match chains + .dst + .query_connection(&self.dst_conn_id, Height::default()) + { + Ok(connection) => connection, + Err(e) => return Output::error(format!("{}", e)).exit(), + }; + + let channel = Channel { + connection_delay: Default::default(), + ordering: Order::default(), + a_side: ChannelSide::new( + chains.src, + ClientId::default(), + ConnectionId::default(), + self.src_port_id.clone(), + Some(self.src_chan_id.clone()), + ), + b_side: ChannelSide::new( + chains.dst.clone(), + dst_connection.client_id().clone(), + self.dst_conn_id.clone(), + self.dst_port_id.clone(), + self.dst_chan_id.clone(), + ), + version: None, + }; + + info!("Message ChanOpenTry: {:?}", channel); + + let res: Result = channel + .build_chan_open_try_and_send() + .map_err(Error::channel); + + match res { + Ok(receipt) => Output::success(receipt).exit(), + Err(e) => Output::error(format!("{}", e)).exit(), + } + tx_chan_cmd!( "ChanOpenTry", build_chan_open_try_and_send, diff --git a/relayer-cli/src/commands/tx/client.rs b/relayer-cli/src/commands/tx/client.rs index 73861e7bf2..303c865694 100644 --- a/relayer-cli/src/commands/tx/client.rs +++ b/relayer-cli/src/commands/tx/client.rs @@ -12,7 +12,7 @@ use ibc_relayer::foreign_client::ForeignClient; use crate::application::{app_config, CliApp}; use crate::cli_utils::{spawn_chain_runtime, ChainHandlePair}; use crate::conclude::{exit_with_unrecoverable_error, Output}; -use crate::error::{Error, Kind}; +use crate::error::Error; #[derive(Clone, Command, Debug, Options)] pub struct TxCreateClientCmd { @@ -43,7 +43,7 @@ impl Runnable for TxCreateClientCmd { // Trigger client creation via the "build" interface, so that we obtain the resulting event let res: Result = client .build_create_client_and_send() - .map_err(|e| Kind::Tx.context(e).into()); + .map_err(Error::foreign_client); match res { Ok(receipt) => Output::success(receipt).exit(), @@ -112,7 +112,7 @@ impl Runnable for TxUpdateClientCmd { let res = client .build_update_client_and_send(height, trusted_height) - .map_err(|e| Kind::Tx.context(e)); + .map_err(Error::foreign_client); match res { Ok(events) => Output::success(events).exit(), @@ -233,7 +233,7 @@ impl TxUpgradeClientsCmd { }; let outputs = dst_chain .query_clients(req) - .map_err(|e| Kind::Query.context(e))? + .map_err(Error::relayer)? .into_iter() .filter_map(|c| (self.src_chain_id == c.client_state.chain_id()).then(|| c.client_id)) .map(|id| TxUpgradeClientsCmd::upgrade_client(id, dst_chain.clone(), src_chain.clone())) @@ -248,7 +248,7 @@ impl TxUpgradeClientsCmd { src_chain: Box, ) -> Result, Error> { let client = ForeignClient::restore(client_id, dst_chain.clone(), src_chain.clone()); - client.upgrade().map_err(|e| Kind::Query.context(e).into()) + client.upgrade().map_err(Error::foreign_client) } } diff --git a/relayer-cli/src/commands/tx/connection.rs b/relayer-cli/src/commands/tx/connection.rs index 65cd0e1514..f8126715eb 100644 --- a/relayer-cli/src/commands/tx/connection.rs +++ b/relayer-cli/src/commands/tx/connection.rs @@ -7,7 +7,7 @@ use ibc_relayer::connection::{Connection, ConnectionSide}; use crate::cli_utils::ChainHandlePair; use crate::conclude::Output; -use crate::error::{Error, Kind}; +use crate::error::Error; use crate::prelude::*; macro_rules! conn_open_cmd { @@ -24,8 +24,7 @@ macro_rules! conn_open_cmd { debug!("Message {}: {:?}", $dbg_string, connection); - let res: Result = - connection.$func().map_err(|e| Kind::Tx.context(e).into()); + let res: Result = connection.$func().map_err(Error::connection); match res { Ok(receipt) => Output::success(receipt).exit(), diff --git a/relayer-cli/src/commands/tx/packet.rs b/relayer-cli/src/commands/tx/packet.rs index d31ad2e0be..2b0045aeac 100644 --- a/relayer-cli/src/commands/tx/packet.rs +++ b/relayer-cli/src/commands/tx/packet.rs @@ -6,7 +6,7 @@ use ibc_relayer::link::{Link, LinkParameters}; use crate::cli_utils::ChainHandlePair; use crate::conclude::Output; -use crate::error::{Error, Kind}; +use crate::error::Error; use crate::prelude::*; #[derive(Clone, Command, Debug, Options)] @@ -44,7 +44,7 @@ impl Runnable for TxRawPacketRecvCmd { let res: Result, Error> = link .build_and_send_recv_packet_messages() - .map_err(|e| Kind::Tx.context(e).into()); + .map_err(Error::link); match res { Ok(ev) => Output::success(ev).exit(), @@ -88,7 +88,7 @@ impl Runnable for TxRawPacketAckCmd { let res: Result, Error> = link .build_and_send_ack_packet_messages() - .map_err(|e| Kind::Tx.context(e).into()); + .map_err(Error::link); match res { Ok(ev) => Output::success(ev).exit(), diff --git a/relayer-cli/src/commands/tx/transfer.rs b/relayer-cli/src/commands/tx/transfer.rs index 5b1525d234..72d9f3c679 100644 --- a/relayer-cli/src/commands/tx/transfer.rs +++ b/relayer-cli/src/commands/tx/transfer.rs @@ -1,5 +1,4 @@ use abscissa_core::{config::Override, Command, FrameworkErrorKind, Options, Runnable}; -use anomaly::BoxError; use ibc::{ events::IbcEvent, @@ -14,7 +13,7 @@ use ibc_relayer::{ use crate::cli_utils::ChainHandlePair; use crate::conclude::{exit_with_unrecoverable_error, Output}; -use crate::error::{Error, Kind}; +use crate::error::Error; use crate::prelude::*; #[derive(Clone, Command, Debug, Options)] @@ -82,7 +81,10 @@ impl Override for TxIcs20MsgTransferCmd { } impl TxIcs20MsgTransferCmd { - fn validate_options(&self, config: &Config) -> Result { + fn validate_options( + &self, + config: &Config, + ) -> Result> { let src_chain_config = config .find_chain(&self.src_chain_id) .ok_or("missing src chain configuration")?; @@ -197,8 +199,7 @@ impl Runnable for TxIcs20MsgTransferCmd { // Checks pass, build and send the tx let res: Result, Error> = - build_and_send_transfer_messages(chains.src, chains.dst, opts) - .map_err(|e| Kind::Tx.context(e).into()); + build_and_send_transfer_messages(chains.src, chains.dst, opts).map_err(Error::packet); match res { Ok(ev) => Output::success(ev).exit(), diff --git a/relayer-cli/src/commands/tx/upgrade.rs b/relayer-cli/src/commands/tx/upgrade.rs index 74dce68437..f5e49ea86a 100644 --- a/relayer-cli/src/commands/tx/upgrade.rs +++ b/relayer-cli/src/commands/tx/upgrade.rs @@ -12,7 +12,7 @@ use ibc_relayer::{ }; use crate::conclude::Output; -use crate::error::{Error, Kind}; +use crate::error::Error; use crate::prelude::*; #[derive(Clone, Command, Debug, Options)] @@ -84,14 +84,14 @@ impl Runnable for TxUpgradeChainCmd { let rt = Arc::new(TokioRuntime::new().unwrap()); let src_chain_res = CosmosSdkChain::bootstrap(opts.src_chain_config.clone(), rt.clone()) - .map_err(|e| Kind::Runtime.context(e)); + .map_err(Error::relayer); let src_chain = match src_chain_res { Ok(chain) => chain, Err(e) => return Output::error(format!("{}", e)).exit(), }; - let dst_chain_res = CosmosSdkChain::bootstrap(opts.dst_chain_config.clone(), rt) - .map_err(|e| Kind::Runtime.context(e)); + let dst_chain_res = + CosmosSdkChain::bootstrap(opts.dst_chain_config.clone(), rt).map_err(Error::relayer); let dst_chain = match dst_chain_res { Ok(chain) => chain, Err(e) => return Output::error(format!("{}", e)).exit(), @@ -99,7 +99,7 @@ impl Runnable for TxUpgradeChainCmd { let res: Result, Error> = build_and_send_upgrade_chain_message(dst_chain, src_chain, &opts) - .map_err(|e| Kind::Tx.context(e).into()); + .map_err(Error::upgrade_chain); match res { Ok(ev) => Output::success(ev).exit(), diff --git a/relayer-cli/src/components.rs b/relayer-cli/src/components.rs index fbb9f81901..8edb0b6ea5 100644 --- a/relayer-cli/src/components.rs +++ b/relayer-cli/src/components.rs @@ -14,7 +14,7 @@ use tracing_subscriber::{ use ibc_relayer::config::GlobalConfig; -use crate::config; +use crate::config::Error; /// Custom types to simplify the `Tracing` definition below type JsonFormatter = TracingFormatter, StdWriter>; @@ -111,12 +111,14 @@ fn build_tracing_filter(log_level: String) -> Result match EnvFilter::try_new(directive_raw.clone()) { Ok(out) => Ok(out), Err(e) => { - let our_err = config::Error::InvalidLogLevel(log_level, e.to_string()); eprintln!( "Unable to initialize Hermes from filter directive {:?}: {}", directive_raw, e ); - Err(FrameworkErrorKind::ConfigError.context(our_err).into()) + let our_err = Error::invalid_log_level(log_level, e); + Err(FrameworkErrorKind::ConfigError + .context(format!("{}", our_err)) + .into()) } } } diff --git a/relayer-cli/src/config.rs b/relayer-cli/src/config.rs index 799ad763bd..96c606e2ad 100644 --- a/relayer-cli/src/config.rs +++ b/relayer-cli/src/config.rs @@ -7,12 +7,11 @@ use std::collections::BTreeSet; use std::path::PathBuf; -use thiserror::Error; - +use flex_error::{define_error, TraceError}; use ibc::ics24_host::identifier::ChainId; +use ibc_relayer::config::Config; use tendermint_light_client::types::TrustThreshold; - -pub use ibc_relayer::config::Config; +use tracing_subscriber::filter::ParseError; use crate::application::app_reader; @@ -22,34 +21,48 @@ pub fn config_path() -> Option { app.config_path().cloned() } -/// Specifies all the possible syntactic errors -/// that a Hermes configuration file could contain. -#[derive(Error, Debug)] -pub enum Error { - /// No chain is configured - #[error("config file does not specify any chain")] - ZeroChains, - - /// The log level is invalid - #[error("config file specifies an invalid log level ('{0}'), caused by: {1}")] - InvalidLogLevel(String, String), - - /// Duplicate chains configured - #[error("config file has duplicate entry for the chain with id {0}")] - DuplicateChains(ChainId), - - /// Invalid trust threshold - #[error("config file specifies an invalid trust threshold ({0}) for the chain with id {1}, caused by: {2}")] - InvalidTrustThreshold(TrustThreshold, ChainId, String), +// Specifies all the possible syntactic errors +// that a Hermes configuration file could contain. +define_error! { + Error { + ZeroChain + |_| { "config file does not specify any chain" }, + + InvalidLogLevel + { log_level: String, } + [ TraceError ] + |e| { + format!("config file specifies an invalid log level ('{0}'), caused by", + e.log_level) + }, + + DuplicateChains + { chain_id: ChainId } + |e| { + format!("config file has duplicate entry for the chain with id {0}", + e.chain_id) + }, + + InvalidTrustThreshold + { + threshold: TrustThreshold, + chain_id: ChainId, + reason: String + } + |e| { + format!("config file specifies an invalid trust threshold ({0}) for the chain with id {1}, caused by: {2}", + e.threshold, e.chain_id, e.reason) + }, + } } /// Method for syntactic validation of the input configuration file. pub fn validate_config(config: &Config) -> Result<(), Error> { // Check for duplicate chain configuration and invalid trust thresholds let mut unique_chain_ids = BTreeSet::new(); - for c in &config.chains { + for c in config.chains.iter() { if !unique_chain_ids.insert(c.id.clone()) { - return Err(Error::DuplicateChains(c.id.clone())); + return Err(Error::duplicate_chains(c.id.clone())); } validate_trust_threshold(&c.id, c.trust_threshold)?; @@ -65,7 +78,7 @@ pub fn validate_config(config: &Config) -> Result<(), Error> { /// c) strictly less than 1 fn validate_trust_threshold(id: &ChainId, trust_threshold: TrustThreshold) -> Result<(), Error> { if trust_threshold.denominator() == 0 { - return Err(Error::InvalidTrustThreshold( + return Err(Error::invalid_trust_threshold( trust_threshold, id.clone(), "trust threshold denominator cannot be zero".to_string(), @@ -73,7 +86,7 @@ fn validate_trust_threshold(id: &ChainId, trust_threshold: TrustThreshold) -> Re } if trust_threshold.numerator() * 3 < trust_threshold.denominator() { - return Err(Error::InvalidTrustThreshold( + return Err(Error::invalid_trust_threshold( trust_threshold, id.clone(), "trust threshold cannot be < 1/3".to_string(), @@ -81,7 +94,7 @@ fn validate_trust_threshold(id: &ChainId, trust_threshold: TrustThreshold) -> Re } if trust_threshold.numerator() >= trust_threshold.denominator() { - return Err(Error::InvalidTrustThreshold( + return Err(Error::invalid_trust_threshold( trust_threshold, id.clone(), "trust threshold cannot be >= 1".to_string(), diff --git a/relayer-cli/src/error.rs b/relayer-cli/src/error.rs index 98c38c1ad5..4d6536f258 100644 --- a/relayer-cli/src/error.rs +++ b/relayer-cli/src/error.rs @@ -1,46 +1,95 @@ -//! Error types +use flex_error::{define_error, DisplayOnly}; +use ibc::ics04_channel::channel::IdentifiedChannelEnd; +use ibc::ics24_host::identifier::ChainId; +use ibc_relayer::channel::ChannelError; +use ibc_relayer::connection::ConnectionError; +use ibc_relayer::error::Error as RelayerError; +use ibc_relayer::foreign_client::ForeignClientError; +use ibc_relayer::link::error::LinkError; +use ibc_relayer::supervisor::Error as SupervisorError; +use ibc_relayer::transfer::PacketError; +use ibc_relayer::upgrade_chain::UpgradeChainError; -use anomaly::{BoxError, Context}; -use thiserror::Error; +define_error! { + /// An error raised within the relayer CLI + Error { + Config + |_| { "config error" }, -/// An error raised within the relayer CLI -pub type Error = anomaly::Error; + Io + |_| { "I/O error" }, -/// Kinds of errors -#[derive(Clone, Debug, Eq, Error, PartialEq)] -pub enum Kind { - /// Error in configuration file - #[error("config error")] - Config, + Query + |_| { "query error" }, - /// Input/output error - #[error("I/O error")] - Io, + Runtime + |_| { "chain runtime error" }, - /// Input/output error - #[error("CLI argument error: {0}")] - CliArg(String), + Tx + |_| { "tx error" }, - /// Error during network query - #[error("query error")] - Query, + InvalidHash + { hash: String } + [ DisplayOnly> ] + | e | { + format_args!("CLI argument error: could not parse '{}' into a valid hash", + e.hash) + }, - /// Error while spawning the runtime - #[error("chain runtime error")] - Runtime, + CliArg + { reason: String } + | e | { + format_args!("CLI argument error: {0}", + e.reason) + }, - /// Error during transaction submission - #[error("tx error")] - Tx, + Keys + |_| { "keys error" }, - /// Error during transaction submission - #[error("keys error")] - Keys, -} + MissingConfig + { chain_id: ChainId } + | e | { + format_args!("missing chain for id ({}) in configuration file", + e.chain_id) + }, + + MissingCounterpartyChannelId + { channel_end: IdentifiedChannelEnd } + | e | { + format_args!("the channel {:?} counterparty has no channel id", + e.channel_end) + }, + + Relayer + [ RelayerError ] + |_| { "relayer error" }, + + Connection + [ ConnectionError ] + |_| { "connection error" }, + + Packet + [ PacketError ] + |_| { "packet error" }, + + Channel + [ ChannelError ] + |_| { "channel error" }, + + ForeignClient + [ ForeignClientError ] + |_| { "foreign client error" }, + + Supervisor + [ SupervisorError ] + |_| { "supervisor error" }, + + Link + [ LinkError ] + |_| { "link error" }, -impl Kind { - /// Create an error context from this error - pub fn context(self, source: impl Into) -> Context { - Context::new(self, Some(source.into())) + UpgradeChain + [ UpgradeChainError ] + |_| { "upgrade chain error" }, } } diff --git a/relayer-cli/src/lib.rs b/relayer-cli/src/lib.rs index 2dbdcc9e11..32043b0eb6 100644 --- a/relayer-cli/src/lib.rs +++ b/relayer-cli/src/lib.rs @@ -14,7 +14,6 @@ #![forbid(unsafe_code)] #![deny( - missing_docs, rust_2018_idioms, trivial_casts, unused_lifetimes, @@ -24,9 +23,10 @@ pub mod application; pub mod commands; pub mod config; -pub mod error; pub mod prelude; +pub mod error; + pub(crate) mod cli_utils; pub(crate) mod components; pub(crate) mod conclude; diff --git a/relayer-cli/src/prelude.rs b/relayer-cli/src/prelude.rs index 1d60e31fb2..b22d0225de 100644 --- a/relayer-cli/src/prelude.rs +++ b/relayer-cli/src/prelude.rs @@ -7,6 +7,3 @@ pub use abscissa_core::prelude::*; /// Application state accessors pub use crate::application::{app_config, app_reader, app_writer}; - -/// BoxError type for top-level error handling -pub use abscissa_core::error::BoxError; diff --git a/relayer/Cargo.toml b/relayer/Cargo.toml index 203c0a3367..5379705a9b 100644 --- a/relayer/Cargo.toml +++ b/relayer/Cargo.toml @@ -13,6 +13,11 @@ description = """ """ [features] +default = ["std", "eyre_tracer"] +std = [ + "flex-error/std" +] +eyre_tracer = ["flex-error/eyre_tracer"] profiling = [] telemetry = ["ibc-telemetry"] @@ -22,7 +27,6 @@ ibc-proto = { version = "0.9.0", path = "../proto" } ibc-telemetry = { version = "0.6.1", path = "../telemetry", optional = true } subtle-encoding = "0.5" -anomaly = "0.2.0" async-trait = "0.1.50" humantime-serde = "1.0.0" serde = "1.0.125" @@ -54,6 +58,10 @@ dirs-next = "2.0.0" dyn-clone = "1.0.3" retry = { version = "1.2.1", default-features = false } async-stream = "0.3.2" +http = "0.2.4" +flex-error = { version = "0.4.1", default-features = false } +signature = "1.3.0" +anyhow = "1.0.41" fraction = {version = "0.8.0", default-features = false } semver = "1.0" diff --git a/relayer/src/chain.rs b/relayer/src/chain.rs index 05f9648a6c..e9ab706d5b 100644 --- a/relayer/src/chain.rs +++ b/relayer/src/chain.rs @@ -35,7 +35,7 @@ use ibc_proto::ibc::core::connection::v1::{ }; use crate::connection::ConnectionMsgType; -use crate::error::{Error, Kind}; +use crate::error::Error; use crate::event::monitor::TxMonitorCmd; use crate::keyring::{KeyEntry, KeyRing}; use crate::light_client::LightClient; @@ -313,19 +313,19 @@ pub trait Chain: Sized { if !connection_end.state_matches(&State::Init) && !connection_end.state_matches(&State::TryOpen) { - return Err(Kind::ConnOpenTry("bad connection state".to_string()).into()); + return Err(Error::bad_connection_state()); } } ConnectionMsgType::OpenAck => { if !connection_end.state_matches(&State::TryOpen) && !connection_end.state_matches(&State::Open) { - return Err(Kind::ConnOpenTry("bad connection state".to_string()).into()); + return Err(Error::bad_connection_state()); } } ConnectionMsgType::OpenConfirm => { if !connection_end.state_matches(&State::Open) { - return Err(Kind::ConnOpenTry("bad connection state".to_string()).into()); + return Err(Error::bad_connection_state()); } } } @@ -350,9 +350,7 @@ pub trait Chain: Sized { CommitmentProofBytes::from(consensus_state_proof), client_state_value.latest_height(), ) - .map_err(|e| { - Kind::ConnOpenTry("failed to build consensus proof".to_string()).context(e) - })?, + .map_err(Error::consensus_proof)?, ); client_state = Some(client_state_value); @@ -369,7 +367,7 @@ pub trait Chain: Sized { None, height.increment(), ) - .map_err(|_| Kind::MalformedProof)?, + .map_err(Error::malformed_proof)?, )) } @@ -384,10 +382,8 @@ pub trait Chain: Sized { let channel_proof = CommitmentProofBytes::from(self.proven_channel(port_id, channel_id, height)?.1); - Ok( - Proofs::new(channel_proof, None, None, None, height.increment()) - .map_err(|_| Kind::MalformedProof)?, - ) + Proofs::new(channel_proof, None, None, None, height.increment()) + .map_err(Error::malformed_proof) } /// Builds the proof for packet messages. @@ -417,7 +413,7 @@ pub trait Chain: Sized { channel_proof, height.increment(), ) - .map_err(|_| Kind::MalformedProof)?; + .map_err(Error::malformed_proof)?; Ok((bytes, proofs)) } diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index 38cba026b1..b4cb7ddd1d 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -8,7 +8,6 @@ use std::{ time::{Duration, Instant}, }; -use anomaly::fail; use bech32::{ToBase32, Variant}; use bitcoin::hashes::hex::ToHex; use itertools::Itertools; @@ -75,7 +74,7 @@ use ibc_proto::ibc::core::connection::v1::{ }; use crate::config::{ChainConfig, GasPrice}; -use crate::error::{Error, Kind}; +use crate::error::Error; use crate::event::monitor::{EventMonitor, EventReceiver}; use crate::keyring::{KeyEntry, KeyRing, Store}; use crate::light_client::tendermint::LightClient as TmLightClient; @@ -133,16 +132,14 @@ impl CosmosSdkChain { let rpc_address = chain.config.rpc_addr.to_string(); // Checkup on the self-reported health endpoint - chain - .rpc_client - .health() - .await - .map_err(|e| Kind::HealthCheckJsonRpc { - chain_id: chain_id.clone(), - address: rpc_address.clone(), - endpoint: "/health".to_string(), - cause: e, - })?; + chain.rpc_client.health().await.map_err(|e| { + Error::health_check_json_rpc( + chain_id.clone(), + rpc_address.clone(), + "/health".to_string(), + e, + ) + })?; // Checkup on transaction indexing chain @@ -155,58 +152,52 @@ impl CosmosSdkChain { Order::Ascending, ) .await - .map_err(|e| Kind::HealthCheckJsonRpc { - chain_id: chain_id.clone(), - address: rpc_address.clone(), - endpoint: "/tx_search".to_string(), - cause: e, + .map_err(|e| { + Error::health_check_json_rpc( + chain_id.clone(), + rpc_address.clone(), + "/tx_search".to_string(), + e, + ) })?; let mut client = ServiceClient::connect(chain.grpc_addr.clone()) .await .map_err(|e| { - // Failed to create the gRPC client to call into `/node_info`. - Kind::HealthCheckGrpc { - chain_id: chain_id.clone(), - address: grpc_address.clone(), - endpoint: "tendermint::ServiceClient".to_string(), - cause: e.to_string(), - } + Error::health_check_json_grpc_transport( + chain_id.clone(), + rpc_address.clone(), + "tendermint::ServiceClient".to_string(), + e, + ) })?; let request = tonic::Request::new(GetNodeInfoRequest {}); - let response = - client - .get_node_info(request) - .await - .map_err(|e| Kind::HealthCheckGrpc { - chain_id: chain_id.clone(), - address: grpc_address.clone(), - endpoint: "tendermint::GetNodeInfoRequest".to_string(), - cause: e.to_string(), - })?; - - let version = - response - .into_inner() - .application_version - .ok_or_else(|| Kind::HealthCheckGrpc { - chain_id: chain_id.clone(), - address: grpc_address.clone(), - endpoint: "tendermint::GetNodeInfoRequest".to_string(), - cause: "the gRPC response contains no application version information" - .to_string(), - })?; + let response = client.get_node_info(request).await.map_err(|e| { + Error::health_check_json_grpc_status( + chain_id.clone(), + rpc_address.clone(), + "tendermint::ServiceClient".to_string(), + e, + ) + })?; + + let version = response.into_inner().application_version.ok_or_else(|| { + Error::health_check_invalid_version( + chain_id.clone(), + rpc_address.clone(), + "tendermint::GetNodeInfoRequest".to_string(), + ) + })?; // Checkup on the underlying SDK version if let Some(diagnostic) = compatibility::run_diagnostic(version) { - return Err(Kind::SdkModuleVersion { - chain_id: chain_id.clone(), - address: grpc_address.clone(), - cause: diagnostic.to_string(), - } - .into()); + return Err(Error::sdk_module_version( + chain_id.clone(), + grpc_address.clone(), + diagnostic.to_string(), + )); } Ok(()) @@ -228,21 +219,21 @@ impl CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(|e| Kind::Grpc.context(e))?; + .map_err(Error::grpc_transport)?; let request = tonic::Request::new(ibc_proto::cosmos::staking::v1beta1::QueryParamsRequest {}); let response = self .block_on(client.params(request)) - .map_err(|e| Kind::Grpc.context(e))?; + .map_err(Error::grpc_status)?; let res = response .into_inner() .params - .ok_or_else(|| Kind::Grpc.context("none staking params".to_string()))? + .ok_or_else(|| Error::grpc_response_param("none staking params".to_string()))? .unbonding_time - .ok_or_else(|| Kind::Grpc.context("none unbonding time".to_string()))?; + .ok_or_else(|| Error::grpc_response_param("none unbonding time".to_string()))?; Ok(Duration::new(res.seconds as u64, res.nanos as u32)) } @@ -262,7 +253,7 @@ impl CosmosSdkChain { Ok(self .block_on(self.rpc_client().genesis()) - .map_err(|e| Kind::Rpc(self.config.rpc_addr.clone()).context(e))? + .map_err(|e| Error::rpc(self.config.rpc_addr.clone(), e))? .consensus_params) } @@ -309,12 +300,11 @@ impl CosmosSdkChain { }); if estimated_gas > self.max_gas() { - return Err(Kind::TxSimulateGasEstimateExceeded { - chain_id: self.id().clone(), + return Err(Error::tx_simulate_gas_estimate_exceeded( + self.id().clone(), estimated_gas, - max_gas: self.max_gas(), - } - .into()); + self.max_gas(), + )); } let adjusted_fee = self.fee_with_gas(estimated_gas); @@ -340,9 +330,7 @@ impl CosmosSdkChain { let mut tx_bytes = Vec::new(); prost::Message::encode(&tx_raw, &mut tx_bytes).unwrap(); - let response = self - .block_on(broadcast_tx_sync(self, tx_bytes)) - .map_err(|e| Kind::Rpc(self.config.rpc_addr.clone()).context(e))?; + let response = self.block_on(broadcast_tx_sync(self, tx_bytes))?; debug!("[{}] send_tx: broadcast_tx_sync: {:?}", self.id(), response); @@ -403,13 +391,10 @@ impl CosmosSdkChain { let path = TendermintABCIPath::from_str(IBC_QUERY_PATH).unwrap(); - let height = - Height::try_from(height.revision_height).map_err(|e| Kind::InvalidHeight.context(e))?; + let height = Height::try_from(height.revision_height).map_err(Error::invalid_height)?; if !data.is_provable() & prove { - return Err(Kind::Store - .context("requested proof for a path in the privateStore") - .into()); + return Err(Error::private_store()); } let response = self.block_on(abci_query(self, path, data.to_string(), height, prove))?; @@ -426,11 +411,10 @@ impl CosmosSdkChain { data: ClientUpgradePath, height: Height, ) -> Result<(MerkleProof, ICSHeight), Error> { - let prev_height = - Height::try_from(height.value() - 1).map_err(|e| Kind::InvalidHeight.context(e))?; + let prev_height = Height::try_from(height.value() - 1).map_err(Error::invalid_height)?; let path = TendermintABCIPath::from_str(SDK_UPGRADE_QUERY_PATH).unwrap(); - let response = self.block_on(abci_query( + let response: QueryResponse = self.block_on(abci_query( self, path, Path::Upgrade(data).to_string(), @@ -438,7 +422,7 @@ impl CosmosSdkChain { true, ))?; - let proof = response.proof.ok_or(Kind::EmptyResponseProof)?; + let proof = response.proof.ok_or_else(Error::empty_response_proof)?; let height = ICSHeight::new( self.config.id.version(), @@ -457,22 +441,21 @@ impl CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(|e| Kind::Grpc.context(e))?; + .map_err(Error::grpc_transport)?; let request = tonic::Request::new(request); let response = self .block_on(client.simulate(request)) - .map_err(|e| Kind::Grpc.context(e))? + .map_err(Error::grpc_status)? .into_inner(); Ok(response) } fn key(&self) -> Result { - Ok(self - .keybase() + self.keybase() .get_key(&self.config.key_name) - .map_err(|e| Kind::KeyBase.context(e))?) + .map_err(Error::key_base) } fn key_bytes(&self, key: &KeyEntry) -> Result, Error> { @@ -489,9 +472,7 @@ impl CosmosSdkChain { fn account(&mut self) -> Result<&mut BaseAccount, Error> { if self.account == None { - let account = self - .block_on(query_account(self, self.key()?.account)) - .map_err(|e| Kind::Grpc.context(e))?; + let account = self.block_on(query_account(self, self.key()?.account))?; debug!( sequence = %account.sequence, @@ -580,7 +561,7 @@ impl CosmosSdkChain { let signed = self .keybase .sign_msg(&self.config.key_name, signdoc_buf) - .map_err(|e| Kind::KeyBase.context(e))?; + .map_err(Error::key_base)?; Ok(signed) } @@ -654,12 +635,7 @@ impl CosmosSdkChain { // All transactions confirmed Ok(()) => Ok(tx_sync_results), // Did not find confirmation - Err(_) => Err(Kind::TxNoConfirmation(format!( - "from chain {} for hash(es) {}", - self.id(), - hashes - )) - .into()), + Err(_) => Err(Error::tx_no_confirmation()), } } } @@ -682,14 +658,14 @@ impl Chain for CosmosSdkChain { fn bootstrap(config: ChainConfig, rt: Arc) -> Result { let rpc_client = HttpClient::new(config.rpc_addr.clone()) - .map_err(|e| Kind::Rpc(config.rpc_addr.clone()).context(e))?; + .map_err(|e| Error::rpc(config.rpc_addr.clone(), e))?; // Initialize key store and load key let keybase = KeyRing::new(Store::Test, &config.account_prefix, &config.id) - .map_err(|e| Kind::KeyBase.context(e))?; + .map_err(Error::key_base)?; - let grpc_addr = - Uri::from_str(&config.grpc_addr.to_string()).map_err(|e| Kind::Grpc.context(e))?; + let grpc_addr = Uri::from_str(&config.grpc_addr.to_string()) + .map_err(|e| Error::invalid_uri(config.grpc_addr.to_string(), e))?; let chain = Self { config, @@ -714,7 +690,7 @@ impl Chain for CosmosSdkChain { .rt .block_on(self.rpc_client.status()) .map(|s| s.node_info.id) - .map_err(|e| Kind::Rpc(self.config.rpc_addr.clone()).context(e))?; + .map_err(|e| Error::rpc(self.config.rpc_addr.clone(), e))?; let light_client = TmLightClient::from_config(&self.config, peer_id)?; @@ -732,9 +708,9 @@ impl Chain for CosmosSdkChain { self.config.websocket_addr.clone(), rt, ) - .map_err(Kind::EventMonitor)?; + .map_err(Error::event_monitor)?; - event_monitor.subscribe().map_err(Kind::EventMonitor)?; + event_monitor.subscribe().map_err(Error::event_monitor)?; thread::spawn(move || event_monitor.run()); @@ -822,7 +798,7 @@ impl Chain for CosmosSdkChain { let key = self .keybase() .get_key(&self.config.key_name) - .map_err(|e| Kind::KeyBase.context(e))?; + .map_err(Error::key_base)?; let bech32 = encode_to_bech32(&key.address.to_hex(), &self.config.account_prefix)?; Ok(Signer::new(bech32)) @@ -836,7 +812,7 @@ impl Chain for CosmosSdkChain { let key = self .keybase() .get_key(&self.config.key_name) - .map_err(|e| Kind::KeyBase.context(e))?; + .map_err(Error::key_base)?; Ok(key) } @@ -856,15 +832,13 @@ impl Chain for CosmosSdkChain { let status = self .block_on(self.rpc_client().status()) - .map_err(|e| Kind::Rpc(self.config.rpc_addr.clone()).context(e))?; + .map_err(|e| Error::rpc(self.config.rpc_addr.clone(), e))?; if status.sync_info.catching_up { - fail!( - Kind::LightClient(self.config.rpc_addr.to_string()), - "node at {} running chain {} not caught up", - self.config().rpc_addr, - self.config().id, - ); + return Err(Error::chain_not_caught_up( + self.config.rpc_addr.to_string(), + self.config().id.clone(), + )); } Ok(ICSHeight { @@ -885,12 +859,12 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(|e| Kind::Grpc.context(e))?; + .map_err(Error::grpc_transport)?; let request = tonic::Request::new(request); let response = self .block_on(client.client_states(request)) - .map_err(|e| Kind::Grpc.context(e))? + .map_err(Error::grpc_status)? .into_inner(); // Deserialize into domain type @@ -919,15 +893,9 @@ impl Chain for CosmosSdkChain { let client_state = self .query(ClientStatePath(client_id.clone()), height, false) - .map_err(|e| Kind::Query("client state".into()).context(e)) - .and_then(|v| { - AnyClientState::decode_vec(&v.value) - .map_err(|e| Kind::Query("client state".into()).context(e)) - })?; - let client_state = - downcast!(client_state => AnyClientState::Tendermint).ok_or_else(|| { - Kind::Query("client state".into()).context("unexpected client state type") - })?; + .and_then(|v| AnyClientState::decode_vec(&v.value).map_err(Error::decode))?; + let client_state = downcast!(client_state.clone() => AnyClientState::Tendermint) + .ok_or_else(|| Error::client_state_type(format!("{:?}", client_state)))?; Ok(client_state) } @@ -943,31 +911,28 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(|e| Kind::Grpc.context(e))?; + .map_err(Error::grpc_transport)?; let req = tonic::Request::new(QueryCurrentPlanRequest {}); let response = self .block_on(client.current_plan(req)) - .map_err(|e| Kind::Grpc.context(e))?; + .map_err(Error::grpc_status)?; let upgraded_client_state_raw = response .into_inner() .plan - .ok_or(Kind::EmptyResponseValue)? + .ok_or_else(Error::empty_response_value)? .upgraded_client_state - .ok_or(Kind::EmptyUpgradedClientState)?; - let client_state = AnyClientState::try_from(upgraded_client_state_raw) - .map_err(|e| Kind::Grpc.context(e))?; + .ok_or_else(Error::empty_upgraded_client_state)?; + let client_state = + AnyClientState::try_from(upgraded_client_state_raw).map_err(Error::ics02)?; // TODO: Better error kinds here. - let tm_client_state = - downcast!(client_state => AnyClientState::Tendermint).ok_or_else(|| { - Kind::Query("upgraded client state".into()).context("unexpected client state type") - })?; + let tm_client_state = downcast!(client_state.clone() => AnyClientState::Tendermint) + .ok_or_else(|| Error::client_state_type(format!("{:?}", client_state)))?; // Query for the proof. - let tm_height = - Height::try_from(height.revision_height).map_err(|e| Kind::InvalidHeight.context(e))?; + let tm_height = Height::try_from(height.revision_height).map_err(Error::invalid_height)?; let (proof, _proof_height) = self.query_client_upgrade_proof( ClientUpgradePath::UpgradedClientState(height.revision_height), tm_height, @@ -982,8 +947,7 @@ impl Chain for CosmosSdkChain { ) -> Result<(Self::ConsensusState, MerkleProof), Error> { crate::time!("query_upgraded_consensus_state"); - let tm_height = - Height::try_from(height.revision_height).map_err(|e| Kind::InvalidHeight.context(e))?; + let tm_height = Height::try_from(height.revision_height).map_err(Error::invalid_height)?; let mut client = self .block_on( @@ -991,29 +955,27 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(|e| Kind::Grpc.context(e))?; + .map_err(Error::grpc_transport)?; let req = tonic::Request::new(QueryUpgradedConsensusStateRequest { last_height: tm_height.into(), }); let response = self .block_on(client.upgraded_consensus_state(req)) - .map_err(|e| Kind::Grpc.context(e))?; + .map_err(Error::grpc_status)?; let upgraded_consensus_state_raw = response .into_inner() .upgraded_consensus_state - .ok_or(Kind::EmptyResponseValue)?; + .ok_or_else(Error::empty_response_value)?; // TODO: More explicit error kinds (should not reuse Grpc all over the place) - let consensus_state = AnyConsensusState::try_from(upgraded_consensus_state_raw) - .map_err(|e| Kind::Grpc.context(e))?; + let consensus_state = + AnyConsensusState::try_from(upgraded_consensus_state_raw).map_err(Error::ics02)?; - let tm_consensus_state = downcast!(consensus_state => AnyConsensusState::Tendermint) - .ok_or_else(|| { - Kind::Query("upgraded consensus state".into()) - .context("unexpected consensus state type") - })?; + let tm_consensus_state = + downcast!(consensus_state.clone() => AnyConsensusState::Tendermint) + .ok_or_else(|| Error::client_state_type(format!("{:?}", consensus_state)))?; // Fetch the proof. let (proof, _proof_height) = self.query_client_upgrade_proof( @@ -1037,12 +999,12 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(|e| Kind::Grpc.context(e))?; + .map_err(Error::grpc_transport)?; let request = tonic::Request::new(request); let response = self .block_on(client.consensus_states(request)) - .map_err(|e| Kind::Grpc.context(e))? + .map_err(Error::grpc_status)? .into_inner(); let mut consensus_states: Vec = response @@ -1081,14 +1043,14 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(|e| Kind::Grpc.context(e))?; + .map_err(Error::grpc_transport)?; let request = tonic::Request::new(request); let response = match self.block_on(client.client_connections(request)) { Ok(res) => res.into_inner(), Err(e) if e.code() == tonic::Code::NotFound => return Ok(vec![]), - Err(e) => return Err(Kind::Grpc.context(e).into()), + Err(e) => return Err(Error::grpc_status(e)), }; // TODO: add warnings for any identifiers that fail to parse (below). @@ -1115,13 +1077,13 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(|e| Kind::Grpc.context(e))?; + .map_err(Error::grpc_transport)?; let request = tonic::Request::new(request); let response = self .block_on(client.connections(request)) - .map_err(|e| Kind::Grpc.context(e))? + .map_err(Error::grpc_status)? .into_inner(); // TODO: add warnings for any identifiers that fail to parse (below). @@ -1152,7 +1114,7 @@ impl Chain for CosmosSdkChain { let mut client = connection::query_client::QueryClient::connect(chain.grpc_addr.clone()) .await - .map_err(|e| Kind::Grpc.context(e))?; + .map_err(Error::grpc_transport)?; let mut request = connection::QueryConnectionRequest { connection_id: connection_id.to_string(), @@ -1160,7 +1122,7 @@ impl Chain for CosmosSdkChain { .into_request(); let height_param = MetadataValue::from_str(&height.revision_height.to_string()) - .map_err(|e| Kind::Grpc.context(e))?; + .map_err(Error::invalid_metadata)?; request .metadata_mut() @@ -1168,17 +1130,15 @@ impl Chain for CosmosSdkChain { let response = client.connection(request).await.map_err(|e| { if e.code() == tonic::Code::NotFound { - Kind::ConnectionNotFound(connection_id.clone()).into() + Error::connection_not_found(connection_id.clone()) } else { - Kind::Grpc.context(e) + Error::grpc_status(e) } })?; match response.into_inner().connection { Some(raw_connection) => { - let connection_end = raw_connection - .try_into() - .map_err(|e| Kind::Grpc.context(e))?; + let connection_end = raw_connection.try_into().map_err(Error::ics03)?; Ok(connection_end) } @@ -1187,7 +1147,7 @@ impl Chain for CosmosSdkChain { // the NotFound error code. Nevertheless even if the call is successful, // the connection field may not be present, because in protobuf3 // everything is optional. - Err(Kind::ConnectionNotFound(connection_id.clone()).into()) + Err(Error::connection_not_found(connection_id.clone())) } } } @@ -1207,13 +1167,13 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(|e| Kind::Grpc.context(e))?; + .map_err(Error::grpc_transport)?; let request = tonic::Request::new(request); let response = self .block_on(client.connection_channels(request)) - .map_err(|e| Kind::Grpc.context(e))? + .map_err(Error::grpc_status)? .into_inner(); // TODO: add warnings for any identifiers that fail to parse (below). @@ -1239,13 +1199,13 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(|e| Kind::Grpc.context(e))?; + .map_err(Error::grpc_transport)?; let request = tonic::Request::new(request); let response = self .block_on(client.channels(request)) - .map_err(|e| Kind::Grpc.context(e))? + .map_err(Error::grpc_status)? .into_inner(); let channels = response @@ -1267,9 +1227,7 @@ impl Chain for CosmosSdkChain { height, false, )?; - let channel_end = ChannelEnd::decode_vec(&res.value).map_err(|e| { - Kind::Query(format!("port '{}' & channel '{}'", port_id, channel_id)).context(e) - })?; + let channel_end = ChannelEnd::decode_vec(&res.value).map_err(Error::decode)?; Ok(channel_end) } @@ -1286,13 +1244,13 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(|e| Kind::Grpc.context(e))?; + .map_err(Error::grpc_transport)?; let request = tonic::Request::new(request); let response = self .block_on(client.channel_client_state(request)) - .map_err(|e| Kind::Grpc.context(e))? + .map_err(Error::grpc_status)? .into_inner(); let client_state: Option = response @@ -1315,22 +1273,21 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(|e| Kind::Grpc.context(e))?; + .map_err(Error::grpc_transport)?; let request = tonic::Request::new(request); let response = self .block_on(client.packet_commitments(request)) - .map_err(|e| Kind::Grpc.context(e))? + .map_err(Error::grpc_status)? .into_inner(); let pc = response.commitments; let height = response .height - .ok_or_else(|| Kind::Grpc.context("missing height in response"))? - .try_into() - .map_err(|_| Kind::Grpc.context("invalid height in response"))?; + .ok_or_else(|| Error::grpc_response_param("height".to_string()))? + .into(); Ok((pc, height)) } @@ -1348,13 +1305,13 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(|e| Kind::Grpc.context(e))?; + .map_err(Error::grpc_transport)?; let request = tonic::Request::new(request); let mut response = self .block_on(client.unreceived_packets(request)) - .map_err(|e| Kind::Grpc.context(e))? + .map_err(Error::grpc_status)? .into_inner(); response.sequences.sort_unstable(); @@ -1374,22 +1331,21 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(|e| Kind::Grpc.context(e))?; + .map_err(Error::grpc_transport)?; let request = tonic::Request::new(request); let response = self .block_on(client.packet_acknowledgements(request)) - .map_err(|e| Kind::Grpc.context(e))? + .map_err(Error::grpc_status)? .into_inner(); let pc = response.acknowledgements; let height = response .height - .ok_or_else(|| Kind::Grpc.context("missing height in response"))? - .try_into() - .map_err(|_| Kind::Grpc.context("invalid height in response"))?; + .ok_or_else(|| Error::grpc_response_param("height".to_string()))? + .into(); Ok((pc, height)) } @@ -1407,13 +1363,13 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(|e| Kind::Grpc.context(e))?; + .map_err(Error::grpc_transport)?; let request = tonic::Request::new(request); let mut response = self .block_on(client.unreceived_acks(request)) - .map_err(|e| Kind::Grpc.context(e))? + .map_err(Error::grpc_status)? .into_inner(); response.sequences.sort_unstable(); @@ -1432,13 +1388,13 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(|e| Kind::Grpc.context(e))?; + .map_err(Error::grpc_transport)?; let request = tonic::Request::new(request); let response = self .block_on(client.next_sequence_receive(request)) - .map_err(|e| Kind::Grpc.context(e))? + .map_err(Error::grpc_status)? .into_inner(); Ok(Sequence::from(response.next_sequence_receive)) @@ -1474,7 +1430,7 @@ impl Chain for CosmosSdkChain { 1, // get only the first Tx matching the query Order::Ascending, )) - .map_err(|e| Kind::Grpc.context(e))?; + .map_err(|e| Error::rpc(self.config.rpc_addr.clone(), e))?; assert!( response.txs.len() <= 1, @@ -1514,7 +1470,7 @@ impl Chain for CosmosSdkChain { 1, // get only the first Tx matching the query Order::Ascending, )) - .map_err(|e| Kind::Grpc.context(e))?; + .map_err(|e| Error::rpc(self.config.rpc_addr.clone(), e))?; if response.txs.is_empty() { return Ok(vec![]); @@ -1541,7 +1497,7 @@ impl Chain for CosmosSdkChain { 1, // get only the first Tx matching the query Order::Ascending, )) - .map_err(|e| Kind::Grpc.context(e))?; + .map_err(|e| Error::rpc(self.config.rpc_addr.clone(), e))?; if response.txs.is_empty() { Ok(vec![]) @@ -1560,23 +1516,16 @@ impl Chain for CosmosSdkChain { ) -> Result<(Self::ClientState, MerkleProof), Error> { crate::time!("proven_client_state"); - let res = self - .query(ClientStatePath(client_id.clone()), height, true) - .map_err(|e| Kind::Query("client state".into()).context(e))?; + let res = self.query(ClientStatePath(client_id.clone()), height, true)?; - let client_state = AnyClientState::decode_vec(&res.value) - .map_err(|e| Kind::Query("client state".into()).context(e))?; + let client_state = AnyClientState::decode_vec(&res.value).map_err(Error::decode)?; - let client_state = - downcast!(client_state => AnyClientState::Tendermint).ok_or_else(|| { - Kind::Query("client state".into()).context("unexpected client state type") - })?; + let client_state = downcast!(client_state.clone() => AnyClientState::Tendermint) + .ok_or_else(|| Error::client_state_type(format!("{:?}", client_state)))?; Ok(( client_state, - res.proof.ok_or_else(|| { - Kind::Query("client state".into()).context("empty proof".to_string()) - })?, + res.proof.ok_or_else(Error::empty_response_proof)?, )) } @@ -1588,31 +1537,25 @@ impl Chain for CosmosSdkChain { ) -> Result<(Self::ConsensusState, MerkleProof), Error> { crate::time!("proven_client_consensus"); - let res = self - .query( - ClientConsensusPath { - client_id: client_id.clone(), - epoch: consensus_height.revision_number, - height: consensus_height.revision_height, - }, - height, - true, - ) - .map_err(|e| Kind::Query("client consensus".into()).context(e))?; + let res = self.query( + ClientConsensusPath { + client_id: client_id.clone(), + epoch: consensus_height.revision_number, + height: consensus_height.revision_height, + }, + height, + true, + )?; - let consensus_state = AnyConsensusState::decode_vec(&res.value) - .map_err(|e| Kind::Query("client consensus".into()).context(e))?; + let consensus_state = AnyConsensusState::decode_vec(&res.value).map_err(Error::decode)?; - let consensus_state = downcast!(consensus_state => AnyConsensusState::Tendermint) - .ok_or_else(|| { - Kind::Query("client consensus".into()).context("unexpected client consensus type") - })?; + let consensus_state = + downcast!(consensus_state.clone() => AnyConsensusState::Tendermint) + .ok_or_else(|| Error::client_state_type(format!("{:?}", consensus_state)))?; Ok(( consensus_state, - res.proof.ok_or_else(|| { - Kind::Query("client consensus".into()).context("empty proof".to_string()) - })?, + res.proof.ok_or_else(Error::empty_response_proof)?, )) } @@ -1621,17 +1564,12 @@ impl Chain for CosmosSdkChain { connection_id: &ConnectionId, height: ICSHeight, ) -> Result<(ConnectionEnd, MerkleProof), Error> { - let res = self - .query(Path::Connections(connection_id.clone()), height, true) - .map_err(|e| Kind::Query("proven connection".into()).context(e))?; - let connection_end = ConnectionEnd::decode_vec(&res.value) - .map_err(|e| Kind::Query("proven connection".into()).context(e))?; + let res = self.query(Path::Connections(connection_id.clone()), height, true)?; + let connection_end = ConnectionEnd::decode_vec(&res.value).map_err(Error::decode)?; Ok(( connection_end, - res.proof.ok_or_else(|| { - Kind::Query("proven connection".into()).context("empty proof".to_string()) - })?, + res.proof.ok_or_else(Error::empty_response_proof)?, )) } @@ -1641,22 +1579,17 @@ impl Chain for CosmosSdkChain { channel_id: &ChannelId, height: ICSHeight, ) -> Result<(ChannelEnd, MerkleProof), Error> { - let res = self - .query( - Path::ChannelEnds(port_id.clone(), channel_id.clone()), - height, - true, - ) - .map_err(|e| Kind::Query("proven channel".into()).context(e))?; + let res = self.query( + Path::ChannelEnds(port_id.clone(), channel_id.clone()), + height, + true, + )?; - let channel_end = ChannelEnd::decode_vec(&res.value) - .map_err(|e| Kind::Query("proven channel".into()).context(e))?; + let channel_end = ChannelEnd::decode_vec(&res.value).map_err(Error::decode)?; Ok(( channel_end, - res.proof.ok_or_else(|| { - Kind::Query("proven channel".into()).context("empty proof".to_string()) - })?, + res.proof.ok_or_else(Error::empty_response_proof)?, )) } @@ -1695,20 +1628,16 @@ impl Chain for CosmosSdkChain { }, }; - let res = self - .query(data, height, true) - .map_err(|e| Kind::Query(packet_type.to_string()).context(e))?; + let res = self.query(data, height, true)?; - let commitment_proof_bytes = res.proof.ok_or_else(|| { - Kind::Query(packet_type.to_string()).context("empty proof".to_string()) - })?; + let commitment_proof_bytes = res.proof.ok_or_else(Error::empty_response_proof)?; Ok((res.value, commitment_proof_bytes)) } fn build_client_state(&self, height: ICSHeight) -> Result { // Build the client state. - Ok(ClientState::new( + ClientState::new( self.id().clone(), self.config.trust_threshold, self.config.trusting_period, @@ -1722,7 +1651,7 @@ impl Chain for CosmosSdkChain { after_misbehaviour: true, }, ) - .map_err(|e| Kind::BuildClientStateFailure.context(e))?) + .map_err(Error::ics07) } fn build_consensus_state( @@ -1906,25 +1835,23 @@ async fn abci_query( .rpc_client() .abci_query(Some(path), data.into_bytes(), height, prove) .await - .map_err(|e| Kind::Rpc(chain.config.rpc_addr.clone()).context(e))?; + .map_err(|e| Error::rpc(chain.config.rpc_addr.clone(), e))?; if !response.code.is_ok() { // Fail with response log. - return Err(Kind::Rpc(chain.config.rpc_addr.clone()) - .context(response.log.to_string()) - .into()); + return Err(Error::abci_query(response)); } if prove && response.proof.is_none() { // Fail due to empty proof - return Err(Kind::EmptyResponseProof.into()); + return Err(Error::empty_response_proof()); } let proof = response .proof .map(|p| convert_tm_to_ics_merkle_proof(&p)) .transpose() - .map_err(Kind::Ics023)?; + .map_err(Error::ics23)?; let response = QueryResponse { value: response.value, @@ -1936,15 +1863,12 @@ async fn abci_query( } /// Perform a `broadcast_tx_sync`, and return the corresponding deserialized response data. -async fn broadcast_tx_sync( - chain: &CosmosSdkChain, - data: Vec, -) -> Result> { +async fn broadcast_tx_sync(chain: &CosmosSdkChain, data: Vec) -> Result { let response = chain .rpc_client() .broadcast_tx_sync(data.into()) .await - .map_err(|e| Kind::Rpc(chain.config.rpc_addr.clone()).context(e))?; + .map_err(|e| Error::rpc(chain.config.rpc_addr.clone(), e))?; Ok(response) } @@ -1955,7 +1879,7 @@ async fn query_account(chain: &CosmosSdkChain, address: String) -> Result Result Result { - let account = - AccountId::from_str(address).map_err(|_| Kind::InvalidKeyAddress(address.to_string()))?; + let account = AccountId::from_str(address) + .map_err(|e| Error::invalid_key_address(address.to_string(), e))?; let encoded = bech32::encode(account_prefix, account.to_base32(), Variant::Bech32) - .map_err(Kind::Bech32Encoding)?; + .map_err(Error::bech32_encoding)?; Ok(encoded) } diff --git a/relayer/src/chain/counterparty.rs b/relayer/src/chain/counterparty.rs index 1a2cd901c3..a68bc08a2c 100644 --- a/relayer/src/chain/counterparty.rs +++ b/relayer/src/chain/counterparty.rs @@ -25,12 +25,12 @@ pub fn counterparty_chain_from_connection( ) -> Result { let connection_end = src_chain .query_connection(&src_connection_id, Height::zero()) - .map_err(|e| Error::QueryFailed(format!("{}", e)))?; + .map_err(Error::relayer)?; let client_id = connection_end.client_id(); let client_state = src_chain .query_client_state(&client_id, Height::zero()) - .map_err(|e| Error::QueryFailed(format!("{}", e)))?; + .map_err(Error::relayer)?; trace!( chain_id=%src_chain.id(), connection_id=%src_connection_id, @@ -48,20 +48,14 @@ fn connection_on_destination( client_id: counterparty_client_id.to_string(), }; - let counterparty_connections = - counterparty_chain - .query_client_connections(req) - .map_err(|e| { - Error::QueryFailed(format!( - "counterparty::query_client_connections({}) failed with error: {}", - counterparty_client_id, e - )) - })?; + let counterparty_connections = counterparty_chain + .query_client_connections(req) + .map_err(Error::relayer)?; for counterparty_connection in counterparty_connections.into_iter() { let counterparty_connection_end = counterparty_chain .query_connection(&counterparty_connection, Height::zero()) - .map_err(|e| Error::QueryFailed(format!("{}", e)))?; + .map_err(Error::relayer)?; let local_connection_end = &counterparty_connection_end.counterparty(); if let Some(local_connection_id) = local_connection_end.connection_id() { @@ -80,7 +74,7 @@ pub fn connection_state_on_destination( if let Some(remote_connection_id) = connection.connection_end.counterparty().connection_id() { let connection_end = counterparty_chain .query_connection(remote_connection_id, Height::zero()) - .map_err(|e| Error::QueryFailed(format!("{}", e)))?; + .map_err(Error::relayer)?; Ok(connection_end.state) } else { @@ -131,10 +125,10 @@ pub fn channel_connection_client( ) -> Result { let channel_end = chain .query_channel(port_id, channel_id, Height::zero()) - .map_err(|e| Error::QueryFailed(format!("{}", e)))?; + .map_err(Error::relayer)?; if channel_end.state_matches(&State::Uninitialized) { - return Err(Error::ChannelUninitialized( + return Err(Error::channel_uninitialized( port_id.clone(), channel_id.clone(), chain.id(), @@ -144,14 +138,14 @@ pub fn channel_connection_client( let connection_id = channel_end .connection_hops() .first() - .ok_or_else(|| Error::MissingConnectionHops(channel_id.clone(), chain.id()))?; + .ok_or_else(|| Error::missing_connection_hops(channel_id.clone(), chain.id()))?; let connection_end = chain .query_connection(connection_id, Height::zero()) - .map_err(|e| Error::QueryFailed(format!("{}", e)))?; + .map_err(Error::relayer)?; if !connection_end.is_open() { - return Err(Error::ConnectionNotOpen( + return Err(Error::connection_not_open( connection_id.clone(), channel_id.clone(), chain.id(), @@ -161,7 +155,7 @@ pub fn channel_connection_client( let client_id = connection_end.client_id(); let client_state = chain .query_client_state(client_id, Height::zero()) - .map_err(|e| Error::QueryFailed(format!("{}", e)))?; + .map_err(Error::relayer)?; let client = IdentifiedAnyClientState::new(client_id.clone(), client_state); let connection = IdentifiedConnectionEnd::new(connection_id.clone(), connection_end); @@ -192,7 +186,7 @@ fn fetch_channel_on_destination( let counterparty_channels = counterparty_chain .query_connection_channels(req) - .map_err(|e| Error::QueryFailed(format!("{}", e)))?; + .map_err(Error::relayer)?; for counterparty_channel in counterparty_channels.into_iter() { let local_channel_end = &counterparty_channel.channel_end.remote; @@ -229,7 +223,7 @@ pub fn channel_on_destination( remote_channel_id, Height::zero(), ) - .map_err(|e| Error::QueryFailed(format!("{}", e)))?; + .map_err(Error::relayer)?; Ok(Some(counterparty)) } else if let Some(remote_connection_id) = connection.end().counterparty().connection_id() { @@ -259,7 +253,7 @@ pub fn check_channel_counterparty( &target_pchan.channel_id, Height::zero(), ) - .map_err(|e| ChannelError::QueryError(target_chain.id(), e))?; + .map_err(|e| ChannelError::query(target_chain.id(), e))?; let counterparty = channel_end_dst.remote; match counterparty.channel_id { @@ -269,9 +263,9 @@ pub fn check_channel_counterparty( port_id: counterparty.port_id, }; if &actual != expected { - return Err(ChannelError::MismatchingChannelEnds( - target_pchan.clone(), + return Err(ChannelError::mismatch_channel_ends( target_chain.id(), + target_pchan.clone(), expected.clone(), actual, )); @@ -283,9 +277,9 @@ pub fn check_channel_counterparty( target_pchan, target_chain.id() ); - return Err(ChannelError::IncompleteChannelState( - target_pchan.clone(), + return Err(ChannelError::incomplete_channel_state( target_chain.id(), + target_pchan.clone(), )); } } diff --git a/relayer/src/chain/handle/prod.rs b/relayer/src/chain/handle/prod.rs index 41c93453e3..bc7f526836 100644 --- a/relayer/src/chain/handle/prod.rs +++ b/relayer/src/chain/handle/prod.rs @@ -35,11 +35,7 @@ use ibc_proto::ibc::core::commitment::v1::MerkleProof; use ibc_proto::ibc::core::connection::v1::QueryClientConnectionsRequest; use ibc_proto::ibc::core::connection::v1::QueryConnectionsRequest; -use crate::{ - connection::ConnectionMsgType, - error::{Error, Kind}, - keyring::KeyEntry, -}; +use crate::{connection::ConnectionMsgType, error::Error, keyring::KeyEntry}; use super::{reply_channel, ChainHandle, ChainRequest, ReplyTo, Subscription}; @@ -68,11 +64,9 @@ impl ProdChainHandle { let (sender, receiver) = reply_channel(); let input = f(sender); - self.runtime_sender - .send(input) - .map_err(|e| Kind::Channel.context(e))?; + self.runtime_sender.send(input).map_err(Error::send)?; - receiver.recv().map_err(|e| Kind::Channel.context(e))? + receiver.recv().map_err(Error::channel_receive)? } } diff --git a/relayer/src/chain/mock.rs b/relayer/src/chain/mock.rs index 03defb81d3..f88659b952 100644 --- a/relayer/src/chain/mock.rs +++ b/relayer/src/chain/mock.rs @@ -40,7 +40,7 @@ use ibc_proto::ibc::core::connection::v1::{ use crate::chain::Chain; use crate::config::ChainConfig; -use crate::error::{Error, Kind}; +use crate::error::Error; use crate::event::monitor::{EventReceiver, EventSender, TxMonitorCmd}; use crate::keyring::{KeyEntry, KeyRing}; use crate::light_client::Verified; @@ -110,10 +110,7 @@ impl Chain for MockChain { fn send_msgs(&mut self, proto_msgs: Vec) -> Result, Error> { // Use the ICS18Context interface to submit the set of messages. - let events = self - .context - .send(proto_msgs) - .map_err(|e| Kind::Rpc(self.config.rpc_addr.clone()).context(e))?; + let events = self.context.send(proto_msgs).map_err(Error::ics18)?; Ok(events) } @@ -150,10 +147,9 @@ impl Chain for MockChain { let any_state = self .context .query_client_full_state(client_id) - .ok_or(Kind::EmptyResponseValue)?; - let client_state = downcast!(any_state => AnyClientState::Tendermint).ok_or_else(|| { - Kind::Query("client state".into()).context("unexpected client state type") - })?; + .ok_or_else(Error::empty_response_value)?; + let client_state = downcast!(any_state.clone() => AnyClientState::Tendermint) + .ok_or_else(|| Error::client_state_type(format!("{:?}", any_state)))?; Ok(client_state) } @@ -315,7 +311,7 @@ impl Chain for MockChain { after_misbehaviour: false, }, ) - .map_err(|e| Kind::BuildClientStateFailure.context(e))?; + .map_err(Error::ics07)?; Ok(client_state) } diff --git a/relayer/src/chain/runtime.rs b/relayer/src/chain/runtime.rs index 31e3588cb4..8550267498 100644 --- a/relayer/src/chain/runtime.rs +++ b/relayer/src/chain/runtime.rs @@ -43,7 +43,7 @@ use ibc_proto::ibc::core::{ use crate::{ config::ChainConfig, connection::ConnectionMsgType, - error::{Error, Kind}, + error::Error, event::{ bus::EventBus, monitor::{EventBatch, EventReceiver, MonitorCmd, Result as MonitorResult, TxMonitorCmd}, @@ -175,17 +175,19 @@ impl ChainRuntime { }, Err(e) => { error!("received error via event bus: {}", e); - return Err(Kind::Channel.into()); + return Err(Error::channel_receive(e)); }, } }, recv(self.request_receiver) -> event => { match event { Ok(ChainRequest::Shutdown { reply_to }) => { - self.tx_monitor_cmd.send(MonitorCmd::Shutdown).map_err(Kind::channel)?; + self.tx_monitor_cmd.send(MonitorCmd::Shutdown) + .map_err(Error::send)?; let res = self.chain.shutdown(); - reply_to.send(res).map_err(Kind::channel)?; + reply_to.send(res) + .map_err(Error::send)?; break; } @@ -350,7 +352,7 @@ impl ChainRuntime { fn subscribe(&mut self, reply_to: ReplyTo) -> Result<(), Error> { let subscription = self.event_bus.subscribe(); - reply_to.send(Ok(subscription)).map_err(Kind::channel)?; + reply_to.send(Ok(subscription)).map_err(Error::send)?; Ok(()) } @@ -362,7 +364,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.send_msgs(proto_msgs); - reply_to.send(result).map_err(Kind::channel)?; + reply_to.send(result).map_err(Error::send)?; Ok(()) } @@ -370,7 +372,7 @@ impl ChainRuntime { fn query_latest_height(&self, reply_to: ReplyTo) -> Result<(), Error> { let latest_height = self.chain.query_latest_height(); - reply_to.send(latest_height).map_err(Kind::channel)?; + reply_to.send(latest_height).map_err(Error::send)?; Ok(()) } @@ -378,7 +380,7 @@ impl ChainRuntime { fn get_signer(&mut self, reply_to: ReplyTo) -> Result<(), Error> { let result = self.chain.get_signer(); - reply_to.send(result).map_err(Kind::channel)?; + reply_to.send(result).map_err(Error::send)?; Ok(()) } @@ -386,7 +388,7 @@ impl ChainRuntime { fn get_key(&mut self, reply_to: ReplyTo) -> Result<(), Error> { let result = self.chain.get_key(); - reply_to.send(result).map_err(Kind::channel)?; + reply_to.send(result).map_err(Error::send)?; Ok(()) } @@ -394,7 +396,7 @@ impl ChainRuntime { fn module_version(&self, port_id: PortId, reply_to: ReplyTo) -> Result<(), Error> { let result = self.chain.query_module_version(&port_id); - reply_to.send(Ok(result)).map_err(Kind::channel)?; + reply_to.send(Ok(result)).map_err(Error::send)?; Ok(()) } @@ -420,7 +422,7 @@ impl ChainRuntime { (header, support) }); - reply_to.send(result).map_err(Kind::channel)?; + reply_to.send(result).map_err(Error::send)?; Ok(()) } @@ -436,7 +438,7 @@ impl ChainRuntime { .build_client_state(height) .map(|cs| cs.wrap_any()); - reply_to.send(client_state).map_err(Kind::channel)?; + reply_to.send(client_state).map_err(Error::send)?; Ok(()) } @@ -456,7 +458,7 @@ impl ChainRuntime { .build_consensus_state(verified.target) .map(|cs| cs.wrap_any()); - reply_to.send(consensus_state).map_err(Kind::channel)?; + reply_to.send(consensus_state).map_err(Error::send)?; Ok(()) } @@ -472,7 +474,7 @@ impl ChainRuntime { .light_client .check_misbehaviour(update_event, &client_state); - reply_to.send(misbehaviour).map_err(Kind::channel)?; + reply_to.send(misbehaviour).map_err(Error::send)?; Ok(()) } @@ -495,7 +497,7 @@ impl ChainRuntime { let result = result .map(|(opt_client_state, proofs)| (opt_client_state.map(|cs| cs.wrap_any()), proofs)); - reply_to.send(result).map_err(Kind::channel)?; + reply_to.send(result).map_err(Error::send)?; Ok(()) } @@ -507,7 +509,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.query_clients(request); - reply_to.send(result).map_err(Kind::channel)?; + reply_to.send(result).map_err(Error::send)?; Ok(()) } @@ -519,7 +521,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.query_client_connections(request); - reply_to.send(result).map_err(Kind::channel)?; + reply_to.send(result).map_err(Error::send)?; Ok(()) } @@ -535,7 +537,7 @@ impl ChainRuntime { .query_client_state(&client_id, height) .map(|cs| cs.wrap_any()); - reply_to.send(client_state).map_err(Kind::channel)?; + reply_to.send(client_state).map_err(Error::send)?; Ok(()) } @@ -550,7 +552,7 @@ impl ChainRuntime { .query_upgraded_client_state(height) .map(|(cl, proof)| (cl.wrap_any(), proof)); - reply_to.send(result).map_err(Kind::channel)?; + reply_to.send(result).map_err(Error::send)?; Ok(()) } @@ -562,7 +564,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let consensus_states = self.chain.query_consensus_states(request); - reply_to.send(consensus_states).map_err(Kind::channel)?; + reply_to.send(consensus_states).map_err(Error::send)?; Ok(()) } @@ -578,7 +580,7 @@ impl ChainRuntime { self.chain .query_consensus_state(client_id, consensus_height, query_height); - reply_to.send(consensus_state).map_err(Kind::channel)?; + reply_to.send(consensus_state).map_err(Error::send)?; Ok(()) } @@ -593,7 +595,7 @@ impl ChainRuntime { .query_upgraded_consensus_state(height) .map(|(cs, proof)| (cs.wrap_any(), proof)); - reply_to.send(result).map_err(Kind::channel)?; + reply_to.send(result).map_err(Error::send)?; Ok(()) } @@ -601,7 +603,7 @@ impl ChainRuntime { fn query_commitment_prefix(&self, reply_to: ReplyTo) -> Result<(), Error> { let prefix = self.chain.query_commitment_prefix(); - reply_to.send(prefix).map_err(Kind::channel)?; + reply_to.send(prefix).map_err(Error::send)?; Ok(()) } @@ -609,7 +611,7 @@ impl ChainRuntime { fn query_compatible_versions(&self, reply_to: ReplyTo>) -> Result<(), Error> { let versions = self.chain.query_compatible_versions(); - reply_to.send(versions).map_err(Kind::channel)?; + reply_to.send(versions).map_err(Error::send)?; Ok(()) } @@ -622,7 +624,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let connection_end = self.chain.query_connection(&connection_id, height); - reply_to.send(connection_end).map_err(Kind::channel)?; + reply_to.send(connection_end).map_err(Error::send)?; Ok(()) } @@ -634,7 +636,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.query_connections(request); - reply_to.send(result).map_err(Kind::channel)?; + reply_to.send(result).map_err(Error::send)?; Ok(()) } @@ -646,9 +648,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.query_connection_channels(request); - reply_to - .send(result) - .map_err(|e| Kind::Channel.context(e))?; + reply_to.send(result).map_err(Error::send)?; Ok(()) } @@ -660,7 +660,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.query_channels(request); - reply_to.send(result).map_err(Kind::channel)?; + reply_to.send(result).map_err(Error::send)?; Ok(()) } @@ -674,7 +674,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.query_channel(&port_id, &channel_id, height); - reply_to.send(result).map_err(Kind::channel)?; + reply_to.send(result).map_err(Error::send)?; Ok(()) } @@ -686,7 +686,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.query_channel_client_state(request); - reply_to.send(result).map_err(Kind::channel)?; + reply_to.send(result).map_err(Error::send)?; Ok(()) } @@ -702,7 +702,7 @@ impl ChainRuntime { .proven_client_state(&client_id, height) .map(|(cs, mp)| (cs.wrap_any(), mp)); - reply_to.send(result).map_err(Kind::channel)?; + reply_to.send(result).map_err(Error::send)?; Ok(()) } @@ -715,7 +715,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.proven_connection(&connection_id, height); - reply_to.send(result).map_err(Kind::channel)?; + reply_to.send(result).map_err(Error::send)?; Ok(()) } @@ -732,7 +732,7 @@ impl ChainRuntime { .proven_client_consensus(&client_id, consensus_height, height) .map(|(cs, mp)| (cs.wrap_any(), mp)); - reply_to.send(result).map_err(Kind::channel)?; + reply_to.send(result).map_err(Error::send)?; Ok(()) } @@ -748,7 +748,7 @@ impl ChainRuntime { .chain .build_channel_proofs(&port_id, &channel_id, height); - reply_to.send(result).map_err(Kind::channel)?; + reply_to.send(result).map_err(Error::send)?; Ok(()) } @@ -766,7 +766,7 @@ impl ChainRuntime { self.chain .build_packet_proofs(packet_type, port_id, channel_id, sequence, height); - reply_to.send(result).map_err(Kind::channel)?; + reply_to.send(result).map_err(Error::send)?; Ok(()) } @@ -778,7 +778,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.query_packet_commitments(request); - reply_to.send(result).map_err(Kind::channel)?; + reply_to.send(result).map_err(Error::send)?; Ok(()) } @@ -790,7 +790,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.query_unreceived_packets(request); - reply_to.send(result).map_err(Kind::channel)?; + reply_to.send(result).map_err(Error::send)?; Ok(()) } @@ -802,7 +802,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.query_packet_acknowledgements(request); - reply_to.send(result).map_err(Kind::channel)?; + reply_to.send(result).map_err(Error::send)?; Ok(()) } @@ -814,7 +814,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.query_unreceived_acknowledgements(request); - reply_to.send(result).map_err(Kind::channel)?; + reply_to.send(result).map_err(Error::send)?; Ok(()) } @@ -826,7 +826,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.query_next_sequence_receive(request); - reply_to.send(result).map_err(Kind::channel)?; + reply_to.send(result).map_err(Error::send)?; Ok(()) } @@ -838,7 +838,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.query_txs(request); - reply_to.send(result).map_err(Kind::channel)?; + reply_to.send(result).map_err(Error::send)?; Ok(()) } diff --git a/relayer/src/channel.rs b/relayer/src/channel.rs index a510b27324..fc2475f110 100644 --- a/relayer/src/channel.rs +++ b/relayer/src/channel.rs @@ -1,10 +1,8 @@ #![allow(clippy::borrowed_box)] -use std::time::Duration; - -use anomaly::BoxError; use prost_types::Any; use serde::Serialize; +use std::time::Duration; use tracing::{debug, error, info, warn}; use ibc::events::IbcEvent; @@ -25,11 +23,11 @@ use crate::chain::handle::ChainHandle; use crate::connection::Connection; use crate::foreign_client::ForeignClient; use crate::object::Channel as WorkerChannelObject; -use crate::supervisor::Error as WorkerChannelError; +use crate::supervisor::error::Error as SupervisorError; +use crate::util::retry::retry_with_index; use crate::util::retry::RetryResult; -use crate::util::retry::{retry_count, retry_with_index}; -mod error; +pub mod error; pub use error::ChannelError; mod retry_strategy { @@ -49,6 +47,25 @@ mod retry_strategy { } } +pub fn from_retry_error(e: retry::Error, description: String) -> ChannelError { + match e { + retry::Error::Operation { + error, + total_delay, + tries, + } => { + let detail = error::ChannelErrorDetail::MaxRetry(error::MaxRetrySubdetail { + description, + tries, + total_delay, + source: Box::new(error.0), + }); + ChannelError(detail, error.1) + } + retry::Error::Internal(reason) => ChannelError::retry_internal(reason), + } +} + #[derive(Clone, Debug, Serialize)] pub struct ChannelSide { pub chain: Box, @@ -119,15 +136,15 @@ impl Channel { let version = version.unwrap_or( b_side_chain .module_version(&a_port) - .map_err(|e| ChannelError::QueryError(b_side_chain.id(), e))?, + .map_err(|e| ChannelError::query(b_side_chain.id(), e))?, ); let src_connection_id = connection .src_connection_id() - .ok_or_else(|| ChannelError::MissingLocalConnection(connection.src_chain().id()))?; + .ok_or_else(|| ChannelError::missing_local_connection(connection.src_chain().id()))?; let dst_connection_id = connection .dst_connection_id() - .ok_or_else(|| ChannelError::MissingLocalConnection(connection.dst_chain().id()))?; + .ok_or_else(|| ChannelError::missing_local_connection(connection.dst_chain().id()))?; let mut channel = Self { ordering, @@ -158,29 +175,28 @@ impl Channel { chain: Box, counterparty_chain: Box, channel_open_event: IbcEvent, - ) -> Result { - let channel_event_attributes = - channel_open_event.channel_attributes().ok_or_else(|| { - ChannelError::Failed(format!( - "a channel object cannot be built from {}", - channel_open_event - )) - })?; + ) -> Result { + let channel_event_attributes = channel_open_event + .channel_attributes() + .ok_or_else(|| ChannelError::invalid_event(channel_open_event.clone()))?; let port_id = channel_event_attributes.port_id.clone(); let channel_id = channel_event_attributes.channel_id.clone(); let version = counterparty_chain .module_version(&port_id) - .map_err(|e| ChannelError::QueryError(counterparty_chain.id(), e))?; + .map_err(|e| ChannelError::query(counterparty_chain.id(), e))?; let connection_id = channel_event_attributes.connection_id.clone(); - let connection = chain.query_connection(&connection_id, Height::zero())?; + let connection = chain + .query_connection(&connection_id, Height::zero()) + .map_err(ChannelError::relayer)?; + let connection_counterparty = connection.counterparty(); let counterparty_connection_id = connection_counterparty .connection_id() - .ok_or(ChannelError::MissingCounterpartyConnection)?; + .ok_or_else(ChannelError::missing_counterparty_connection)?; Ok(Channel { // The event does not include the channel ordering. @@ -215,25 +231,32 @@ impl Channel { counterparty_chain: Box, channel: WorkerChannelObject, height: Height, - ) -> Result<(Channel, State), BoxError> { - let a_channel = - chain.query_channel(&channel.src_port_id, &channel.src_channel_id, height)?; + ) -> Result<(Channel, State), ChannelError> { + let a_channel = chain + .query_channel(&channel.src_port_id, &channel.src_channel_id, height) + .map_err(ChannelError::relayer)?; let a_connection_id = a_channel.connection_hops().first().ok_or_else(|| { - WorkerChannelError::MissingConnectionHops(channel.src_channel_id.clone(), chain.id()) + ChannelError::supervisor(SupervisorError::missing_connection_hops( + channel.src_channel_id.clone(), + chain.id(), + )) })?; - let a_connection = chain.query_connection(a_connection_id, Height::zero())?; + let a_connection = chain + .query_connection(a_connection_id, Height::zero()) + .map_err(ChannelError::relayer)?; + let b_connection_id = a_connection .counterparty() .connection_id() .cloned() .ok_or_else(|| { - WorkerChannelError::ChannelConnectionUninitialized( + ChannelError::supervisor(SupervisorError::channel_connection_uninitialized( channel.src_channel_id.clone(), chain.id(), a_connection.counterparty().clone(), - ) + )) })?; let mut handshake_channel = Channel { @@ -262,8 +285,9 @@ impl Channel { pagination: ibc_proto::cosmos::base::query::pagination::all(), }; - let channels: Vec = - counterparty_chain.query_connection_channels(req)?; + let channels: Vec = counterparty_chain + .query_connection_channels(req) + .map_err(ChannelError::relayer)?; for chan in channels { if let Some(remote_channel_id) = chan.channel_end.remote.channel_id() { @@ -329,13 +353,7 @@ impl Channel { } fn do_chan_open_init_and_send(&mut self) -> Result<(), ChannelError> { - let event = self - .flipped() - .build_chan_open_init_and_send() - .map_err(|e| { - error!("Failed ChanInit {:?}: {:?}", self.a_side, e); - e - })?; + let event = self.flipped().build_chan_open_init_and_send()?; info!("done {} => {:#?}\n", self.src_chain().id(), event); @@ -353,11 +371,11 @@ impl Channel { }) .map_err(|err| { error!("failed to open channel after {} retries", err); - ChannelError::Failed(format!( - "Failed to finish channel open init in {} iterations for {:?}", - retry_count(&err), - self - )) + + from_retry_error( + err, + format!("Failed to finish channel open init for {:?}", self), + ) })?; Ok(()) @@ -382,11 +400,11 @@ impl Channel { }) .map_err(|err| { error!("failed to open channel after {} retries", err); - ChannelError::Failed(format!( - "Failed to finish channel open try in {} iterations for {:?}", - retry_count(&err), - self - )) + + from_retry_error( + err, + format!("Failed to finish channel open try for {:?}", self), + ) })?; Ok(()) @@ -410,11 +428,11 @@ impl Channel { fn query_channel_states(channel: &Channel) -> Result<(State, State), ChannelError> { let src_channel_id = channel .src_channel_id() - .ok_or(ChannelError::MissingLocalChannelId)?; + .ok_or_else(ChannelError::missing_local_channel_id)?; let dst_channel_id = channel .dst_channel_id() - .ok_or(ChannelError::MissingCounterpartyChannelId)?; + .ok_or_else(ChannelError::missing_counterparty_connection)?; debug!( "do_chan_open_finalize for src_channel_id: {}, dst_channel_id: {}", @@ -426,11 +444,11 @@ impl Channel { .src_chain() .query_channel(&channel.src_port_id(), src_channel_id, Height::zero()) .map_err(|e| { - ChannelError::HandshakeFinalize( + ChannelError::handshake_finalize( channel.src_port_id().clone(), src_channel_id.clone(), channel.src_chain().id(), - e.to_string(), + e, ) })?; @@ -438,11 +456,11 @@ impl Channel { .dst_chain() .query_channel(&channel.dst_port_id(), dst_channel_id, Height::zero()) .map_err(|e| { - ChannelError::HandshakeFinalize( + ChannelError::handshake_finalize( channel.dst_port_id().clone(), dst_channel_id.clone(), channel.dst_chain().id(), - e.to_string(), + e, ) })?; @@ -464,7 +482,7 @@ impl Channel { // One more step (confirm) left. // Returning error signals that the caller should retry. - Err(ChannelError::PartialOpenHandshake(a2, b2)) + Err(ChannelError::partial_open_handshake(a1, b1)) } } @@ -541,11 +559,10 @@ impl Channel { retry_with_index(retry_strategy::default(), |_| self.do_chan_open_finalize()).map_err( |err| { error!("failed to open channel after {} retries", err); - ChannelError::Failed(format!( - "Failed to finish channel handshake in {} iterations for {:?}", - retry_count(&err), - self - )) + from_retry_error( + err, + format!("Failed to finish channel handshake for {:?}", self), + ) }, )?; @@ -563,28 +580,18 @@ impl Channel { // Source channel ID must be specified let channel_id = self .src_channel_id() - .ok_or(ChannelError::MissingLocalChannelId)?; + .ok_or_else(ChannelError::missing_local_channel_id)?; let channel_deps = channel_connection_client(self.src_chain().as_ref(), self.src_port_id(), channel_id) - .map_err(|_| { - ChannelError::Failed(format!( - "failed to query the channel dependecies for {}", - channel_id - )) - })?; + .map_err(|e| ChannelError::query_channel(channel_id.clone(), e))?; channel_state_on_destination( &channel_deps.channel, &channel_deps.connection, self.dst_chain().as_ref(), ) - .map_err(|_| { - ChannelError::Failed(format!( - "failed to query the channel state on destination for {}", - channel_id - )) - }) + .map_err(|e| ChannelError::query_channel(channel_id.clone(), e)) } pub fn handshake_step(&mut self, state: State) -> Result, ChannelError> { @@ -633,7 +640,7 @@ impl Channel { ); client.build_update_client(height).map_err(|e| { - ChannelError::ClientOperation(self.dst_client_id().clone(), self.dst_chain().id(), e) + ChannelError::client_operation(self.dst_client_id().clone(), self.dst_chain().id(), e) }) } @@ -642,47 +649,28 @@ impl Channel { /// Note: This query is currently not available and it is hardcoded in the `module_version()` /// to be `ics20-1` for `transfer` port. pub fn dst_version(&self) -> Result { - Ok(self.version.clone() - .unwrap_or( - self - .dst_chain() - .module_version(self.dst_port_id()) - .map_err(|e| { - ChannelError::Failed(format!( - "failed while getting the module version from dst chain ({}) with error: {}", - self.dst_chain().id(), - e - )) - })? - )) + Ok(self.version.clone().unwrap_or( + self.dst_chain() + .module_version(self.dst_port_id()) + .map_err(|e| ChannelError::query(self.dst_chain().id(), e))?, + )) } /// Returns the channel version if already set, otherwise it queries the source chain /// for the source port's version. pub fn src_version(&self) -> Result { - Ok(self.version.clone() - .unwrap_or( - self - .src_chain() - .module_version(self.src_port_id()) - .map_err(|e| { - ChannelError::Failed(format!( - "failed while getting the module version from src chain ({}) with error: {}", - self.src_chain().id(), - e - )) - })? - )) + Ok(self.version.clone().unwrap_or( + self.src_chain() + .module_version(self.src_port_id()) + .map_err(|e| ChannelError::query(self.src_chain().id(), e))?, + )) } pub fn build_chan_open_init(&self) -> Result, ChannelError> { - let signer = self.dst_chain().get_signer().map_err(|e| { - ChannelError::Failed(format!( - "failed while fetching the signer for dst chain ({}) with error: {}", - self.dst_chain().id(), - e - )) - })?; + let signer = self + .dst_chain() + .get_signer() + .map_err(|e| ChannelError::query(self.dst_chain().id(), e))?; let counterparty = Counterparty::new(self.src_port_id().clone(), None); @@ -710,7 +698,7 @@ impl Channel { let events = self .dst_chain() .send_msgs(dst_msgs) - .map_err(|e| ChannelError::SubmitError(self.dst_chain().id(), e))?; + .map_err(|e| ChannelError::submit(self.dst_chain().id(), e))?; // Find the relevant event for channel open init let result = events @@ -720,18 +708,13 @@ impl Channel { || matches!(event, IbcEvent::ChainError(_)) }) .ok_or_else(|| { - ChannelError::Failed("no chan init event was in the response".to_string()) + ChannelError::missing_event("no chan init event was in the response".to_string()) })?; match result { IbcEvent::OpenInitChannel(_) => Ok(result), - IbcEvent::ChainError(e) => { - Err(ChannelError::Failed(format!("tx response error: {}", e))) - } - _ => Err(ChannelError::Failed(format!( - "unexpected IBC event: {}", - result - ))), + IbcEvent::ChainError(e) => Err(ChannelError::tx_response(e)), + _ => Err(ChannelError::invalid_event(result)), } } @@ -746,7 +729,7 @@ impl Channel { // Destination channel ID must be specified let dst_channel_id = self .dst_channel_id() - .ok_or(ChannelError::MissingCounterpartyChannelId)?; + .ok_or_else(ChannelError::missing_counterparty_channel_id)?; // If there is a channel present on the destination chain, it should look like this: let counterparty = @@ -772,14 +755,12 @@ impl Channel { let dst_channel = self .dst_chain() .query_channel(self.dst_port_id(), dst_channel_id, Height::zero()) - .map_err(|e| ChannelError::QueryError(self.dst_chain().id(), e))?; + .map_err(|e| ChannelError::query(self.dst_chain().id(), e))?; // Check if a channel is expected to exist on destination chain // A channel must exist on destination chain for Ack and Confirm Tx-es to succeed if dst_channel.state_matches(&State::Uninitialized) { - return Err(ChannelError::Failed( - "missing channel on destination chain".to_string(), - )); + return Err(ChannelError::missing_channel_on_destination()); } check_destination_channel_state( @@ -795,40 +776,38 @@ impl Channel { // Source channel ID must be specified let src_channel_id = self .src_channel_id() - .ok_or(ChannelError::MissingLocalChannelId)?; + .ok_or_else(ChannelError::missing_local_channel_id)?; // Channel must exist on source let src_channel = self .src_chain() .query_channel(self.src_port_id(), src_channel_id, Height::zero()) - .map_err(|e| ChannelError::QueryError(self.src_chain().id(), e))?; + .map_err(|e| ChannelError::query(self.src_chain().id(), e))?; if src_channel.counterparty().port_id() != self.dst_port_id() { - return Err(ChannelError::Failed(format!( - "channel open try to chain `{}` and destination port `{}` does not match \ - the source chain `{}` counterparty port `{}` for channel_id {}", + return Err(ChannelError::mismatch_port( self.dst_chain().id(), - self.dst_port_id(), + self.dst_port_id().clone(), self.src_chain().id(), - src_channel.counterparty().port_id, - src_channel_id - ))); + src_channel.counterparty().port_id.clone(), + src_channel_id.clone(), + )); } // Connection must exist on destination self.dst_chain() .query_connection(self.dst_connection_id(), Height::zero()) - .map_err(|e| ChannelError::QueryError(self.dst_chain().id(), e))?; + .map_err(|e| ChannelError::query(self.dst_chain().id(), e))?; let query_height = self .src_chain() .query_latest_height() - .map_err(|e| ChannelError::QueryError(self.src_chain().id(), e))?; + .map_err(|e| ChannelError::query(self.src_chain().id(), e))?; let proofs = self .src_chain() .build_channel_proofs(self.src_port_id(), src_channel_id, query_height) - .map_err(|e| ChannelError::Failed(format!("failed to build channel proofs: {}", e)))?; + .map_err(ChannelError::channel_proof)?; // Build message(s) to update client on destination let mut msgs = self.build_update_client_on_dst(proofs.height())?; @@ -845,13 +824,10 @@ impl Channel { ); // Get signer - let signer = self.dst_chain().get_signer().map_err(|e| { - ChannelError::Failed(format!( - "failed while fetching the signer for dst chain ({}) with error: {}", - self.dst_chain().id(), - e - )) - })?; + let signer = self + .dst_chain() + .get_signer() + .map_err(|e| ChannelError::fetch_signer(self.dst_chain().id(), e))?; let previous_channel_id = if src_channel.counterparty().channel_id.is_none() { self.b_side.channel_id.clone() @@ -879,7 +855,7 @@ impl Channel { let events = self .dst_chain() .send_msgs(dst_msgs) - .map_err(|e| ChannelError::SubmitError(self.dst_chain().id(), e))?; + .map_err(|e| ChannelError::submit(self.dst_chain().id(), e))?; // Find the relevant event for channel open try let result = events @@ -889,18 +865,13 @@ impl Channel { || matches!(event, IbcEvent::ChainError(_)) }) .ok_or_else(|| { - ChannelError::Failed("no chan try event was in the response".to_string()) + ChannelError::missing_event("no chan try event was in the response".to_string()) })?; match result { IbcEvent::OpenTryChannel(_) => Ok(result), - IbcEvent::ChainError(e) => { - Err(ChannelError::Failed(format!("tx response error: {}", e))) - } - _ => Err(ChannelError::Failed(format!( - "unexpected IBC event: {}", - result - ))), + IbcEvent::ChainError(e) => Err(ChannelError::tx_response(e)), + _ => Err(ChannelError::invalid_event(result)), } } @@ -908,10 +879,10 @@ impl Channel { // Source and destination channel IDs must be specified let src_channel_id = self .src_channel_id() - .ok_or(ChannelError::MissingLocalChannelId)?; + .ok_or_else(ChannelError::missing_local_channel_id)?; let dst_channel_id = self .dst_channel_id() - .ok_or(ChannelError::MissingCounterpartyChannelId)?; + .ok_or_else(ChannelError::missing_counterparty_channel_id)?; // Check that the destination chain will accept the message self.validated_expected_channel(ChannelMsgType::OpenAck)?; @@ -919,39 +890,31 @@ impl Channel { // Channel must exist on source self.src_chain() .query_channel(self.src_port_id(), src_channel_id, Height::zero()) - .map_err(|e| ChannelError::QueryError(self.src_chain().id(), e))?; + .map_err(|e| ChannelError::query(self.src_chain().id(), e))?; // Connection must exist on destination self.dst_chain() .query_connection(self.dst_connection_id(), Height::zero()) - .map_err(|e| ChannelError::QueryError(self.dst_chain().id(), e))?; + .map_err(|e| ChannelError::query(self.dst_chain().id(), e))?; let query_height = self .src_chain() .query_latest_height() - .map_err(|e| ChannelError::QueryError(self.src_chain().id(), e))?; + .map_err(|e| ChannelError::query(self.src_chain().id(), e))?; let proofs = self .src_chain() .build_channel_proofs(self.src_port_id(), src_channel_id, query_height) - .map_err(|e| { - ChannelError::Failed(format!( - "failed while building the channel proofs at ACK step with error: {}", - e - )) - })?; + .map_err(ChannelError::channel_proof)?; // Build message(s) to update client on destination let mut msgs = self.build_update_client_on_dst(proofs.height())?; // Get signer - let signer = self.dst_chain().get_signer().map_err(|e| { - ChannelError::Failed(format!( - "failed while fetching the signer for dst chain ({}) with error: {}", - self.dst_chain().id(), - e - )) - })?; + let signer = self + .dst_chain() + .get_signer() + .map_err(|e| ChannelError::fetch_signer(self.dst_chain().id(), e))?; // Build the domain type message let new_msg = MsgChannelOpenAck { @@ -974,7 +937,7 @@ impl Channel { let events = channel .dst_chain() .send_msgs(dst_msgs) - .map_err(|e| ChannelError::SubmitError(channel.dst_chain().id(), e))?; + .map_err(|e| ChannelError::submit(channel.dst_chain().id(), e))?; // Find the relevant event for channel open ack let event = events @@ -984,7 +947,7 @@ impl Channel { || matches!(event, IbcEvent::ChainError(_)) }) .ok_or_else(|| { - ChannelError::Failed("no chan ack event was in the response".to_string()) + ChannelError::missing_event("no chan ack event was in the response".to_string()) })?; match event { @@ -997,13 +960,8 @@ impl Channel { Ok(event) } - IbcEvent::ChainError(e) => { - Err(ChannelError::Failed(format!("tx response error: {}", e))) - } - _ => Err(ChannelError::Failed(format!( - "unexpected IBC event: {}", - event - ))), + IbcEvent::ChainError(e) => Err(ChannelError::tx_response(e)), + _ => Err(ChannelError::invalid_event(event)), } } @@ -1017,10 +975,10 @@ impl Channel { // Source and destination channel IDs must be specified let src_channel_id = self .src_channel_id() - .ok_or(ChannelError::MissingLocalChannelId)?; + .ok_or_else(ChannelError::missing_local_channel_id)?; let dst_channel_id = self .dst_channel_id() - .ok_or(ChannelError::MissingCounterpartyChannelId)?; + .ok_or_else(ChannelError::missing_counterparty_channel_id)?; // Check that the destination chain will accept the message self.validated_expected_channel(ChannelMsgType::OpenConfirm)?; @@ -1028,34 +986,31 @@ impl Channel { // Channel must exist on source self.src_chain() .query_channel(self.src_port_id(), src_channel_id, Height::zero()) - .map_err(|e| ChannelError::QueryError(self.src_chain().id(), e))?; + .map_err(|e| ChannelError::query(self.src_chain().id(), e))?; // Connection must exist on destination self.dst_chain() .query_connection(self.dst_connection_id(), Height::zero()) - .map_err(|e| ChannelError::QueryError(self.dst_chain().id(), e))?; + .map_err(|e| ChannelError::query(self.dst_chain().id(), e))?; let query_height = self .src_chain() .query_latest_height() - .map_err(|e| ChannelError::QueryError(self.src_chain().id(), e))?; + .map_err(|e| ChannelError::query(self.src_chain().id(), e))?; let proofs = self .src_chain() .build_channel_proofs(self.src_port_id(), src_channel_id, query_height) - .map_err(|e| ChannelError::Failed(format!("failed to build channel proofs: {}", e)))?; + .map_err(ChannelError::channel_proof)?; // Build message(s) to update client on destination let mut msgs = self.build_update_client_on_dst(proofs.height())?; // Get signer - let signer = self.dst_chain().get_signer().map_err(|e| { - ChannelError::Failed(format!( - "failed while fetching the signer for dst chain ({}) with error: {}", - self.dst_chain().id(), - e - )) - })?; + let signer = self + .dst_chain() + .get_signer() + .map_err(|e| ChannelError::fetch_signer(self.dst_chain().id(), e))?; // Build the domain type message let new_msg = MsgChannelOpenConfirm { @@ -1078,7 +1033,7 @@ impl Channel { let events = channel .dst_chain() .send_msgs(dst_msgs) - .map_err(|e| ChannelError::SubmitError(channel.dst_chain().id(), e))?; + .map_err(|e| ChannelError::submit(channel.dst_chain().id(), e))?; // Find the relevant event for channel open confirm let event = events @@ -1088,7 +1043,9 @@ impl Channel { || matches!(event, IbcEvent::ChainError(_)) }) .ok_or_else(|| { - ChannelError::Failed("no chan confirm event was in the response".to_string()) + ChannelError::missing_event( + "no chan confirm event was in the response".to_string(), + ) })?; match event { @@ -1096,13 +1053,8 @@ impl Channel { info!("done {} => {:#?}\n", channel.dst_chain().id(), event); Ok(event) } - IbcEvent::ChainError(e) => { - Err(ChannelError::Failed(format!("tx response error: {}", e))) - } - _ => Err(ChannelError::Failed(format!( - "unexpected IBC event: {}", - event - ))), + IbcEvent::ChainError(e) => Err(ChannelError::tx_response(e)), + _ => Err(ChannelError::invalid_event(event)), } } @@ -1116,20 +1068,17 @@ impl Channel { // Destination channel ID must be specified let dst_channel_id = self .dst_channel_id() - .ok_or(ChannelError::MissingCounterpartyChannelId)?; + .ok_or_else(ChannelError::missing_counterparty_channel_id)?; // Channel must exist on destination self.dst_chain() .query_channel(self.dst_port_id(), dst_channel_id, Height::zero()) - .map_err(|e| ChannelError::QueryError(self.dst_chain().id(), e))?; + .map_err(|e| ChannelError::query(self.dst_chain().id(), e))?; - let signer = self.dst_chain().get_signer().map_err(|e| { - ChannelError::Failed(format!( - "failed while fetching the signer for dst chain ({}) with error: {}", - self.dst_chain().id(), - e - )) - })?; + let signer = self + .dst_chain() + .get_signer() + .map_err(|e| ChannelError::fetch_signer(self.dst_chain().id(), e))?; // Build the domain type message let new_msg = MsgChannelCloseInit { @@ -1147,7 +1096,7 @@ impl Channel { let events = self .dst_chain() .send_msgs(dst_msgs) - .map_err(|e| ChannelError::SubmitError(self.dst_chain().id(), e))?; + .map_err(|e| ChannelError::submit(self.dst_chain().id(), e))?; // Find the relevant event for channel close init let result = events @@ -1157,19 +1106,13 @@ impl Channel { || matches!(event, IbcEvent::ChainError(_)) }) .ok_or_else(|| { - ChannelError::Failed("no chan init event was in the response".to_string()) + ChannelError::missing_event("no chan init event was in the response".to_string()) })?; match result { IbcEvent::CloseInitChannel(_) => Ok(result), - IbcEvent::ChainError(e) => Err(ChannelError::Failed(format!( - "tx response event consists of an error: {}", - e - ))), - _ => Err(ChannelError::Failed(format!( - "unexpected IBC event: {}", - result - ))), + IbcEvent::ChainError(e) => Err(ChannelError::tx_response(e)), + _ => Err(ChannelError::invalid_event(result)), } } @@ -1177,10 +1120,10 @@ impl Channel { // Source and destination channel IDs must be specified let src_channel_id = self .src_channel_id() - .ok_or(ChannelError::MissingLocalChannelId)?; + .ok_or_else(ChannelError::missing_local_channel_id)?; let dst_channel_id = self .dst_channel_id() - .ok_or(ChannelError::MissingCounterpartyChannelId)?; + .ok_or_else(ChannelError::missing_counterparty_channel_id)?; // Check that the destination chain will accept the message self.validated_expected_channel(ChannelMsgType::CloseConfirm)?; @@ -1188,34 +1131,31 @@ impl Channel { // Channel must exist on source self.src_chain() .query_channel(self.src_port_id(), src_channel_id, Height::zero()) - .map_err(|e| ChannelError::QueryError(self.src_chain().id(), e))?; + .map_err(|e| ChannelError::query(self.src_chain().id(), e))?; // Connection must exist on destination self.dst_chain() .query_connection(self.dst_connection_id(), Height::zero()) - .map_err(|e| ChannelError::QueryError(self.dst_chain().id(), e))?; + .map_err(|e| ChannelError::query(self.dst_chain().id(), e))?; let query_height = self .src_chain() .query_latest_height() - .map_err(|e| ChannelError::QueryError(self.src_chain().id(), e))?; + .map_err(|e| ChannelError::query(self.src_chain().id(), e))?; let proofs = self .src_chain() .build_channel_proofs(self.src_port_id(), src_channel_id, query_height) - .map_err(|e| ChannelError::Failed(format!("failed to build channel proofs: {}", e)))?; + .map_err(ChannelError::channel_proof)?; // Build message(s) to update client on destination let mut msgs = self.build_update_client_on_dst(proofs.height())?; // Get signer - let signer = self.dst_chain().get_signer().map_err(|e| { - ChannelError::Failed(format!( - "failed while fetching the signer for dst chain ({}) with error: {}", - self.dst_chain().id(), - e - )) - })?; + let signer = self + .dst_chain() + .get_signer() + .map_err(|e| ChannelError::fetch_signer(self.dst_chain().id(), e))?; // Build the domain type message let new_msg = MsgChannelCloseConfirm { @@ -1235,7 +1175,7 @@ impl Channel { let events = self .dst_chain() .send_msgs(dst_msgs) - .map_err(|e| ChannelError::SubmitError(self.dst_chain().id(), e))?; + .map_err(|e| ChannelError::submit(self.dst_chain().id(), e))?; // Find the relevant event for channel close confirm let result = events @@ -1245,18 +1185,13 @@ impl Channel { || matches!(event, IbcEvent::ChainError(_)) }) .ok_or_else(|| { - ChannelError::Failed("no chan confirm event was in the response".to_string()) + ChannelError::missing_event("no chan confirm event was in the response".to_string()) })?; match result { IbcEvent::CloseConfirmChannel(_) => Ok(result), - IbcEvent::ChainError(e) => { - Err(ChannelError::Failed(format!("tx response error: {}", e))) - } - _ => Err(ChannelError::Failed(format!( - "unexpected IBC event: {}", - result - ))), + IbcEvent::ChainError(e) => Err(ChannelError::tx_response(e)), + _ => Err(ChannelError::invalid_event(result)), } } } @@ -1269,7 +1204,7 @@ pub fn extract_channel_id(event: &IbcEvent) -> Result<&ChannelId, ChannelError> IbcEvent::OpenConfirmChannel(ev) => ev.channel_id(), _ => None, } - .ok_or_else(|| ChannelError::Failed("cannot extract channel_id from result".to_string())) + .ok_or_else(|| ChannelError::missing_event("cannot extract channel_id from result".to_string())) } /// Enumeration of proof carrying ICS4 message, helper for relayer. @@ -1302,9 +1237,6 @@ fn check_destination_channel_state( if good_state && good_connection_hops && good_channel_port_ids { Ok(()) } else { - Err(ChannelError::Failed(format!( - "channel {} already exist in an incompatible state", - channel_id - ))) + Err(ChannelError::channel_already_exist(channel_id)) } } diff --git a/relayer/src/channel/error.rs b/relayer/src/channel/error.rs index 3b179c1fb7..65ec15e9e4 100644 --- a/relayer/src/channel/error.rs +++ b/relayer/src/channel/error.rs @@ -1,48 +1,195 @@ -use thiserror::Error; - +use flex_error::define_error; +use ibc::events::IbcEvent; +use ibc::ics02_client::error::Error as ClientError; use ibc::ics04_channel::channel::State; use ibc::ics24_host::identifier::{ChainId, ChannelId, ClientId, PortChannelId, PortId}; +use std::time::Duration; use crate::error::Error; use crate::foreign_client::ForeignClientError; +use crate::supervisor::Error as SupervisorError; + +define_error! { + ChannelError { + Relayer + [ Error ] + |_| { "relayer error" }, + + Supervisor + [ SupervisorError ] + |_| { "supervisor error" }, + + Client + [ ClientError ] + |_| { "ICS02 client error" }, + + InvalidChannel + { reason: String } + | e | { + format_args!("invalid channel: {0}", + e.reason) + }, + + MissingLocalChannelId + |_| { "failed due to missing local channel id" }, + + MissingLocalConnection + { chain_id: ChainId } + | e | { + format_args!("channel constructor failed due to missing connection id on chain id {0}", + e.chain_id) + }, + + MissingCounterpartyChannelId + |_| { "failed due to missing counterparty channel id" }, + + MissingCounterpartyConnection + |_| { "failed due to missing counterparty connection" }, + + MissingChannelOnDestination + |_| { "missing channel on destination chain" }, + + ChannelProof + [ Error ] + |_| { "failed to build channel proofs" }, + + ClientOperation + { + client_id: ClientId, + chain_id: ChainId, + } + [ ForeignClientError ] + | e | { + format_args!("failed during an operation on client ({0}) hosted by chain ({1})", + e.client_id, e.chain_id) + }, + + FetchSigner + { chain_id: ChainId } + [ Error ] + |e| { format_args!("failed while fetching the signer for destination chain {}", e.chain_id) }, + + Query + { chain_id: ChainId } + [ Error ] + |e| { format_args!("failed during a query to chain id {0}", e.chain_id) }, + + QueryChannel + { channel_id: ChannelId } + [ SupervisorError ] + |e| { format_args!("failed during a query to channel id {0}", e.channel_id) }, -#[derive(Debug, Error)] -pub enum ChannelError { - #[error("failed with underlying cause: {0}")] - Failed(String), + Submit + { chain_id: ChainId } + [ Error ] + |_| { "failed during a transaction submission step to chain id {0}" }, - #[error("failed due to missing local channel id")] - MissingLocalChannelId, + HandshakeFinalize + { + port_id: PortId, + channel_id: ChannelId, + chain_id: ChainId, + } + [ Error ] + |e| { + format_args!("failed to finalize a channel open handshake while querying for channel end {0}/{1} on chain chain {2}", + e.port_id, e.channel_id, e.chain_id) + }, - #[error("failed due to missing counterparty channel id")] - MissingCounterpartyChannelId, + PartialOpenHandshake + { + state: State, + counterparty_state: State + } + | e | { + format_args!("the channel is partially open ({0}, {1})", + e.state, e.counterparty_state) + }, - #[error("failed due to missing counterparty connection")] - MissingCounterpartyConnection, + IncompleteChannelState + { + chain_id: ChainId, + port_channel_id: PortChannelId, + } + | e | { + format_args!("channel {0} on chain {1} has no counterparty channel id", + e.port_channel_id, e.chain_id) + }, - #[error("channel constructor failed due to missing connection id on chain id {0}")] - MissingLocalConnection(ChainId), + ChannelAlreadyExist + { channel_id: ChannelId } + |e| { format_args!("channel {} already exist in an incompatible state", e.channel_id) }, - #[error("failed during an operation on client ({0}) hosted by chain ({1}) with error: {2}")] - ClientOperation(ClientId, ChainId, ForeignClientError), + MismatchChannelEnds + { + chain_id: ChainId, + port_channel_id: PortChannelId, + expected_counterrparty_port_channel_id: PortChannelId, + actual_counterrparty_port_channel_id: PortChannelId, + } + | e | { + format_args!("channel {0} on chain {1} expected to have counterparty {2} (but instead has {3})", + e.port_channel_id, e.chain_id, + e.expected_counterrparty_port_channel_id, + e.actual_counterrparty_port_channel_id) + }, - #[error("failed during a query to chain id {0} with underlying error: {1}")] - QueryError(ChainId, Error), + MismatchPort + { + destination_chain_id: ChainId, + destination_port_id: PortId, + source_chain_id: ChainId, + counterparty_port_id: PortId, + counterparty_channel_id: ChannelId, + } + | e | { + format_args!("channel open try to chain `{}` and destination port `{}` does not match \ + the source chain `{}` counterparty port `{}` for channel_id {}", + e.destination_chain_id, e.destination_port_id, + e.source_chain_id, + e.counterparty_port_id, + e.counterparty_channel_id) + }, - #[error( - "failed during a transaction submission step to chain id {0} with underlying error: {1}" - )] - SubmitError(ChainId, Error), + MissingEvent + { + description: String + } + | e | { + format_args!("missing event: {}", e.description) + }, - #[error("failed to finalize a channel open handshake while querying for channel end {0}/{1} on chain chain {2}: {3}")] - HandshakeFinalize(PortId, ChannelId, ChainId, String), + MaxRetry + { + description: String, + tries: u64, + total_delay: Duration, + source: Box, + } + | e | { + format_args!("Error after maximum retry of {} and total delay of {}s: {}", + e.tries, e.total_delay.as_secs(), e.description) + }, - #[error("the channel is partially open ({0}, {1})")] - PartialOpenHandshake(State, State), + RetryInternal + { reason: String } + | e | { + format_args!("Encountered internal error during retry: {}", + e.reason) + }, - #[error("channel {0} on chain {1} has no counterparty channel id")] - IncompleteChannelState(PortChannelId, ChainId), + TxResponse + { reason: String } + | e | { + format_args!("tx response error: {}", + e.reason) + }, - #[error("channel {0} on chain {1} expected to have counterparty {2} (but instead has {3})")] - MismatchingChannelEnds(PortChannelId, ChainId, PortChannelId, PortChannelId), + InvalidEvent + { event: IbcEvent } + | e | { + format_args!("channel object cannot be built from event: {}", + e.event) + }, + } } diff --git a/relayer/src/config.rs b/relayer/src/config.rs index 03b58b1504..93d0adbcbb 100644 --- a/relayer/src/config.rs +++ b/relayer/src/config.rs @@ -11,7 +11,7 @@ use tendermint_light_client::types::TrustThreshold; use ibc::ics24_host::identifier::{ChainId, ChannelId, PortId}; use ibc::timestamp::ZERO_DURATION; -use crate::error; +use crate::error::Error; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GasPrice { @@ -270,34 +270,31 @@ pub struct ChainConfig { } /// Attempt to load and parse the TOML config file as a `Config`. -pub fn load(path: impl AsRef) -> Result { - let config_toml = - std::fs::read_to_string(&path).map_err(|e| error::Kind::ConfigIo.context(e))?; +pub fn load(path: impl AsRef) -> Result { + let config_toml = std::fs::read_to_string(&path).map_err(Error::config_io)?; - let config = - toml::from_str::(&config_toml[..]).map_err(|e| error::Kind::Config.context(e))?; + let config = toml::from_str::(&config_toml[..]).map_err(Error::config_decode)?; Ok(config) } /// Serialize the given `Config` as TOML to the given config file. -pub fn store(config: &Config, path: impl AsRef) -> Result<(), error::Error> { +pub fn store(config: &Config, path: impl AsRef) -> Result<(), Error> { let mut file = if path.as_ref().exists() { fs::OpenOptions::new().write(true).truncate(true).open(path) } else { File::create(path) } - .map_err(|e| error::Kind::Config.context(e))?; + .map_err(Error::config_io)?; store_writer(config, &mut file) } /// Serialize the given `Config` as TOML to the given writer. -pub(crate) fn store_writer(config: &Config, mut writer: impl Write) -> Result<(), error::Error> { - let toml_config = - toml::to_string_pretty(&config).map_err(|e| error::Kind::Config.context(e))?; +pub(crate) fn store_writer(config: &Config, mut writer: impl Write) -> Result<(), Error> { + let toml_config = toml::to_string_pretty(&config).map_err(Error::config_encode)?; - writeln!(writer, "{}", toml_config).map_err(|e| error::Kind::Config.context(e))?; + writeln!(writer, "{}", toml_config).map_err(Error::config_io)?; Ok(()) } diff --git a/relayer/src/connection.rs b/relayer/src/connection.rs index 8c7852b56d..5f995959a2 100644 --- a/relayer/src/connection.rs +++ b/relayer/src/connection.rs @@ -2,11 +2,10 @@ use std::time::Duration; use crate::chain::counterparty::connection_state_on_destination; use crate::util::retry::RetryResult; -use anomaly::BoxError; +use flex_error::define_error; use ibc_proto::ibc::core::connection::v1::QueryConnectionsRequest; use prost_types::Any; use serde::Serialize; -use thiserror::Error; use tracing::debug; use tracing::{error, warn}; @@ -24,39 +23,171 @@ use ibc::timestamp::ZERO_DURATION; use ibc::tx_msg::Msg; use crate::chain::handle::ChainHandle; -use crate::error::Error; +use crate::error::Error as RelayerError; use crate::foreign_client::{ForeignClient, ForeignClientError}; use crate::object::Connection as WorkerConnectionObject; +use crate::supervisor::Error as SupervisorError; /// Maximum value allowed for packet delay on any new connection that the relayer establishes. pub const MAX_PACKET_DELAY: Duration = Duration::from_secs(120); const MAX_RETRIES: usize = 5; -#[derive(Debug, Error)] -pub enum ConnectionError { - #[error("failed with underlying cause: {0}")] - Failed(String), +define_error! { + ConnectionError { + Relayer + [ RelayerError ] + |_| { "relayer error" }, - #[error("connection constructor error: {0}")] - ConstructorFailed(String), + MissingLocalConnectionId + |_| { "failed due to missing local channel id" }, - #[error("failed due to missing local channel id")] - MissingLocalConnectionId, + MissingCounterpartyConnectionIdField + { counterparty: Counterparty } + |e| { + format!("the connection end has no connection id field in the counterparty: {:?}", + e.counterparty) + }, + + MissingCounterpartyConnectionId + |_| { "failed due to missing counterparty connection id" }, + + ChainQuery + { chain_id: ChainId } + [ RelayerError ] + |e| { + format!("failed during a query to chain id {0}", e.chain_id) + }, + + ConnectionQuery + { connection_id: ConnectionId } + [ RelayerError ] + |e| { + format!("failed to query the connection for {}", e.connection_id) + }, + + ClientOperation + { + client_id: ClientId, + chain_id: ChainId, + } + [ ForeignClientError ] + |e| { + format!("failed during an operation on client ({0}) hosted by chain ({1})", + e.client_id, e.chain_id) + }, + + Submit + { chain_id: ChainId } + [ RelayerError ] + |e| { + format!("failed during a transaction submission step to chain id {0}", + e.chain_id) + }, + + MaxDelayPeriod + { delay_period: Duration } + |e| { + format!("Invalid delay period '{:?}': should be at max '{:?}'", + e.delay_period, MAX_PACKET_DELAY) + }, + + InvalidEvent + { event: IbcEvent } + |e| { + format!("a connection object cannot be built from {}", + e.event) + }, - #[error("failed due to missing counterparty connection id ")] - MissingCounterpartyConnectionId, + TxResponse + { event: String } + |e| { + format!("tx response event consists of an error: {}", + e.event) + }, - #[error("failed during a query to chain id {0} due to underlying error: {1}")] - QueryError(ChainId, Error), + ConnectionClientIdMismatch + { + client_id: ClientId, + foreign_client_id: ClientId + } + |e| { + format!("the client id in the connection end ({}) does not match the foreign client id ({})", + e.client_id, e.foreign_client_id) + }, - #[error("failed during an operation on client ({0}) hosted by chain ({1}) with error {2}")] - ClientOperation(ClientId, ChainId, ForeignClientError), + ChainIdMismatch + { + source_chain_id: ChainId, + destination_chain_id: ChainId + } + |e| { + format!("the source chain of client a ({}) does not not match the destination chain of client b ({})", + e.source_chain_id, e.destination_chain_id) + }, + + ConnectionNotOpen + { + state: State, + } + |e| { + format!("the connection end is expected to be in state 'Open'; found state: {:?}", + e.state) + }, + + MaxRetry + |_| { + format!("Failed to finish connection handshake in {:?} iterations", + MAX_RETRIES) + }, + + Supervisor + [ SupervisorError ] + |_| { "supervisor error" }, + + MissingConnectionId + { + chain_id: ChainId, + } + |e| { + format!("missing connection on source chain {}", + e.chain_id) + }, + + Signer + { chain_id: ChainId } + [ RelayerError ] + |e| { + format!("failed while fetching the signer for chain ({})", + e.chain_id) + }, + + MissingConnectionIdFromEvent + |_| { "cannot extract connection_id from result" }, + + MissingConnectionInitEvent + |_| { "no conn init event was in the response" }, + + MissingConnectionTryEvent + |_| { "no conn try event was in the response" }, + + MissingConnectionAckEvent + |_| { "no conn ack event was in the response" }, + + MissingConnectionConfirmEvent + |_| { "no conn confirm event was in the response" }, + + ConnectionProof + [ RelayerError ] + |_| { "failed to build connection proofs" }, + + ConnectionAlreadyExist + { connection_id: ConnectionId } + |e| { + format!("connection {} already exist in an incompatible state", e.connection_id) + }, - #[error( - "failed during a transaction submission step to chain id {0} with underlying error: {1}" - )] - SubmitError(ChainId, Error), + } } #[derive(Clone, Debug)] @@ -122,10 +253,7 @@ impl Connection { // Validate the delay period against the upper bound if delay_period > MAX_PACKET_DELAY { - return Err(ConnectionError::ConstructorFailed(format!( - "Invalid delay period '{:?}': should be max '{:?}'", - delay_period, MAX_PACKET_DELAY - ))); + return Err(ConnectionError::max_delay_period(delay_period)); } let mut c = Self { @@ -150,15 +278,10 @@ impl Connection { chain: Box, counterparty_chain: Box, connection_open_event: IbcEvent, - ) -> Result { + ) -> Result { let connection_event_attributes = connection_open_event .connection_attributes() - .ok_or_else(|| { - ConnectionError::Failed(format!( - "a connection object cannot be built from {}", - connection_open_event - )) - })?; + .ok_or_else(|| ConnectionError::invalid_event(connection_open_event.clone()))?; let connection_id = connection_event_attributes.connection_id.clone(); @@ -188,8 +311,11 @@ impl Connection { counterparty_chain: Box, connection: WorkerConnectionObject, height: Height, - ) -> Result<(Connection, State), BoxError> { - let a_connection = chain.query_connection(&connection.src_connection_id, height)?; + ) -> Result<(Connection, State), ConnectionError> { + let a_connection = chain + .query_connection(&connection.src_connection_id, height) + .map_err(ConnectionError::relayer)?; + let client_id = a_connection.client_id(); let delay_period = a_connection.delay_period(); @@ -215,8 +341,9 @@ impl Connection { let req = QueryConnectionsRequest { pagination: ibc_proto::cosmos::base::query::pagination::all(), }; - let connections: Vec = - counterparty_chain.query_connections(req)?; + let connections: Vec = counterparty_chain + .query_connections(req) + .map_err(ConnectionError::relayer)?; for conn in connections { if !conn @@ -248,22 +375,21 @@ impl Connection { // Validate the connection end if conn_end_a.end().client_id().ne(a_client.id()) { - return Err(ConnectionError::ConstructorFailed(format!( - "the client id in the connection end ({}) does not match the foreign client id ({})", - conn_end_a.end().client_id(), a_client.id() - ))); + return Err(ConnectionError::connection_client_id_mismatch( + conn_end_a.end().client_id().clone(), + a_client.id().clone(), + )); } if conn_end_a.end().counterparty().client_id() != b_client.id() { - return Err(ConnectionError::ConstructorFailed(format!( - "the counterparty client id in the connection end ({}) does not match the foreign client id ({})", - conn_end_a.end().counterparty().client_id(), b_client.id() - ))); + return Err(ConnectionError::connection_client_id_mismatch( + conn_end_a.end().counterparty().client_id().clone(), + b_client.id().clone(), + )); } if !conn_end_a.end().state_matches(&State::Open) { - return Err(ConnectionError::ConstructorFailed(format!( - "the connection end is expected to be in state 'Open'; found state: {:?}", - conn_end_a.end().state() - ))); + return Err(ConnectionError::connection_not_open( + *conn_end_a.end().state(), + )); } let b_conn_id = conn_end_a .end() @@ -271,10 +397,9 @@ impl Connection { .connection_id() .cloned() .ok_or_else(|| { - ConnectionError::ConstructorFailed(format!( - "the connection end has no connection id field in the counterparty: {:?}", - conn_end_a.end().counterparty() - )) + ConnectionError::missing_counterparty_connection_id_field( + conn_end_a.end().counterparty().clone(), + ) })?; let c = Connection { @@ -300,19 +425,17 @@ impl Connection { b_client: &ForeignClient, ) -> Result<(), ConnectionError> { if a_client.src_chain().id() != b_client.dst_chain().id() { - return Err(ConnectionError::ConstructorFailed(format!( - "the source chain of client a ({}) does not not match the destination chain of client b ({})", + return Err(ConnectionError::chain_id_mismatch( a_client.src_chain().id(), - b_client.dst_chain().id() - ))); + b_client.dst_chain().id(), + )); } if a_client.dst_chain().id() != b_client.src_chain().id() { - return Err(ConnectionError::ConstructorFailed(format!( - "the destination chain of client a ({}) does not not match the source chain of client b ({})", + return Err(ConnectionError::chain_id_mismatch( a_client.dst_chain().id(), - b_client.src_chain().id() - ))); + b_client.src_chain().id(), + )); } Ok(()) @@ -397,10 +520,10 @@ impl Connection { let src_connection_id = self .src_connection_id() - .ok_or(ConnectionError::MissingLocalConnectionId)?; + .ok_or_else(ConnectionError::missing_local_connection_id)?; let dst_connection_id = self .dst_connection_id() - .ok_or(ConnectionError::MissingCounterpartyConnectionId)?; + .ok_or_else(ConnectionError::missing_counterparty_connection_id)?; // Continue loop if query error let a_connection = a_chain.query_connection(&src_connection_id, Height::zero()); @@ -451,39 +574,27 @@ impl Connection { } } - Err(ConnectionError::Failed(format!( - "Failed to finish connection handshake in {:?} iterations", - MAX_RETRIES - ))) + Err(ConnectionError::max_retry()) } pub fn counterparty_state(&self) -> Result { // Source connection ID must be specified let connection_id = self .src_connection_id() - .ok_or(ConnectionError::MissingLocalConnectionId)?; + .ok_or_else(ConnectionError::missing_local_connection_id)?; let connection_end = self .src_chain() .query_connection(&connection_id, Height::zero()) - .map_err(|_| { - ConnectionError::Failed(format!( - "failed to query the connection for {}", - connection_id - )) - })?; + .map_err(|e| ConnectionError::connection_query(connection_id.clone(), e))?; let connection = IdentifiedConnectionEnd { connection_end, connection_id: connection_id.clone(), }; - connection_state_on_destination(connection, self.dst_chain().as_ref()).map_err(|e| { - ConnectionError::Failed(format!( - "failed to query the connection state on destination for {}; caused by {}", - connection_id, e - )) - }) + connection_state_on_destination(connection, self.dst_chain().as_ref()) + .map_err(ConnectionError::supervisor) } pub fn handshake_step(&mut self, state: State) -> Result, ConnectionError> { @@ -533,12 +644,12 @@ impl Connection { ) -> Result { let dst_connection_id = self .dst_connection_id() - .ok_or(ConnectionError::MissingCounterpartyConnectionId)?; + .ok_or_else(ConnectionError::missing_counterparty_connection_id)?; let prefix = self .src_chain() .query_commitment_prefix() - .map_err(|e| ConnectionError::QueryError(self.src_chain().id(), e))?; + .map_err(|e| ConnectionError::chain_query(self.src_chain().id(), e))?; // If there is a connection present on the destination chain, it should look like this: let counterparty = Counterparty::new( @@ -557,7 +668,7 @@ impl Connection { let versions = self .src_chain() .query_compatible_versions() - .map_err(|e| ConnectionError::QueryError(self.src_chain().id(), e))?; + .map_err(|e| ConnectionError::chain_query(self.src_chain().id(), e))?; let dst_expected_connection = ConnectionEnd::new( highest_state, @@ -571,16 +682,14 @@ impl Connection { let dst_connection = self .dst_chain() .query_connection(dst_connection_id, Height::zero()) - .map_err(|e| ConnectionError::QueryError(self.dst_chain().id(), e))?; + .map_err(|e| ConnectionError::chain_query(self.dst_chain().id(), e))?; // Check if a connection is expected to exist on destination chain // A connection must exist on destination chain for Ack and Confirm Tx-es to succeed if dst_connection.state_matches(&State::Uninitialized) { - return Err(ConnectionError::Failed(format!( - "missing connection {:?} on source chain {}", - self.src_connection_id().clone(), - self.dst_chain().id() - ))); + return Err(ConnectionError::missing_connection_id( + self.dst_chain().id(), + )); } check_destination_connection_state( @@ -595,38 +704,43 @@ impl Connection { pub fn build_update_client_on_src(&self, height: Height) -> Result, ConnectionError> { let client = self.restore_src_client(); client.build_update_client(height).map_err(|e| { - ConnectionError::ClientOperation(self.src_client_id().clone(), self.src_chain().id(), e) + ConnectionError::client_operation( + self.src_client_id().clone(), + self.src_chain().id(), + e, + ) }) } pub fn build_update_client_on_dst(&self, height: Height) -> Result, ConnectionError> { let client = self.restore_dst_client(); client.build_update_client(height).map_err(|e| { - ConnectionError::ClientOperation(self.dst_client_id().clone(), self.dst_chain().id(), e) + ConnectionError::client_operation( + self.dst_client_id().clone(), + self.dst_chain().id(), + e, + ) }) } pub fn build_conn_init(&self) -> Result, ConnectionError> { // Get signer - let signer = self.dst_chain().get_signer().map_err(|e| { - ConnectionError::Failed(format!( - "failed while fetching the signer for dst chain ({}) with error: {}", - self.dst_chain().id(), - e - )) - })?; + let signer = self + .dst_chain() + .get_signer() + .map_err(|e| ConnectionError::signer(self.dst_chain().id(), e))?; let prefix = self .src_chain() .query_commitment_prefix() - .map_err(|e| ConnectionError::QueryError(self.src_chain().id(), e))?; + .map_err(|e| ConnectionError::chain_query(self.src_chain().id(), e))?; let counterparty = Counterparty::new(self.src_client_id().clone(), None, prefix); let version = self .dst_chain() .query_compatible_versions() - .map_err(|e| ConnectionError::QueryError(self.dst_chain().id(), e))?[0] + .map_err(|e| ConnectionError::chain_query(self.dst_chain().id(), e))?[0] .clone(); // Build the domain type message @@ -647,7 +761,7 @@ impl Connection { let events = self .dst_chain() .send_msgs(dst_msgs) - .map_err(|e| ConnectionError::SubmitError(self.dst_chain().id(), e))?; + .map_err(|e| ConnectionError::submit(self.dst_chain().id(), e))?; // Find the relevant event for connection init let result = events @@ -656,17 +770,12 @@ impl Connection { matches!(event, IbcEvent::OpenInitConnection(_)) || matches!(event, IbcEvent::ChainError(_)) }) - .ok_or_else(|| { - ConnectionError::Failed("no conn init event was in the response".to_string()) - })?; + .ok_or_else(ConnectionError::missing_connection_init_event)?; // TODO - make chainError an actual error match result { IbcEvent::OpenInitConnection(_) => Ok(result), - IbcEvent::ChainError(e) => Err(ConnectionError::Failed(format!( - "tx response event consists of an error: {}", - e - ))), + IbcEvent::ChainError(e) => Err(ConnectionError::tx_response(e)), _ => panic!("internal error"), } } @@ -675,12 +784,12 @@ impl Connection { pub fn build_conn_try(&self) -> Result, ConnectionError> { let src_connection_id = self .src_connection_id() - .ok_or(ConnectionError::MissingLocalConnectionId)?; + .ok_or_else(ConnectionError::missing_local_connection_id)?; let src_connection = self .src_chain() .query_connection(src_connection_id, Height::zero()) - .map_err(|e| ConnectionError::QueryError(self.src_chain().id(), e))?; + .map_err(|e| ConnectionError::chain_query(self.src_chain().id(), e))?; // TODO - check that the src connection is consistent with the try options @@ -704,16 +813,16 @@ impl Connection { let src_client_target_height = self .dst_chain() .query_latest_height() - .map_err(|e| ConnectionError::QueryError(self.dst_chain().id(), e))?; + .map_err(|e| ConnectionError::chain_query(self.dst_chain().id(), e))?; let client_msgs = self.build_update_client_on_src(src_client_target_height)?; self.src_chain() .send_msgs(client_msgs) - .map_err(|e| ConnectionError::SubmitError(self.src_chain().id(), e))?; + .map_err(|e| ConnectionError::submit(self.src_chain().id(), e))?; let query_height = self .src_chain() .query_latest_height() - .map_err(|e| ConnectionError::QueryError(self.src_chain().id(), e))?; + .map_err(|e| ConnectionError::chain_query(self.src_chain().id(), e))?; let (client_state, proofs) = self .src_chain() .build_connection_proofs_and_client_state( @@ -722,9 +831,7 @@ impl Connection { self.src_client_id(), query_height, ) - .map_err(|e| { - ConnectionError::Failed(format!("failed to build connection proofs: {}", e)) - })?; + .map_err(ConnectionError::connection_proof)?; // Build message(s) for updating client on destination let mut msgs = self.build_update_client_on_dst(proofs.height())?; @@ -732,24 +839,21 @@ impl Connection { let counterparty_versions = if src_connection.versions().is_empty() { self.src_chain() .query_compatible_versions() - .map_err(|e| ConnectionError::QueryError(self.src_chain().id(), e))? + .map_err(|e| ConnectionError::chain_query(self.src_chain().id(), e))? } else { src_connection.versions() }; // Get signer - let signer = self.dst_chain().get_signer().map_err(|e| { - ConnectionError::Failed(format!( - "failed while fetching the signer for dst chain ({}) with error: {}", - self.dst_chain().id(), - e - )) - })?; + let signer = self + .dst_chain() + .get_signer() + .map_err(|e| ConnectionError::signer(self.dst_chain().id(), e))?; let prefix = self .src_chain() .query_commitment_prefix() - .map_err(|e| ConnectionError::QueryError(self.src_chain().id(), e))?; + .map_err(|e| ConnectionError::chain_query(self.src_chain().id(), e))?; let counterparty = Counterparty::new( self.src_client_id().clone(), @@ -784,7 +888,7 @@ impl Connection { let events = self .dst_chain() .send_msgs(dst_msgs) - .map_err(|e| ConnectionError::SubmitError(self.dst_chain().id(), e))?; + .map_err(|e| ConnectionError::submit(self.dst_chain().id(), e))?; // Find the relevant event for connection try transaction let result = events @@ -793,15 +897,11 @@ impl Connection { matches!(event, IbcEvent::OpenTryConnection(_)) || matches!(event, IbcEvent::ChainError(_)) }) - .ok_or_else(|| { - ConnectionError::Failed("no conn try event was in the response".to_string()) - })?; + .ok_or_else(ConnectionError::missing_connection_try_event)?; match result { IbcEvent::OpenTryConnection(_) => Ok(result), - IbcEvent::ChainError(e) => { - Err(ConnectionError::Failed(format!("tx response error: {}", e))) - } + IbcEvent::ChainError(e) => Err(ConnectionError::tx_response(e)), _ => panic!("internal error"), } } @@ -810,22 +910,18 @@ impl Connection { pub fn build_conn_ack(&self) -> Result, ConnectionError> { let src_connection_id = self .src_connection_id() - .ok_or(ConnectionError::MissingLocalConnectionId)?; + .ok_or_else(ConnectionError::missing_local_connection_id)?; let dst_connection_id = self .dst_connection_id() - .ok_or(ConnectionError::MissingCounterpartyConnectionId)?; + .ok_or_else(ConnectionError::missing_counterparty_connection_id)?; - let _expected_dst_connection = self - .validated_expected_connection(ConnectionMsgType::OpenAck) - .map_err(|e| - ConnectionError::Failed(format!( - "ack options inconsistent with existing connection on destination chain; context={}", e - )))?; + let _expected_dst_connection = + self.validated_expected_connection(ConnectionMsgType::OpenAck)?; let src_connection = self .src_chain() .query_connection(src_connection_id, Height::zero()) - .map_err(|e| ConnectionError::QueryError(self.src_chain().id(), e))?; + .map_err(|e| ConnectionError::chain_query(self.src_chain().id(), e))?; // TODO - check that the src connection is consistent with the ack options @@ -834,16 +930,17 @@ impl Connection { let src_client_target_height = self .dst_chain() .query_latest_height() - .map_err(|e| ConnectionError::QueryError(self.dst_chain().id(), e))?; + .map_err(|e| ConnectionError::chain_query(self.dst_chain().id(), e))?; let client_msgs = self.build_update_client_on_src(src_client_target_height)?; self.src_chain() .send_msgs(client_msgs) - .map_err(|e| ConnectionError::SubmitError(self.src_chain().id(), e))?; + .map_err(|e| ConnectionError::submit(self.src_chain().id(), e))?; let query_height = self .src_chain() .query_latest_height() - .map_err(|e| ConnectionError::QueryError(self.src_chain().id(), e))?; + .map_err(|e| ConnectionError::chain_query(self.src_chain().id(), e))?; + let (client_state, proofs) = self .src_chain() .build_connection_proofs_and_client_state( @@ -852,21 +949,16 @@ impl Connection { self.src_client_id(), query_height, ) - .map_err(|e| { - ConnectionError::Failed(format!("failed to build connection proofs: {}", e)) - })?; + .map_err(ConnectionError::connection_proof)?; // Build message(s) for updating client on destination let mut msgs = self.build_update_client_on_dst(proofs.height())?; // Get signer - let signer = self.dst_chain().get_signer().map_err(|e| { - ConnectionError::Failed(format!( - "failed while fetching the signer for dst chain ({}) with error: {}", - self.dst_chain().id(), - e - )) - })?; + let signer = self + .dst_chain() + .get_signer() + .map_err(|e| ConnectionError::signer(self.dst_chain().id(), e))?; let new_msg = MsgConnectionOpenAck { connection_id: dst_connection_id.clone(), @@ -887,7 +979,7 @@ impl Connection { let events = self .dst_chain() .send_msgs(dst_msgs) - .map_err(|e| ConnectionError::SubmitError(self.dst_chain().id(), e))?; + .map_err(|e| ConnectionError::submit(self.dst_chain().id(), e))?; // Find the relevant event for connection ack let result = events @@ -896,15 +988,11 @@ impl Connection { matches!(event, IbcEvent::OpenAckConnection(_)) || matches!(event, IbcEvent::ChainError(_)) }) - .ok_or_else(|| { - ConnectionError::Failed("no conn ack event was in the response".to_string()) - })?; + .ok_or_else(ConnectionError::missing_connection_ack_event)?; match result { IbcEvent::OpenAckConnection(_) => Ok(result), - IbcEvent::ChainError(e) => { - Err(ConnectionError::Failed(format!("tx response error: {}", e))) - } + IbcEvent::ChainError(e) => Err(ConnectionError::tx_response(e)), _ => panic!("internal error"), } } @@ -913,32 +1001,23 @@ impl Connection { pub fn build_conn_confirm(&self) -> Result, ConnectionError> { let src_connection_id = self .src_connection_id() - .ok_or(ConnectionError::MissingLocalConnectionId)?; + .ok_or_else(ConnectionError::missing_local_connection_id)?; let dst_connection_id = self .dst_connection_id() - .ok_or(ConnectionError::MissingCounterpartyConnectionId)?; + .ok_or_else(ConnectionError::missing_counterparty_connection_id)?; - let _expected_dst_connection = self - .validated_expected_connection(ConnectionMsgType::OpenAck) - .map_err(|e| { - ConnectionError::Failed(format!( - "confirm options inconsistent with existing connection on destination chain; context={}", e)) - })?; + let _expected_dst_connection = + self.validated_expected_connection(ConnectionMsgType::OpenAck)?; let query_height = self .src_chain() .query_latest_height() - .map_err(|e| ConnectionError::QueryError(self.src_chain().id(), e))?; + .map_err(|e| ConnectionError::chain_query(self.src_chain().id(), e))?; let _src_connection = self .src_chain() .query_connection(src_connection_id, query_height) - .map_err(|_| { - ConnectionError::Failed(format!( - "missing connection {} on source chain", - src_connection_id - )) - })?; + .map_err(|e| ConnectionError::connection_query(src_connection_id.clone(), e))?; // TODO - check that the src connection is consistent with the confirm options @@ -950,21 +1029,16 @@ impl Connection { self.src_client_id(), query_height, ) - .map_err(|e| { - ConnectionError::Failed(format!("failed to build connection proofs: {}", e)) - })?; + .map_err(ConnectionError::connection_proof)?; // Build message(s) for updating client on destination let mut msgs = self.build_update_client_on_dst(proofs.height())?; // Get signer - let signer = self.dst_chain().get_signer().map_err(|e| { - ConnectionError::Failed(format!( - "failed while fetching the signer for dst chain ({}) with error: {}", - self.dst_chain().id(), - e - )) - })?; + let signer = self + .dst_chain() + .get_signer() + .map_err(|e| ConnectionError::signer(self.dst_chain().id(), e))?; let new_msg = MsgConnectionOpenConfirm { connection_id: dst_connection_id.clone(), @@ -982,7 +1056,7 @@ impl Connection { let events = self .dst_chain() .send_msgs(dst_msgs) - .map_err(|e| ConnectionError::SubmitError(self.dst_chain().id(), e))?; + .map_err(|e| ConnectionError::submit(self.dst_chain().id(), e))?; // Find the relevant event for connection confirm let result = events @@ -991,15 +1065,11 @@ impl Connection { matches!(event, IbcEvent::OpenConfirmConnection(_)) || matches!(event, IbcEvent::ChainError(_)) }) - .ok_or_else(|| { - ConnectionError::Failed("no conn confirm event was in the response".to_string()) - })?; + .ok_or_else(ConnectionError::missing_connection_confirm_event)?; match result { IbcEvent::OpenConfirmConnection(_) => Ok(result), - IbcEvent::ChainError(e) => { - Err(ConnectionError::Failed(format!("tx response error: {}", e))) - } + IbcEvent::ChainError(e) => Err(ConnectionError::tx_response(e)), _ => panic!("internal error"), } } @@ -1029,7 +1099,7 @@ fn extract_connection_id(event: &IbcEvent) -> Result<&ConnectionId, ConnectionEr IbcEvent::OpenConfirmConnection(ev) => ev.connection_id().as_ref(), _ => None, } - .ok_or_else(|| ConnectionError::Failed("cannot extract connection_id from result".to_string())) + .ok_or_else(ConnectionError::missing_connection_id_from_event) } /// Enumeration of proof carrying ICS3 message, helper for relayer. @@ -1060,9 +1130,6 @@ fn check_destination_connection_state( if good_state && good_client_ids && good_connection_ids { Ok(()) } else { - Err(ConnectionError::Failed(format!( - "connection {} already exist in an incompatible state", - connection_id - ))) + Err(ConnectionError::connection_already_exist(connection_id)) } } diff --git a/relayer/src/error.rs b/relayer/src/error.rs index 8b5f35599f..b3cd9f61a3 100644 --- a/relayer/src/error.rs +++ b/relayer/src/error.rs @@ -1,244 +1,394 @@ //! This module defines the various errors that be raised in the relayer. -use anomaly::{BoxError, Context}; -use thiserror::Error; +use crate::keyring::errors::Error as KeyringError; +use crate::sdk_error::SdkError; +use flex_error::{define_error, DisplayOnly, TraceClone, TraceError}; +use http::uri::InvalidUri; +use prost::DecodeError; +use tendermint_light_client::{ + components::io::IoError as LightClientIoError, errors::Error as LightClientError, +}; +use tendermint_proto::Error as TendermintProtoError; +use tendermint_rpc::endpoint::abci_query::AbciQuery; +use tendermint_rpc::endpoint::broadcast::tx_commit::TxResult; +use tendermint_rpc::Error as TendermintError; +use tonic::{ + metadata::errors::InvalidMetadataValue, transport::Error as TransportError, + Status as GrpcStatus, +}; use ibc::{ - ics02_client::client_type::ClientType, + ics02_client::{client_type::ClientType, error as client_error}, + ics03_connection::error as connection_error, + ics07_tendermint::error as tendermint_error, + ics18_relayer::error as relayer_error, + ics23_commitment::error as commitment_error, ics24_host::identifier::{ChainId, ChannelId, ConnectionId}, + proofs::ProofError, }; -/// An error that can be raised by the relayer. -pub type Error = anomaly::Error; - -/// Various kinds of errors that can be raiser by the relayer. -#[derive(Clone, Debug, Error)] -pub enum Kind { - /// Config I/O error - #[error("config I/O error")] - ConfigIo, - - /// I/O error - #[error("I/O error")] - Io, - - /// Invalid configuration - #[error("invalid configuration")] - Config, - - /// RPC error (typically raised by the RPC client or the RPC requester) - #[error("RPC error to endpoint {0}")] - Rpc(tendermint_rpc::Url), - - /// Websocket error (typically raised by the Websocket client) - #[error("Websocket error to endpoint {0}")] - Websocket(tendermint_rpc::Url), - - /// Event monitor error - #[error("event monitor error: {0}")] - EventMonitor(crate::event::monitor::Error), - - /// GRPC error (typically raised by the GRPC client or the GRPC requester) - #[error("GRPC error")] - Grpc, - - /// Light client instance error, typically raised by a `Client` - #[error("Light client error for RPC address {0}")] - LightClient(String), - - /// Trusted store error, raised by instances of `Store` - #[error("Store error")] - Store, - - /// Event error (raised by the event monitor) - #[error("Bad Notification")] - Event, - - /// Missing ClientState in the upgrade CurrentPlan - #[error("The upgrade plan specifies no upgraded client state")] - EmptyUpgradedClientState, +use crate::event::monitor; + +define_error! { + Error { + ConfigIo + [ TraceError ] + |_| { "config I/O error" }, + + Io + [ TraceError ] + |_| { "I/O error" }, + + ConfigDecode + [ TraceError ] + |_| { "invalid configuration" }, + + ConfigEncode + [ TraceError ] + |_| { "invalid configuration" }, + + Rpc + { url: tendermint_rpc::Url } + [ TraceClone ] + |e| { format!("RPC error to endpoint {}", e.url) }, - /// Response does not contain data - #[error("Empty response value")] - EmptyResponseValue, + AbciQuery + { query: AbciQuery } + |e| { format!("ABCI query returns error: {:?}", e.query) }, - /// Response does not contain a proof - #[error("Empty response proof")] - EmptyResponseProof, + CheckTx + { + detail: SdkError, + tx: TxResult + } + |e| { format!("CheckTX Commit returns error: {0}. RawResult: {1:?}", e.detail, e.tx) }, - /// Response does not contain a proof - #[error("Malformed proof")] - MalformedProof, + DeliverTx + { + detail: SdkError, + tx: TxResult + } + |e| { format!("DeliverTx Commit returns error: {0}. RawResult: {1:?}", e.detail, e.tx) }, + + WebSocket + { url: tendermint_rpc::Url } + |e| { format!("Websocket error to endpoint {}", e.url) }, + + EventMonitor + [ monitor::Error ] + |_| { "event monitor error" }, + + Grpc + |_| { "GRPC error" }, + + GrpcStatus + { status: GrpcStatus } + |e| { format!("GRPC call return error status {0}", e.status) }, + + GrpcTransport + [ TraceError ] + |_| { "error in underlying transport when making GRPC call" }, + + GrpcResponseParam + { param: String } + |e| { format!("missing parameter in GRPC response: {}", e.param) }, + + Decode + [ TraceError ] + |_| { "error decoding protobuf" }, + + LightClient + { address: String } + [ TraceError ] + |e| { format!("Light client error for RPC address {0}", e.address) }, + + LightClientIo + { address: String } + [ TraceError ] + |e| { format!("Light client error for RPC address {0}", e.address) }, + + ChainNotCaughtUp + { + address: String, + chain_id: ChainId, + } + |e| { format!("node at {} running chain {} not caught up", e.address, e.chain_id) }, + + PrivateStore + |_| { "requested proof for a path in the private store" }, + + Store + [ TraceError ] + |_| { "Store error" }, + + Event + |_| { "Bad Notification" }, + + EmptyUpgradedClientState + |_| { "The upgrade plan specifies no upgraded client state" }, + + EmptyResponseValue + |_| { "Empty response value" }, + + EmptyResponseProof + |_| { "Empty response proof" }, + + MalformedProof + [ ProofError ] + |_| { "Malformed proof" }, + + InvalidHeight + [ DisplayOnly> ] + |_| { "Invalid height" }, + + InvalidMetadata + [ TraceError ] + |_| { "invalid metadata" }, + + BuildClientStateFailure + |_| { "Failed to create client state" }, + + CreateClient + { client_id: String } + |e| { format!("Failed to create client {0}", e.client_id) }, + + ClientStateType + { client_state_type: String } + |e| { format!("unexpected client state type {0}", e.client_state_type) }, + + ConnectionNotFound + { connection_id: ConnectionId } + |e| { format!("Connection not found: {0}", e.connection_id) }, + + BadConnectionState + |_| { "bad connection state" }, + + ConnOpen + { connection_id: ConnectionId, reason: String } + |e| { + format!("Failed to build conn open message {0}: {1}", + e.connection_id, e.reason) + }, + + ConnOpenInit + { reason: String } + |e| { format!("Failed to build conn open init: {0}", e.reason) }, + + ConnOpenTry + { reason: String } + |e| { format!("Failed to build conn open try: {0}", e.reason) }, + + ChanOpenAck + { channel_id: ChannelId, reason: String } + |e| { + format!("Failed to build channel open ack {0}: {1}", + e.channel_id, e.reason) + }, + + ChanOpenConfirm + { channel_id: ChannelId, reason: String } + |e| { + format!("Failed to build channel open confirm {0}: {1}", + e.channel_id, e.reason) + }, + + ConsensusProof + [ ProofError ] + |_| { "failed to build consensus proof" }, + + Packet + { channel_id: ChannelId, reason: String } + |e| { + format!("Failed to build packet {0}: {1}", + e.channel_id, e.reason) + }, + + RecvPacket + { channel_id: ChannelId, reason: String } + |e| { + format!("Failed to build recv packet {0}: {1}", + e.channel_id, e.reason) + }, + + AckPacket + { channel_id: ChannelId, reason: String } + |e| { + format!("Failed to build acknowledge packet {0}: {1}", + e.channel_id, e.reason) + }, + + TimeoutPacket + { channel_id: ChannelId, reason: String } + |e| { + format!("Failed to build timeout packet {0}: {1}", + e.channel_id, e.reason) + }, + + MessageTransaction + { reason: String } + |e| { format!("Message transaction failure: {0}", e.reason) }, + + Query + { query: String } + |e| { format!("Query error occurred (failed to query for {0})", e.query) }, + + KeyBase + [ KeyringError ] + |_| { "Keybase error" }, + + Ics02 + [ client_error::Error ] + |_| { "ICS 02 error" }, + + Ics03 + [ connection_error::Error ] + |_| { "ICS 03 error" }, + + Ics07 + [ tendermint_error::Error ] + |_| { "ICS 07 error" }, + + Ics18 + [ relayer_error::Error ] + |_| { "ICS 18 error" }, + + Ics23 + [ commitment_error::Error ] + |_| { "ICS 23 error" }, + + InvalidUri + { uri: String } + [ TraceError ] + |e| { + format!("error parsing URI {}", e.uri) + }, + + ChainIdentifier + { chain_id: String } + |e| { format!("invalid chain identifier format: {0}", e.chain_id) }, + + NonProvableData + |_| { "requested proof for data in the privateStore" }, + + ChannelSend + |_| { "failed to send through channel" }, + + ChannelReceive + [ TraceError ] + |_| { "failed to receive through channel" }, + + InvalidInputHeader + |_| { "the input header is not recognized as a header for this chain" }, + + TxNoConfirmation + |_| { "Failed Tx: no confirmation" }, + + Misbehaviour + { reason: String } + |e| { format!("error raised while submitting the misbehaviour evidence: {0}", e.reason) }, + + InvalidKeyAddress + { address: String } + [ DisplayOnly> ] + |e| { format!("invalid key address: {0}", e.address) }, + + Bech32Encoding + [ TraceError ] + |_| { "bech32 encoding failed" }, + + ClientTypeMismatch + { + expected: ClientType, + got: ClientType, + } + |e| { + format!("client type mismatch: expected '{}', got '{}'", + e.expected, e.got) + }, + + ProtobufDecode + { payload_type: String } + [ TraceError ] + |e| { format!("Error decoding protocol buffer for {}", e.payload_type) }, + + Cbor + [ TraceError ] + | _ | { "error decoding CBOR payload" }, + + TxSimulateGasEstimateExceeded + { + chain_id: ChainId, + estimated_gas: u64, + max_gas: u64, + } + |e| { + format!("{} gas estimate {} from simulated Tx exceeds the maximum configured {}", + e.chain_id, e.estimated_gas, e.max_gas) + }, + + HealthCheckJsonRpc + { + chain_id: ChainId, + address: String, + endpoint: String, + } + [ DisplayOnly ] + |e| { + format!("Hermes health check failed for endpoint {0} on the Json RPC interface of chain {1}:{2}", + e.endpoint, e.chain_id, e.address) + }, + + HealthCheckJsonGrpcTransport + { + chain_id: ChainId, + address: String, + endpoint: String, + } + [ DisplayOnly ] + |e| { + format!("Hermes health check failed for endpoint {0} on the Json RPC interface of chain {1}:{2}", + e.endpoint, e.chain_id, e.address) + }, + + HealthCheckJsonGrpcStatus + { + chain_id: ChainId, + address: String, + endpoint: String, + status: tonic::Status + } + |e| { + format!("Hermes health check failed for endpoint {0} on the Json RPC interface of chain {1}:{2}; caused by: {3}", + e.endpoint, e.chain_id, e.address, e.status) + }, + + HealthCheckInvalidVersion + { + chain_id: ChainId, + address: String, + endpoint: String, + } + |e| { + format!("Hermes health check failed for endpoint {0} on the Json RPC interface of chain {1}:{2}; the gRPC response contains no application version information", + e.endpoint, e.chain_id, e.address) + }, + + SdkModuleVersion + { + chain_id: ChainId, + address: String, + cause: String + } + |e| { + format!("Hermes health check failed while verifying the application compatibility for chain {0}:{1}; caused by: {2}", + e.chain_id, e.address, e.cause) + }, - /// Invalid height - #[error("Invalid height")] - InvalidHeight, - - /// Unable to build the client state - #[error("Failed to create client state")] - BuildClientStateFailure, - - /// Did not find tx confirmation - #[error("did not find tx confirmation {0}")] - TxNoConfirmation(String), - - /// Gas estimate from simulated Tx exceeds the maximum configured - #[error("{chain_id} gas estimate {estimated_gas} from simulated Tx exceeds the maximum configured {max_gas}")] - TxSimulateGasEstimateExceeded { - chain_id: ChainId, - estimated_gas: u64, - max_gas: u64, - }, - - /// Create client failure - #[error("Failed to create client {0}")] - CreateClient(String), - - #[error("Connection not found: {0}")] - ConnectionNotFound(ConnectionId), - - /// Common failures to all connection messages - #[error("Failed to build conn open message {0}: {1}")] - ConnOpen(ConnectionId, String), - - /// Connection open init failure - #[error("Failed to build conn open init {0}")] - ConnOpenInit(String), - - /// Connection open try failure - #[error("Failed to build conn open try {0}")] - ConnOpenTry(String), - - /// Connection open ack failure - #[error("Failed to build conn open ack {0}: {1}")] - ConnOpenAck(ConnectionId, String), - - /// Connection open confirm failure - #[error("Failed to build conn open confirm {0}: {1}")] - ConnOpenConfirm(ConnectionId, String), - - /// Common failures to all channel messages - #[error("Failed to build chan open msg {0}: {1}")] - ChanOpen(ChannelId, String), - - /// Channel open init failure - #[error("Failed to build channel open init {0}")] - ChanOpenInit(String), - - /// Channel open try failure - #[error("Failed to build channel open try {0}")] - ChanOpenTry(String), - - /// Channel open ack failure - #[error("Failed to build channel open ack {0}: {1}")] - ChanOpenAck(ChannelId, String), - - /// Channel open confirm failure - #[error("Failed to build channel open confirm {0}: {1}")] - ChanOpenConfirm(ChannelId, String), - - /// Packet build failure - #[error("Failed to build packet {0}: {1}")] - Packet(ChannelId, String), - - /// Packet recv failure - #[error("Failed to build recv packet {0}: {1}")] - RecvPacket(ChannelId, String), - - /// Packet acknowledgement failure - #[error("Failed to build acknowledge packet {0}: {1}")] - AckPacket(ChannelId, String), - - /// Packet timeout failure - #[error("Failed to build timeout packet {0}: {1}")] - TimeoutPacket(ChannelId, String), - - /// A message transaction failure - #[error("Message transaction failure: {0}")] - MessageTransaction(String), - - /// Failed query - #[error("Query error occurred (failed to query for {0})")] - Query(String), - - /// Keybase related error - #[error("Keybase error")] - KeyBase, - - /// ICS 007 error - #[error("ICS 007 error")] - Ics007, - - /// ICS 023 error - #[error("ICS 023 error")] - Ics023(#[from] ibc::ics23_commitment::error::Error), - - /// Invalid chain identifier - #[error("invalid chain identifier format: {0}")] - ChainIdentifier(String), - - #[error("requested proof for data in the privateStore")] - NonProvableData, - - #[error("failed to send or receive through channel")] - Channel, - - #[error("the input header is not recognized as a header for this chain")] - InvalidInputHeader, - - #[error("error raised while submitting the misbehaviour evidence: {0}")] - Misbehaviour(String), - - #[error("invalid key address: {0}")] - InvalidKeyAddress(String), - - #[error("bech32 encoding failed")] - Bech32Encoding(#[from] bech32::Error), - - #[error("client type mismatch: expected '{expected}', got '{got}'")] - ClientTypeMismatch { - expected: ClientType, - got: ClientType, - }, - - #[error("Hermes health check failed for endpoint {endpoint} on the Json RPC interface of chain {chain_id}:{address}; caused by: {cause}")] - HealthCheckJsonRpc { - chain_id: ChainId, - address: String, - endpoint: String, - cause: tendermint_rpc::error::Error, - }, - - #[error("Hermes health check failed for service {endpoint} on the gRPC interface of chain {chain_id}:{address}; caused by: {cause}")] - HealthCheckGrpc { - chain_id: ChainId, - address: String, - endpoint: String, - cause: String, - }, - - #[error("Hermes health check failed while verifying the application compatibility for chain {chain_id}:{address}; caused by: {cause}")] - SdkModuleVersion { - chain_id: ChainId, - address: String, - cause: String, - }, -} - -impl Kind { - /// Add a given source error as context for this error kind - /// - /// This is typically use with `map_err` as follows: - /// - /// ```ignore - /// let x = self.something.do_stuff() - /// .map_err(|e| error::Kind::Config.context(e))?; - /// ``` - pub fn context(self, source: impl Into) -> Context { - Context::new(self, Some(source.into())) } +} - pub fn channel(err: impl Into) -> Context { - Self::Channel.context(err) +impl Error { + pub fn send(_: crossbeam_channel::SendError) -> Error { + Error::channel_send() } } diff --git a/relayer/src/event/monitor.rs b/relayer/src/event/monitor.rs index 942153e532..24afdb0c8e 100644 --- a/relayer/src/event/monitor.rs +++ b/relayer/src/event/monitor.rs @@ -1,12 +1,12 @@ use std::{cmp::Ordering, sync::Arc}; use crossbeam_channel as channel; +use flex_error::{define_error, TraceError}; use futures::{ pin_mut, stream::{self, select_all, StreamExt}, Stream, TryStreamExt, }; -use thiserror::Error; use tokio::task::JoinHandle; use tokio::{runtime::Runtime as TokioRuntime, sync::mpsc}; use tracing::{debug, error, info, trace}; @@ -41,50 +41,63 @@ mod retry_strategy { } } -#[derive(Debug, Clone, Error)] -pub enum Error { - #[error("WebSocket driver failed: {0}")] - WebSocketDriver(RpcError), +define_error! { + #[derive(Debug, Clone)] + Error { + WebSocketDriver + [ TraceError ] + |_| { "WebSocket driver failed" }, - #[error("failed to create WebSocket driver: {0}")] - ClientCreationFailed(RpcError), + ClientCreationFailed + [ TraceError ] + |_| { "failed to create WebSocket driver" }, - #[error("failed to terminate previous WebSocket driver: {0}")] - ClientTerminationFailed(Arc), + ClientTerminationFailed + [ TraceError ] + |_| { "failed to terminate previous WebSocket driver" }, - #[error("failed to run previous WebSocket driver to completion: {0}")] - ClientCompletionFailed(RpcError), + ClientCompletionFailed + [ TraceError ] + |_| { "failed to run previous WebSocket driver to completion" }, - #[error("failed to subscribe to events via WebSocket client: {0}")] - ClientSubscriptionFailed(RpcError), + ClientSubscriptionFailed + [ TraceError ] + |_| { "failed to run previous WebSocket driver to completion" }, - #[error("failed to collect events over WebSocket subscription: {0}")] - NextEventBatchFailed(RpcError), + NextEventBatchFailed + [ TraceError ] + |_| { "failed to collect events over WebSocket subscription" }, - #[error("failed to extract IBC events: {0}")] - CollectEventsFailed(String), + CollectEventsFailed + { reason: String } + |e| { format!("failed to extract IBC events: {0}", e.reason) }, - #[error("{0}")] - SubscriptionCancelled(RpcError), + ChannelSendFailed + |_| { "failed to send event batch through channel" }, - #[error("RPC error: {0}")] - GenericRpcError(RpcError), + SubscriptionCancelled + [ TraceError ] + |_| { "subscription cancelled" }, - #[error("event monitor failed to dispatch event batch to subscribers")] - ChannelSendFailed, + Rpc + [ TraceError ] + |_| { "RPC error" }, + } } impl Error { fn canceled_or_generic(e: RpcError) -> Self { match (e.code(), e.data()) { (Code::ServerError, Some(msg)) if msg.contains("subscription was cancelled") => { - Self::SubscriptionCancelled(e) + Self::subscription_cancelled(e) } - _ => Self::GenericRpcError(e), + _ => Self::rpc(e), } } } +pub type Result = std::result::Result; + /// A batch of events from a chain at a specific height #[derive(Clone, Debug)] pub struct EventBatch { @@ -93,21 +106,9 @@ pub struct EventBatch { pub events: Vec, } -pub trait UnwrapOrClone { - fn unwrap_or_clone(self: Arc) -> Self; -} - -impl UnwrapOrClone for Result { - fn unwrap_or_clone(self: Arc) -> Self { - Arc::try_unwrap(self).unwrap_or_else(|batch| batch.as_ref().clone()) - } -} - type SubscriptionResult = RpcResult; type SubscriptionStream = dyn Stream + Send + Sync + Unpin; -pub type Result = std::result::Result; - pub type EventSender = channel::Sender>; pub type EventReceiver = channel::Receiver>; pub type TxMonitorCmd = channel::Sender; @@ -164,7 +165,7 @@ impl EventMonitor { let ws_addr = node_addr.clone(); let (client, driver) = rt .block_on(async move { WebSocketClient::new(ws_addr).await }) - .map_err(Error::ClientCreationFailed)?; + .map_err(Error::client_creation_failed)?; let (tx_err, rx_err) = mpsc::unbounded_channel(); let websocket_driver_handle = rt.spawn(run_driver(driver, tx_err.clone())); @@ -215,7 +216,7 @@ impl EventMonitor { let subscription = self .rt .block_on(self.client.subscribe(query.clone())) - .map_err(Error::ClientSubscriptionFailed)?; + .map_err(Error::client_subscription_failed)?; subscriptions.push(subscription); } @@ -238,7 +239,7 @@ impl EventMonitor { let (mut client, driver) = self .rt .block_on(WebSocketClient::new(self.node_addr.clone())) - .map_err(Error::ClientCreationFailed)?; + .map_err(Error::client_creation_failed)?; let mut driver_handle = self.rt.spawn(run_driver(driver, self.tx_err.clone())); @@ -263,7 +264,7 @@ impl EventMonitor { self.rt .block_on(driver_handle) - .map_err(|e| Error::ClientTerminationFailed(Arc::new(e)))?; + .map_err(Error::client_termination_failed)?; trace!("[{}] previous client successfully shutdown", self.chain_id); @@ -363,7 +364,7 @@ impl EventMonitor { let result = rt.block_on(async { tokio::select! { Some(batch) = batches.next() => batch, - Some(err) = self.rx_err.recv() => Err(Error::WebSocketDriver(err)), + Some(e) = self.rx_err.recv() => Err(Error::web_socket_driver(e)), } }); @@ -371,37 +372,40 @@ impl EventMonitor { Ok(batch) => self.process_batch(batch).unwrap_or_else(|e| { error!("[{}] {}", self.chain_id, e); }), - Err(Error::SubscriptionCancelled(reason)) => { - error!( - "[{}] subscription cancelled, reason: {}", - self.chain_id, reason - ); - - self.propagate_error(Error::SubscriptionCancelled(reason)) - .unwrap_or_else(|e| { - error!("[{}] {}", self.chain_id, e); - }); - - // Reconnect to the WebSocket endpoint, and subscribe again to the queries. - self.reconnect(); - - // Abort this event loop, the `run` method will start a new one. - // We can't just write `return self.run()` here because Rust - // does not perform tail call optimization, and we would - // thus potentially blow up the stack after many restarts. - return Next::Continue; - } Err(e) => { - error!("[{}] failed to collect events: {}", self.chain_id, e); - - // Reconnect to the WebSocket endpoint, and subscribe again to the queries. - self.reconnect(); - - // Abort this event loop, the `run` method will start a new one. - // We can't just write `return self.run()` here because Rust - // does not perform tail call optimization, and we would - // thus potentially blow up the stack after many restarts. - return Next::Continue; + match e.detail() { + ErrorDetail::SubscriptionCancelled(reason) => { + error!( + "[{}] subscription cancelled, reason: {}", + self.chain_id, reason + ); + + self.propagate_error(e).unwrap_or_else(|e| { + error!("[{}] {}", self.chain_id, e); + }); + + // Reconnect to the WebSocket endpoint, and subscribe again to the queries. + self.reconnect(); + + // Abort this event loop, the `run` method will start a new one. + // We can't just write `return self.run()` here because Rust + // does not perform tail call optimization, and we would + // thus potentially blow up the stack after many restarts. + return Next::Continue; + } + _ => { + error!("[{}] failed to collect events: {}", self.chain_id, e); + + // Reconnect to the WebSocket endpoint, and subscribe again to the queries. + self.reconnect(); + + // Abort this event loop, the `run` method will start a new one. + // We can't just write `return self.run()` here because Rust + // does not perform tail call optimization, and we would + // thus potentially blow up the stack after many restarts. + return Next::Continue; + } + } } } } @@ -417,7 +421,7 @@ impl EventMonitor { fn propagate_error(&self, error: Error) -> Result<()> { self.tx_batch .send(Err(error)) - .map_err(|_| Error::ChannelSendFailed)?; + .map_err(|_| Error::channel_send_failed())?; Ok(()) } @@ -426,7 +430,7 @@ impl EventMonitor { fn process_batch(&self, batch: EventBatch) -> Result<()> { self.tx_batch .send(Ok(batch)) - .map_err(|_| Error::ChannelSendFailed)?; + .map_err(|_| Error::channel_send_failed())?; Ok(()) } diff --git a/relayer/src/event/rpc.rs b/relayer/src/event/rpc.rs index 676caae9fa..34073a3927 100644 --- a/relayer/src/event/rpc.rs +++ b/relayer/src/event/rpc.rs @@ -1,13 +1,12 @@ use std::{collections::HashMap, convert::TryFrom}; -use anomaly::BoxError; use tendermint_rpc::event::{Event as RpcEvent, EventData as RpcEventData}; use ibc::ics02_client::events::NewBlock; use ibc::ics02_client::height::Height; use ibc::ics24_host::identifier::ChainId; use ibc::{ - events::{IbcEvent, RawObject}, + events::{Error as EventError, IbcEvent, RawObject}, ics02_client::events as ClientEvents, ics03_connection::events as ConnectionEvents, ics04_channel::events as ChannelEvents, @@ -57,7 +56,7 @@ pub fn get_all_events( Ok(vals) } -pub fn build_event(mut object: RawObject) -> Result { +pub fn build_event(mut object: RawObject) -> Result { match object.action.as_str() { // Client events "create_client" => Ok(IbcEvent::from(ClientEvents::CreateClient::try_from( @@ -128,7 +127,7 @@ pub fn build_event(mut object: RawObject) -> Result { )) } - event_type => Err(format!("Incorrect event type: '{}'", event_type).into()), + event_type => Err(EventError::incorrect_event_type(event_type.to_string())), } } diff --git a/relayer/src/foreign_client.rs b/relayer/src/foreign_client.rs index 9c10e7608b..55ae552307 100644 --- a/relayer/src/foreign_client.rs +++ b/relayer/src/foreign_client.rs @@ -3,15 +3,17 @@ use std::{fmt, thread, time::Duration}; use itertools::Itertools; use prost_types::Any; -use thiserror::Error; use tracing::{debug, error, info, trace, warn}; +use crate::error::Error as RelayerError; +use flex_error::define_error; use ibc::downcast; use ibc::events::{IbcEvent, IbcEventType}; use ibc::ics02_client::client_consensus::{ AnyConsensusState, AnyConsensusStateWithHeight, ConsensusState, QueryClientEventRequest, }; use ibc::ics02_client::client_state::ClientState; +use ibc::ics02_client::error::Error as ClientError; use ibc::ics02_client::events::UpdateClient; use ibc::ics02_client::header::Header; use ibc::ics02_client::misbehaviour::MisbehaviourEvidence; @@ -32,37 +34,182 @@ const MAX_MISBEHAVIOUR_CHECK_DURATION: Duration = Duration::from_secs(120); const MAX_RETRIES: usize = 5; -#[derive(Debug, Error)] -pub enum ForeignClientError { - #[error("error raised while creating client: {0}")] - ClientCreate(String), - - #[error("error raised while updating client: {0}")] - ClientUpdate(String), - - #[error("error raised while trying to refresh client {0}: {1}")] - ClientRefresh(ClientId, String), - - #[error("failed while querying for client {0} on chain id: {1} with error: {2}")] - ClientQuery(ClientId, ChainId, String), - - #[error("failed while querying Tx for client {0} on chain id: {1} with error: {2}")] - ClientEventQuery(ClientId, ChainId, String), - - #[error("failed while finding client {0}: expected chain_id in client state: {1}; actual chain_id: {2}")] - ClientFind(ClientId, ChainId, ChainId), - - #[error("client {0} on chain id {1} is expired or frozen")] - ExpiredOrFrozen(ClientId, ChainId), - - #[error("error raised while checking for misbehaviour evidence: {0}")] - Misbehaviour(String), - - #[error("cannot run misbehaviour: {0}")] - MisbehaviourExit(String), - - #[error("failed while trying to upgrade client id {0} hosted on chain id {1} with error: {2}")] - ClientUpgrade(ClientId, ChainId, String), +define_error! { + ForeignClientError { + ClientCreate + { + chain_id: ChainId, + description: String + } + [ RelayerError ] + |e| { + format_args!("error raised while creating client for chain {0}: {1}", + e.chain_id, e.description) + }, + + Client + [ ClientError ] + |_| { "ICS02 client error" }, + + ClientUpdate + { + chain_id: ChainId, + description: String + } + [ RelayerError ] + |e| { + format_args!("error raised while updating client on chain {0}: {1}", e.chain_id, e.description) + }, + + ClientAlreadyUpToDate + { + client_id: ClientId, + chain_id: ChainId, + height: Height, + } + |e| { + format_args!("Client {} is already up-to-date with chain {}@{}", + e.client_id, e.chain_id, e.height) + }, + + MissingSmallerTrustedHeight + { + chain_id: ChainId, + target_height: Height, + } + |e| { + format_args!("chain {} is missing trusted state smaller than target height {}", + e.chain_id, e.target_height) + }, + + MissingTrustedHeight + { + chain_id: ChainId, + target_height: Height, + } + |e| { + format_args!("chain {} is missing trusted state at target height {}", + e.chain_id, e.target_height) + }, + + ClientRefresh + { + client_id: ClientId, + reason: String + } + [ RelayerError ] + |e| { + format_args!("error raised while trying to refresh client {0}: {1}", + e.client_id, e.reason) + }, + + ClientQuery + { + client_id: ClientId, + chain_id: ChainId, + } + [ RelayerError ] + |e| { + format_args!("failed while querying Tx for client {0} on chain id: {1}", + e.client_id, e.chain_id) + }, + + ClientUpgrade + { + client_id: ClientId, + chain_id: ChainId, + description: String, + } + [ RelayerError ] + |e| { + format_args!("failed while trying to upgrade client id {0} for chain {1}: {2}", + e.client_id, e.chain_id, e.description) + }, + + ClientEventQuery + { + client_id: ClientId, + chain_id: ChainId, + consensus_height: Height + } + [ RelayerError ] + |e| { + format_args!("failed while querying Tx for client {0} on chain id {1} at consensus height {2}", + e.client_id, e.chain_id, e.consensus_height) + }, + + UnexpectedEvent + { + client_id: ClientId, + chain_id: ChainId, + event: String, + } + |e| { + format_args!("failed while querying Tx for client {0} on chain id {1}: query Tx-es returned unexpected event: {2}", + e.client_id, e.chain_id, e.event) + }, + + MismatchChainId + { + client_id: ClientId, + expected_chain_id: ChainId, + actual_chain_id: ChainId, + } + |e| { + format_args!("failed while finding client {0}: expected chain_id in client state: {1}; actual chain_id: {2}", + e.client_id, e.expected_chain_id, e.actual_chain_id) + }, + + ExpiredOrFrozen + { + client_id: ClientId, + chain_id: ChainId, + } + |e| { + format_args!("client {0} on chain id {1} is expired or frozen", + e.client_id, e.chain_id) + }, + + Misbehaviour + { + description: String, + } + [ RelayerError ] + |e| { + format_args!("error raised while checking for misbehaviour evidence: {0}", e.description) + }, + + MisbehaviourExit + { reason: String } + |e| { + format_args!("cannot run misbehaviour: {0}", e.reason) + }, + + SameChainId + { + chain_id: ChainId + } + |e| { + format_args!("the chain ID ({}) at the source and destination chains must be different", e.chain_id) + }, + + MissingClientIdFromEvent + { event: IbcEvent } + |e| { + format_args!("cannot extract client_id from result: {:?}", + e.event) + }, + + ChainErrorEvent + { + chain_id: ChainId, + event: IbcEvent + } + |e| { + format_args!("Failed to update client on destination {} because of error event: {}", + e.chain_id, e.event) + }, + } } #[derive(Clone, Debug)] @@ -107,11 +254,7 @@ impl ForeignClient { ) -> Result { // Sanity check if src_chain.id().eq(&dst_chain.id()) { - return Err(ForeignClientError::ClientCreate(format!( - "the source ({}) and destination ({}) chains must be different", - src_chain.id(), - dst_chain.id(), - ))); + return Err(ForeignClientError::same_chain_id(src_chain.id())); } let mut client = ForeignClient { @@ -152,7 +295,7 @@ impl ForeignClient { match host_chain.query_client_state(client_id, height) { Ok(cs) => { if cs.chain_id() != expected_target_chain.id() { - Err(ForeignClientError::ClientFind( + Err(ForeignClientError::mismatch_chain_id( client_id.clone(), expected_target_chain.id(), cs.chain_id(), @@ -166,10 +309,10 @@ impl ForeignClient { )) } } - Err(e) => Err(ForeignClientError::ClientQuery( + Err(e) => Err(ForeignClientError::client_query( client_id.clone(), host_chain.id(), - format!("{}", e), + e, )), } } @@ -177,14 +320,11 @@ impl ForeignClient { pub fn upgrade(&self) -> Result, ForeignClientError> { // Fetch the latest height of the source chain. let src_height = self.src_chain.query_latest_height().map_err(|e| { - ForeignClientError::ClientUpgrade( + ForeignClientError::client_upgrade( self.id.clone(), - self.dst_chain.id(), - format!( - "failed while querying src chain ({}) for latest height: {}", - self.src_chain.id(), - e - ), + self.src_chain.id(), + "failed while querying src chain for latest height".to_string(), + e, ) })?; @@ -197,14 +337,11 @@ impl ForeignClient { .src_chain .query_upgraded_client_state(src_height) .map_err(|e| { - ForeignClientError::ClientUpgrade( + ForeignClientError::client_upgrade( self.id.clone(), - self.dst_chain.id(), - format!( - "failed while fetching from chain {} the upgraded client state: {}", - self.src_chain.id(), - e - ), + self.src_chain.id(), + "failed while fetching from chain the upgraded client state".to_string(), + e, ) })?; @@ -213,12 +350,15 @@ impl ForeignClient { let (consensus_state, proof_upgrade_consensus_state) = self .src_chain .query_upgraded_consensus_state(src_height) - .map_err(|e| ForeignClientError::ClientUpgrade(self.id.clone(), - self.dst_chain.id(), - format!( - "failed while fetching from chain {} \ - the upgraded client consensus state: {}", - self.src_chain.id(), e)))?; + .map_err(|e| { + ForeignClientError::client_upgrade( + self.id.clone(), + self.src_chain.id(), + "failed while fetching from chain the upgraded client consensus state" + .to_string(), + e, + ) + })?; debug!( "[{}] upgraded client consensus state {:?}", @@ -227,10 +367,11 @@ impl ForeignClient { // Get signer let signer = self.dst_chain.get_signer().map_err(|e| { - ForeignClientError::ClientUpgrade( + ForeignClientError::client_upgrade( self.id.clone(), self.dst_chain.id(), - format!("failed while fetching the destination chain signer: {}", e), + "failed while fetching the destination chain signer".to_string(), + e, ) })?; @@ -247,13 +388,11 @@ impl ForeignClient { msgs.push(msg_upgrade); let res = self.dst_chain.send_msgs(msgs).map_err(|e| { - ForeignClientError::ClientUpgrade( + ForeignClientError::client_upgrade( self.id.clone(), self.dst_chain.id(), - format!( - "failed while sending message to destination chain with err: {}", - e - ), + "failed while sending message to destination chain".to_string(), + e, ) })?; @@ -278,46 +417,53 @@ impl ForeignClient { pub fn build_create_client(&self) -> Result { // Get signer let signer = self.dst_chain.get_signer().map_err(|e| { - ForeignClientError::ClientCreate(format!( - "failed while fetching the destination chain ({}) signer: {}", + ForeignClientError::client_create( self.dst_chain.id(), - e - )) + "failed while fetching the destination chain signer".to_string(), + e, + ) })?; // Build client create message with the data from source chain at latest height. let latest_height = self.src_chain.query_latest_height().map_err(|e| { - ForeignClientError::ClientCreate(format!( - "failed while querying src chain ({}) for latest height: {}", - self.src_chain.id(), - e - )) + ForeignClientError::client_create( + self.dst_chain.id(), + "failed while querying src chain ({}) for latest height: {}".to_string(), + e, + ) })?; let client_state = self .src_chain .build_client_state(latest_height) .map_err(|e| { - ForeignClientError::ClientCreate(format!( - "failed while building client state from src chain ({}) with error: {}", + ForeignClientError::client_create( self.src_chain.id(), - e - )) + "failed while querying src chain for latest height".to_string(), + e, + ) })? .wrap_any(); - let consensus_state = self.src_chain - .build_consensus_state(client_state.latest_height(), latest_height, client_state.clone()) - .map_err(|e| ForeignClientError::ClientCreate(format!("failed while building client consensus state from src chain ({}) with error: {}", self.src_chain.id(), e)))? + let consensus_state = self + .src_chain + .build_consensus_state( + client_state.latest_height(), + latest_height, + client_state.clone(), + ) + .map_err(|e| { + ForeignClientError::client_create( + self.src_chain.id(), + "failed while building client consensus state from src chain".to_string(), + e, + ) + })? .wrap_any(); //TODO Get acct_prefix - let msg = MsgCreateAnyClient::new(client_state, consensus_state, signer).map_err(|e| { - ForeignClientError::ClientCreate(format!( - "failed while building the create client message: {}", - e - )) - })?; + let msg = MsgCreateAnyClient::new(client_state, consensus_state, signer) + .map_err(ForeignClientError::client)?; Ok(msg) } @@ -330,11 +476,11 @@ impl ForeignClient { .dst_chain .send_msgs(vec![new_msg.to_any()]) .map_err(|e| { - ForeignClientError::ClientCreate(format!( - "failed sending message to dst chain ({}) with err: {}", + ForeignClientError::client_create( self.dst_chain.id(), - e - )) + "failed sending message to dst chain ".to_string(), + e, + ) })?; assert!(!res.is_empty()); @@ -343,19 +489,14 @@ impl ForeignClient { /// Sends the client creation transaction & subsequently sets the id of this ForeignClient fn create(&mut self) -> Result<(), ForeignClientError> { - match self.build_create_client_and_send() { - Err(e) => { - error!("[{}] failed CreateClient: {}", self, e); - return Err(ForeignClientError::ClientCreate(format!( - "Create client failed ({:?})", - e - ))); - } - Ok(event) => { - self.id = extract_client_id(&event)?.clone(); - info!("🍭 [{}] => {:#?}\n", self, event); - } - } + let event = self.build_create_client_and_send().map_err(|e| { + error!("[{}] failed CreateClient: {}", self, e); + e + })?; + + self.id = extract_client_id(&event)?.clone(); + info!("🍭 [{}] => {:#?}\n", self, event); + Ok(()) } @@ -364,10 +505,11 @@ impl ForeignClient { .dst_chain .query_client_state(self.id(), Height::zero()) .map_err(|e| { - ForeignClientError::ClientUpdate(format!( - "failed querying client state on dst chain {} with error: {}", - self.id, e - )) + ForeignClientError::client_refresh( + self.id().clone(), + "failed querying client state on dst chain".to_string(), + e, + ) })?; let last_update_time = self @@ -378,7 +520,7 @@ impl ForeignClient { let elapsed = Timestamp::now().duration_since(&last_update_time); if client_state.is_frozen() || client_state.expired(elapsed.unwrap_or_default()) { - return Err(ForeignClientError::ExpiredOrFrozen( + return Err(ForeignClientError::expired_or_frozen( self.id().clone(), self.dst_chain.id(), )); @@ -415,10 +557,11 @@ impl ForeignClient { ) -> Result, ForeignClientError> { // Wait for source chain to reach `target_height` while self.src_chain().query_latest_height().map_err(|e| { - ForeignClientError::ClientUpdate(format!( - "failed fetching src chain latest height with error: {}", - e - )) + ForeignClientError::client_create( + self.src_chain.id(), + "failed fetching src chain latest height with error".to_string(), + e, + ) })? < target_height { thread::sleep(Duration::from_millis(100)) @@ -429,10 +572,11 @@ impl ForeignClient { .dst_chain() .query_client_state(&self.id, Height::default()) .map_err(|e| { - ForeignClientError::ClientUpdate(format!( - "failed querying client state on dst chain {} with error: {}", - self.id, e - )) + ForeignClientError::client_create( + self.dst_chain.id(), + "failed querying client state on dst chain".to_string(), + e, + ) })?; // If not specified, set trusted state to the highest height smaller than target height. @@ -444,22 +588,18 @@ impl ForeignClient { .into_iter() .find(|h| h < &target_height) .ok_or_else(|| { - ForeignClientError::ClientUpdate(format!( - "chain {} is missing trusted state smaller than target height {}", + ForeignClientError::missing_smaller_trusted_height( self.dst_chain().id(), - target_height - )) + target_height, + ) + // )) })? } else { cs_heights .into_iter() .find(|h| h == &trusted_height) .ok_or_else(|| { - ForeignClientError::ClientUpdate(format!( - "chain {} is missing trusted state at height {}", - self.dst_chain().id(), - trusted_height - )) + ForeignClientError::missing_trusted_height(self.dst_chain().id(), target_height) })? }; @@ -475,18 +615,19 @@ impl ForeignClient { .src_chain() .build_header(trusted_height, target_height, client_state) .map_err(|e| { - ForeignClientError::ClientUpdate(format!( - "failed building header with error: {}", - e - )) + ForeignClientError::client_update( + self.src_chain.id(), + "failed building header with error".to_string(), + e, + ) })?; let signer = self.dst_chain().get_signer().map_err(|e| { - ForeignClientError::ClientUpdate(format!( - "failed getting signer for dst chain ({}) with error: {}", + ForeignClientError::client_update( self.dst_chain.id(), - e - )) + "failed getting signer for dst chain".to_string(), + e, + ) })?; let mut msgs = vec![]; @@ -538,11 +679,11 @@ impl ForeignClient { ) -> Result, ForeignClientError> { let h = if height == Height::zero() { self.src_chain.query_latest_height().map_err(|e| { - ForeignClientError::ClientUpdate(format!( - "failed while querying src chain ({}) for latest height: {}", + ForeignClientError::client_update( self.src_chain.id(), - e - )) + "failed while querying src chain ({}) for latest height".to_string(), + e, + ) })? } else { height @@ -550,20 +691,19 @@ impl ForeignClient { let new_msgs = self.build_update_client_with_trusted(h, trusted_height)?; if new_msgs.is_empty() { - return Err(ForeignClientError::ClientUpdate(format!( - "Client {} is already up-to-date with chain {}@{}", - self.id, + return Err(ForeignClientError::client_already_up_to_date( + self.id.clone(), self.src_chain.id(), - h - ))); + h, + )); } let events = self.dst_chain().send_msgs(new_msgs).map_err(|e| { - ForeignClientError::ClientUpdate(format!( - "failed sending message to dst chain ({}) with err: {}", + ForeignClientError::client_update( self.dst_chain.id(), - e - )) + "failed sending message to dst chain".to_string(), + e, + ) })?; Ok(events) @@ -571,9 +711,7 @@ impl ForeignClient { /// Attempts to update a client using header from the latest height of its source chain. pub fn update(&self) -> Result<(), ForeignClientError> { - let res = self.build_latest_update_client_and_send().map_err(|e| { - ForeignClientError::ClientUpdate(format!("build_create_client_and_send {:?}", e)) - })?; + let res = self.build_latest_update_client_and_send()?; debug!("[{}] client updated with return message {:?}\n", self, res); @@ -602,10 +740,11 @@ impl ForeignClient { .dst_chain .query_txs(QueryTxRequest::Client(request.clone())) .map_err(|e| { - ForeignClientError::ClientEventQuery( + ForeignClientError::client_event_query( self.id().clone(), self.dst_chain.id(), - format!("update event for {}: {}", consensus_height, e), + consensus_height, + e, ) }); match result { @@ -635,10 +774,10 @@ impl ForeignClient { // Regardless, just take the event from the first update. let event = events[0].clone(); let update = downcast!(event.clone() => IbcEvent::UpdateClient).ok_or_else(|| { - ForeignClientError::ClientEventQuery( + ForeignClientError::unexpected_event( self.id().clone(), self.dst_chain.id(), - format!("query Tx-es returned unexpected event {}", event.to_json()), + event.to_json(), ) })?; Ok(Some(update)) @@ -655,11 +794,7 @@ impl ForeignClient { pagination: ibc_proto::cosmos::base::query::pagination::all(), }) .map_err(|e| { - ForeignClientError::ClientQuery( - self.id().clone(), - self.src_chain.id(), - format!("{}", e), - ) + ForeignClientError::client_query(self.id().clone(), self.src_chain.id(), e) })?; consensus_states.sort_by_key(|a| std::cmp::Reverse(a.height)); Ok(consensus_states) @@ -671,14 +806,7 @@ impl ForeignClient { .dst_chain .query_consensus_state(self.id.clone(), height, Height::zero()) .map_err(|e| { - ForeignClientError::ClientQuery( - self.id.clone(), - self.dst_chain.id(), - format!( - "failed querying consensus state at height {} with error {}", - height, e - ), - ) + ForeignClientError::client_query(self.id.clone(), self.dst_chain.id(), e) })?; Ok(res) @@ -741,10 +869,10 @@ impl ForeignClient { .dst_chain() .query_client_state(&self.id, Height::zero()) .map_err(|e| { - ForeignClientError::Misbehaviour(format!( - "failed querying client state on dst chain {} with error: {}", - self.id, e - )) + ForeignClientError::misbehaviour( + format!("failed querying client state on dst chain {}", self.id), + e, + ) })?; // Get the list of consensus state heights in descending order. @@ -807,7 +935,7 @@ impl ForeignClient { // No header in events, cannot run misbehavior. // May happen on chains running older SDKs (e.g., Akash) if update_event.header.is_none() { - return Err(ForeignClientError::MisbehaviourExit( + return Err(ForeignClientError::misbehaviour_exit( "no header in update client events".to_string(), )); } @@ -819,12 +947,14 @@ impl ForeignClient { .src_chain .check_misbehaviour(update_event.clone(), client_state.clone()) .map_err(|e| { - ForeignClientError::Misbehaviour(format!( - "failed to check misbehaviour for {} at consensus height {}: {}", - update_event.client_id(), - update_event.consensus_height(), - e - )) + ForeignClientError::misbehaviour( + format!( + "failed to check misbehaviour for {} at consensus height {}", + update_event.client_id(), + update_event.consensus_height(), + ), + e, + ) })?; if misbehavior.is_some() { @@ -857,11 +987,13 @@ impl ForeignClient { evidence: MisbehaviourEvidence, ) -> Result, ForeignClientError> { let signer = self.dst_chain().get_signer().map_err(|e| { - ForeignClientError::Misbehaviour(format!( - "failed getting signer for destination chain ({}), error: {}", - self.dst_chain.id(), - e - )) + ForeignClientError::misbehaviour( + format!( + "failed getting signer for destination chain ({})", + self.dst_chain.id() + ), + e, + ) })?; let mut msgs = vec![]; @@ -887,11 +1019,13 @@ impl ForeignClient { ); let events = self.dst_chain().send_msgs(msgs).map_err(|e| { - ForeignClientError::Misbehaviour(format!( - "failed sending evidence to destination chain ({}), error: {}", - self.dst_chain.id(), - e - )) + ForeignClientError::misbehaviour( + format!( + "failed sending evidence to destination chain ({})", + self.dst_chain.id(), + ), + e, + ) })?; Ok(events) @@ -919,7 +1053,7 @@ impl ForeignClient { // Even if some states may have failed to verify, e.g. if they were expired, just // warn the user and continue. match result { - Err(ForeignClientError::MisbehaviourExit(s)) => { + Err(ForeignClientError(ForeignClientErrorDetail::MisbehaviourExit(s), _)) => { warn!( "[{}] misbehaviour checking is being disabled: {:?}", self, s @@ -937,14 +1071,23 @@ impl ForeignClient { MisbehaviourResults::ValidClient } } - Err(e) => { - if update_event.is_some() { + Err(e) => match e.detail() { + ForeignClientErrorDetail::MisbehaviourExit(s) => { + error!( + "[{}] misbehaviour checking is being disabled: {:?}", + self, s + ); MisbehaviourResults::CannotExecute - } else { - warn!("[{}] misbehaviour checking result {:?}", self, e); - MisbehaviourResults::ValidClient } - } + _ => { + if update_event.is_some() { + MisbehaviourResults::CannotExecute + } else { + warn!("[{}] misbehaviour checking result {:?}", self, e); + MisbehaviourResults::ValidClient + } + } + }, } } } @@ -961,10 +1104,9 @@ pub fn extract_client_id(event: &IbcEvent) -> Result<&ClientId, ForeignClientErr match event { IbcEvent::CreateClient(ev) => Ok(ev.client_id()), IbcEvent::UpdateClient(ev) => Ok(ev.client_id()), - other => Err(ForeignClientError::ClientCreate(format!( - "cannot extract client_id from result: {:?}", - other - ))), + _ => Err(ForeignClientError::missing_client_id_from_event( + event.clone(), + )), } } diff --git a/relayer/src/keyring.rs b/relayer/src/keyring.rs index 3fe0cd83cd..ad52f6ac37 100644 --- a/relayer/src/keyring.rs +++ b/relayer/src/keyring.rs @@ -17,7 +17,7 @@ use ripemd160::Ripemd160; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; -use errors::{Error, Kind}; +use errors::Error; pub use pub_key::EncodedPubKey; pub mod errors; @@ -82,19 +82,18 @@ impl KeyEntry { // Ensure that the public key in the key file and the one extracted from the mnemonic match. if keyfile_pubkey_bytes != derived_pubkey_bytes { - return Err(Kind::PublicKeyMismatch { - keyfile: keyfile_pubkey_bytes, - mnemonic: derived_pubkey_bytes, - } - .into()); + Err(Error::public_key_mismatch( + keyfile_pubkey_bytes, + derived_pubkey_bytes, + )) + } else { + Ok(Self { + public_key: derived_pubkey, + private_key, + account: key_file.address, + address: keyfile_address_bytes, + }) } - - Ok(Self { - public_key: derived_pubkey, - private_key, - account: key_file.address, - address: keyfile_address_bytes, - }) } } @@ -124,17 +123,17 @@ impl KeyStore for Memory { self.keys .get(key_name) .cloned() - .ok_or_else(|| Kind::KeyNotFound.into()) + .ok_or_else(Error::key_not_found) } fn add_key(&mut self, key_name: &str, key_entry: KeyEntry) -> Result<(), Error> { if self.keys.contains_key(key_name) { - return Err(Kind::ExistingKey.into()); - } + Err(Error::key_already_exist()) + } else { + self.keys.insert(key_name.to_string(), key_entry); - self.keys.insert(key_name.to_string(), key_entry); - - Ok(()) + Ok(()) + } } fn keys(&self) -> Result, Error> { @@ -167,22 +166,19 @@ impl KeyStore for Test { key_file.set_extension(KEYSTORE_FILE_EXTENSION); if !key_file.as_path().exists() { - return Err(Kind::KeyStore - .context(format!("cannot find key file at '{}'", key_file.display())) - .into()); + return Err(Error::key_file_not_found(format!("{}", key_file.display()))); } - let file = File::open(&key_file).map_err(|_| { - Kind::KeyStore.context(format!("cannot open key file at '{}'", key_file.display())) + let file = File::open(&key_file).map_err(|e| { + Error::key_file_io( + key_file.display().to_string(), + "failed to open file".to_string(), + e, + ) })?; - let key_entry = serde_json::from_reader(file).map_err(|e| { - Kind::KeyStore.context(format!( - "invalid key file at '{}': {}", - key_file.display(), - e - )) - })?; + let key_entry = serde_json::from_reader(file) + .map_err(|e| Error::key_file_decode(format!("{}", key_file.display()), e))?; Ok(key_entry) } @@ -190,19 +186,26 @@ impl KeyStore for Test { fn add_key(&mut self, key_name: &str, key_entry: KeyEntry) -> Result<(), Error> { let mut filename = self.store.join(key_name); filename.set_extension(KEYSTORE_FILE_EXTENSION); + let file_path = filename.display().to_string(); - let file = File::create(filename) - .map_err(|_| Kind::KeyStore.context("error creating the key file"))?; + let file = File::create(filename).map_err(|e| { + Error::key_file_io(file_path.clone(), "failed to create file".to_string(), e) + })?; serde_json::to_writer_pretty(file, &key_entry) - .map_err(|_| Kind::KeyStore.context("error writing the key file"))?; + .map_err(|e| Error::key_file_encode(file_path, e))?; Ok(()) } fn keys(&self) -> Result, Error> { - let dir = fs::read_dir(&self.store) - .map_err(|e| Kind::KeyStore.context(format!("cannot list keys: {}", e)))?; + let dir = fs::read_dir(&self.store).map_err(|e| { + Error::key_file_io( + self.store.display().to_string(), + "failed to list keys".to_string(), + e, + ) + })?; let ext = OsStr::new(KEYSTORE_FILE_EXTENSION); @@ -235,13 +238,15 @@ impl KeyRing { Store::Memory => Ok(Self::Memory(Memory::new(account_prefix.to_string()))), Store::Test => { - let keys_folder = disk_store_path(chain_id.as_str()).map_err(|e| { - Kind::KeyStore.context(format!("failed to compute keys folder path: {:?}", e)) - })?; + let keys_folder = disk_store_path(chain_id.as_str())?; // Create keys folder if it does not exist fs::create_dir_all(&keys_folder).map_err(|e| { - Kind::KeyStore.context(format!("failed to create keys folder: {:?}", e)) + Error::key_file_io( + keys_folder.display().to_string(), + "failed to create keys folder".to_string(), + e, + ) })?; Ok(Self::Test(Test::new( @@ -279,8 +284,7 @@ impl KeyRing { key_file_content: &str, hd_path: &HDPath, ) -> Result { - let key_file: KeyFile = - serde_json::from_str(key_file_content).map_err(|e| Kind::InvalidKey.context(e))?; + let key_file: KeyFile = serde_json::from_str(key_file_content).map_err(Error::encode)?; KeyEntry::from_key_file(key_file, hd_path) } @@ -302,7 +306,7 @@ impl KeyRing { // Compute Bech32 account let account = bech32::encode(self.account_prefix(), address.to_base32(), Variant::Bech32) - .map_err(|e| Kind::Bech32Account.context(e))?; + .map_err(Error::bech32)?; Ok(KeyEntry { public_key, @@ -317,9 +321,8 @@ impl KeyRing { let key = self.get_key(key_name)?; let private_key_bytes = key.private_key.private_key.to_bytes(); - let signing_key = SigningKey::from_bytes(private_key_bytes.as_slice()).map_err(|_| { - Kind::InvalidKey.context("could not build signing key from private key bytes") - })?; + let signing_key = + SigningKey::from_bytes(private_key_bytes.as_slice()).map_err(Error::invalid_key)?; let signature: Signature = signing_key.sign(&msg); Ok(signature.as_ref().to_vec()) @@ -339,13 +342,13 @@ fn private_key_from_mnemonic( hd_path: &StandardHDPath, ) -> Result { let mnemonic = Mnemonic::from_phrase(mnemonic_words, Language::English) - .map_err(|e| Kind::InvalidMnemonic.context(e))?; + .map_err(Error::invalid_mnemonic)?; let seed = Seed::new(&mnemonic, ""); let private_key = ExtendedPrivKey::new_master(Network::Bitcoin, seed.as_bytes()) .and_then(|k| k.derive_priv(&Secp256k1::new(), &DerivationPath::from(hd_path))) - .map_err(|e| Kind::PrivateKey.context(e))?; + .map_err(Error::private_key)?; Ok(private_key) } @@ -371,14 +374,13 @@ fn decode_bech32(input: &str) -> Result, Error> { let bytes = bech32::decode(input) .and_then(|(_, data, _)| Vec::from_base32(&data)) - .map_err(|e| Kind::Bech32Account.context(e))?; + .map_err(Error::bech32_account)?; Ok(bytes) } fn disk_store_path(folder_name: &str) -> Result { - let home = dirs_next::home_dir() - .ok_or_else(|| Kind::KeyStore.context("cannot retrieve home folder location"))?; + let home = dirs_next::home_dir().ok_or_else(Error::home_location_unavailable)?; let folder = Path::new(home.as_path()) .join(KEYSTORE_DEFAULT_FOLDER) diff --git a/relayer/src/keyring/errors.rs b/relayer/src/keyring/errors.rs index ca2b44ba50..35c3b54abc 100644 --- a/relayer/src/keyring/errors.rs +++ b/relayer/src/keyring/errors.rs @@ -1,46 +1,105 @@ -use anomaly::{BoxError, Context}; -use thiserror::Error; +use flex_error::{define_error, DisplayOnly, TraceError}; +use std::io::Error as IoError; -pub type Error = anomaly::Error; +define_error! { + Error { + InvalidKey + [ TraceError ] + |_| { "invalid key: could not build signing key from private key bytes" }, -#[derive(Clone, Debug, Error)] -pub enum Kind { - #[error("invalid key")] - InvalidKey, + KeyNotFound + |_| { "key not found" }, - #[error("key not found")] - KeyNotFound, + KeyAlreadyExist + |_| { "key already exist" }, - #[error("key already exists")] - ExistingKey, + InvalidMnemonic + [ DisplayOnly ] + |_| { "invalid mnemonic" }, - #[error("invalid mnemonic")] - InvalidMnemonic, + PrivateKey + [ TraceError ] + |_| { "cannot generate private key" }, - #[error("cannot generate private key")] - PrivateKey, + UnsupportedPublicKey + { key_type: String } + |e| { + format!("unsupported public key: {}. only secp256k1 pub keys are currently supported", + e.key_type) + }, - #[error("cannot deserialize the encoded public key {0} with error {1}")] - EncodedPublicKey(String, String), + EncodedPublicKey + { + key: String, + } + [ TraceError ] + |e| { + format!("cannot deserialize the encoded public key {0}", + e.key) + }, - #[error("cannot generate bech32 account")] - Bech32Account, + Bech32Account + [ TraceError ] + |_| { "cannot generate bech32 account" }, - #[error("bech32 error")] - Bech32, + Bech32 + [ TraceError ] + |_| { "bech32 error" }, - #[error("mismatch between the public key in the key file and the public key in the mnemonic")] - PublicKeyMismatch { keyfile: Vec, mnemonic: Vec }, + PublicKeyMismatch + { keyfile: Vec, mnemonic: Vec } + |_| { "mismatch between the public key in the key file and the public key in the mnemonic" }, - #[error("key store error")] - KeyStore, + KeyFileEncode + { file_path: String } + [ TraceError ] + |e| { + format!("error encoding key file at '{}'", + e.file_path) + }, - #[error("invalid HD path: {0}")] - InvalidHdPath(String), -} + Encode + [ TraceError ] + |_| { "error encoding key" }, + + KeyFileDecode + { file_path: String } + [ TraceError ] + |e| { + format!("error decoding key file at '{}'", + e.file_path) + }, + + KeyFileIo + { + file_path: String, + description: String, + } + [ TraceError ] + |e| { + format!("I/O error on key file at '{}': {}", + e.file_path, e.description) + }, + + KeyFileNotFound + { file_path: String } + |e| { + format!("cannot find key file at '{}'", + e.file_path) + }, + + HomeLocationUnavailable + |_| { "home location is unavailable" }, + + KeyStore + |_| { "key store error" }, -impl Kind { - pub fn context(self, source: impl Into) -> Context { - Context::new(self, Some(source.into())) + InvalidHdPath + { + path: String, + } + |e| { + format!("invalid HD path: {0}", e.path) + }, } } diff --git a/relayer/src/keyring/pub_key.rs b/relayer/src/keyring/pub_key.rs index 1974985fe2..97ba01bc09 100644 --- a/relayer/src/keyring/pub_key.rs +++ b/relayer/src/keyring/pub_key.rs @@ -5,7 +5,7 @@ use subtle_encoding::base64; use tracing::{error, trace}; use super::decode_bech32; -use super::errors::{Error, Kind}; +use super::errors::Error; #[derive(Debug)] pub enum EncodedPubKey { @@ -64,14 +64,10 @@ impl FromStr for EncodedPubKey { ); if proto.tpe != "/cosmos.crypto.secp256k1.PubKey" { - return Err(Kind::EncodedPublicKey( - s.to_string(), - "only secp256k1 pub keys are currently supported".to_string(), - ) - .into()); + Err(Error::unsupported_public_key(proto.tpe)) + } else { + Ok(EncodedPubKey::Proto(proto)) } - - Ok(EncodedPubKey::Proto(proto)) } Err(e) if e.classify() == serde_json::error::Category::Syntax => { // Input is not syntactically-correct JSON. @@ -85,7 +81,7 @@ impl FromStr for EncodedPubKey { s, e ); - Err(Kind::EncodedPublicKey(s.to_string(), e.to_string()).into()) + Err(Error::encoded_public_key(s.to_string(), e)) } } } diff --git a/relayer/src/lib.rs b/relayer/src/lib.rs index a6be011169..ea56af7282 100644 --- a/relayer/src/lib.rs +++ b/relayer/src/lib.rs @@ -29,6 +29,7 @@ pub mod link; pub mod macros; pub mod object; pub mod registry; +pub mod sdk_error; pub mod supervisor; pub mod telemetry; pub mod transfer; diff --git a/relayer/src/light_client/tendermint.rs b/relayer/src/light_client/tendermint.rs index 170091488f..c67285be91 100644 --- a/relayer/src/light_client/tendermint.rs +++ b/relayer/src/light_client/tendermint.rs @@ -30,12 +30,7 @@ use ibc::{ }; use tracing::trace; -use crate::error::Kind; -use crate::{ - chain::CosmosSdkChain, - config::ChainConfig, - error::{self, Error}, -}; +use crate::{chain::CosmosSdkChain, config::ChainConfig, error::Error}; use super::Verified; @@ -65,8 +60,8 @@ impl super::LightClient for LightClient { ) -> Result, Error> { trace!(%trusted, %target, "light client verification"); - let target_height = TMHeight::try_from(target.revision_height) - .map_err(|e| error::Kind::InvalidHeight.context(e))?; + let target_height = + TMHeight::try_from(target.revision_height).map_err(Error::invalid_height)?; let client = self.prepare_client(client_state)?; let mut state = self.prepare_state(trusted)?; @@ -74,7 +69,7 @@ impl super::LightClient for LightClient { // Verify the target header let target = client .verify_to_target(target_height, &mut state) - .map_err(|e| error::Kind::LightClient(self.chain_id.to_string()).context(e))?; + .map_err(|e| Error::light_client(self.chain_id.to_string(), e))?; // Collect the verification trace for the target block let target_trace = state.get_trace(target.height()); @@ -93,8 +88,7 @@ impl super::LightClient for LightClient { fn fetch(&mut self, height: ibc::Height) -> Result { trace!(%height, "fetching header"); - let height = TMHeight::try_from(height.revision_height) - .map_err(|e| error::Kind::InvalidHeight.context(e))?; + let height = TMHeight::try_from(height.revision_height).map_err(Error::invalid_height)?; self.fetch_light_block(AtHeight::At(height)) } @@ -112,14 +106,14 @@ impl super::LightClient for LightClient { crate::time!("light client check_misbehaviour"); let update_header = update.header.clone().ok_or_else(|| { - Kind::Misbehaviour(format!( + Error::misbehaviour(format!( "missing header in update client event {}", self.chain_id )) })?; let update_header = downcast!(update_header => AnyHeader::Tendermint).ok_or_else(|| { - Kind::Misbehaviour(format!( + Error::misbehaviour(format!( "header type incompatible for chain {}", self.chain_id )) @@ -172,7 +166,7 @@ impl super::LightClient for LightClient { impl LightClient { pub fn from_config(config: &ChainConfig, peer_id: PeerId) -> Result { let rpc_client = rpc::HttpClient::new(config.rpc_addr.clone()) - .map_err(|e| error::Kind::LightClient(config.rpc_addr.to_string()).context(e))?; + .map_err(|e| Error::rpc(config.rpc_addr.clone(), e))?; let io = components::io::ProdIo::new(peer_id, rpc_client, Some(config.rpc_timeout)); @@ -191,10 +185,7 @@ impl LightClient { let client_state = downcast!(client_state => AnyClientState::Tendermint).ok_or_else(|| { - error::Kind::ClientTypeMismatch { - expected: ClientType::Tendermint, - got: client_state.client_type(), - } + Error::client_type_mismatch(ClientType::Tendermint, client_state.client_type()) })?; let params = TmOptions { @@ -215,8 +206,8 @@ impl LightClient { } fn prepare_state(&self, trusted: ibc::Height) -> Result { - let trusted_height = TMHeight::try_from(trusted.revision_height) - .map_err(|e| error::Kind::InvalidHeight.context(e))?; + let trusted_height = + TMHeight::try_from(trusted.revision_height).map_err(Error::invalid_height)?; let trusted_block = self.fetch_light_block(AtHeight::At(trusted_height))?; @@ -229,11 +220,9 @@ impl LightClient { fn fetch_light_block(&self, height: AtHeight) -> Result { use tendermint_light_client::components::io::Io; - self.io.fetch_light_block(height).map_err(|e| { - error::Kind::LightClient(self.chain_id.to_string()) - .context(e) - .into() - }) + self.io + .fetch_light_block(height) + .map_err(|e| Error::light_client_io(self.chain_id.to_string(), e)) } fn adjust_headers( diff --git a/relayer/src/link.rs b/relayer/src/link.rs index 2292e8f5e5..98eaf2184b 100644 --- a/relayer/src/link.rs +++ b/relayer/src/link.rs @@ -12,7 +12,7 @@ use crate::channel::{Channel, ChannelSide}; use crate::link::error::LinkError; use crate::link::relay_path::RelayPath; -mod error; +pub mod error; mod operational_data; mod relay_path; mod relay_summary; @@ -45,12 +45,7 @@ impl Link { .src_chain() .query_channel(self.a_to_b.src_port_id(), a_channel_id, Height::default()) .map_err(|e| { - LinkError::Failed(format!( - "channel {} does not exist on chain {}; context={}", - a_channel_id, - self.a_to_b.src_chain().id(), - e - )) + LinkError::channel_not_found(a_channel_id.clone(), self.a_to_b.src_chain().id(), e) })?; let b_channel_id = self.a_to_b.dst_channel_id()?; @@ -60,12 +55,7 @@ impl Link { .dst_chain() .query_channel(self.a_to_b.dst_port_id(), b_channel_id, Height::default()) .map_err(|e| { - LinkError::Failed(format!( - "channel {} does not exist on chain {}; context={}", - b_channel_id, - self.a_to_b.dst_chain().id(), - e - )) + LinkError::channel_not_found(b_channel_id.clone(), self.a_to_b.dst_chain().id(), e) })?; if a_channel.state_matches(&ChannelState::Closed) @@ -86,38 +76,28 @@ impl Link { let a_channel_id = &opts.src_channel_id; let a_channel = a_chain .query_channel(&opts.src_port_id, a_channel_id, Height::default()) - .map_err(|e| { - LinkError::Failed(format!( - "channel {} does not exist on chain {}; context={}", - a_channel_id.clone(), - a_chain.id(), - e - )) - })?; + .map_err(|e| LinkError::channel_not_found(a_channel_id.clone(), a_chain.id(), e))?; if !a_channel.state_matches(&ChannelState::Open) && !a_channel.state_matches(&ChannelState::Closed) { - return Err(LinkError::ConstructorFailed( + return Err(LinkError::invalid_channel_state( a_channel_id.clone(), - opts.src_port_id, a_chain.id(), )); } - let b_channel_id = a_channel.counterparty().channel_id.clone().ok_or_else(|| { - LinkError::Failed(format!( - "counterparty channel id not found for {}", - a_channel_id - )) - })?; + let b_channel_id = a_channel + .counterparty() + .channel_id + .clone() + .ok_or_else(|| LinkError::counterparty_channel_not_found(a_channel_id.clone()))?; if a_channel.connection_hops().is_empty() { - return Err(LinkError::Failed(format!( - "channel {} on chain {} has no connection hops", + return Err(LinkError::no_connection_hop( a_channel_id.clone(), - a_chain.id() - ))); + a_chain.id(), + )); } // Check that the counterparty details on the destination chain matches the source chain @@ -132,18 +112,19 @@ impl Link { port_id: opts.src_port_id.clone(), }, ) - .map_err(LinkError::Initialization)?; + .map_err(LinkError::initialization)?; // Check the underlying connection let a_connection_id = a_channel.connection_hops()[0].clone(); - let a_connection = a_chain.query_connection(&a_connection_id, Height::zero())?; + let a_connection = a_chain + .query_connection(&a_connection_id, Height::zero()) + .map_err(LinkError::relayer)?; if !a_connection.state_matches(&ConnectionState::Open) { - return Err(LinkError::Failed(format!( - "connection for channel {} on chain {} not in open state", + return Err(LinkError::channel_not_opened( a_channel_id.clone(), - a_chain.id() - ))); + a_chain.id(), + )); } let channel = Channel { diff --git a/relayer/src/link/error.rs b/relayer/src/link/error.rs index e41d19fb43..0c3c96f783 100644 --- a/relayer/src/link/error.rs +++ b/relayer/src/link/error.rs @@ -1,7 +1,8 @@ -use thiserror::Error; - +use flex_error::define_error; use ibc::events::IbcEvent; -use ibc::ics24_host::identifier::{ChainId, ChannelId, PortId}; +use ibc::ics02_client::error::Error as Ics02Error; +use ibc::ics24_host::identifier::{ChainId, ChannelId}; +use ibc::Height; use crate::channel::ChannelError; use crate::connection::ConnectionError; @@ -9,41 +10,130 @@ use crate::error::Error; use crate::foreign_client::ForeignClientError; use crate::transfer::PacketError; -#[derive(Debug, Error)] -pub enum LinkError { - #[error("failed with underlying error: {0}")] - Failed(String), +define_error! { + LinkError { + Relayer + [ Error ] + |_| { "failed with underlying error" }, + + Initialization + [ ChannelError ] + |_| { "link initialization failed during channel counterparty verification" }, + + PacketProofsConstructor + { chain_id: ChainId } + [ Error ] + |e| { + format!("failed to construct packet proofs for chain {0}", e.chain_id) + }, + + Query + { chain_id: ChainId } + [ Error ] + |e| { + format!("failed during query to chain id {0}", e.chain_id) + }, + + Channel + [ ChannelError ] + |_| { "channel error" }, + + ChannelNotFound + { + channel_id: ChannelId, + chain_id: ChainId, + } + [ Error ] + |e| { + format!("channel {} does not exist on chain {}", + e.channel_id, e.chain_id) + }, + + Connection + [ ConnectionError ] + |_| { "connection error" }, + + Client + [ ForeignClientError ] + |_| { "failed during a client operation" }, + + Packet + [ PacketError ] + |_| { "packet error" }, - #[error("failed to establish link: channel/port '{0}'/'{1}' on chain {2} not in open or close state when packets and timeouts can be relayed")] - ConstructorFailed(ChannelId, PortId, ChainId), + OldPacketClearingFailed + |_| { "clearing of old packets failed" }, - #[error("failed with underlying error: {0}")] - Generic(#[from] Error), + Send + { event: IbcEvent } + |e| { + format!("chain error when sending messages: {0}", e.event) + }, - #[error("link initialization failed during channel counterparty verification: {0}")] - Initialization(ChannelError), + MissingChannelId + { chain_id: ChainId } + |e| { + format!("missing channel_id on chain {}", e.chain_id) + }, - #[error("failed to construct packet proofs for chain {0} with error: {1}")] - PacketProofsConstructor(ChainId, Error), + Signer + { chain_id: ChainId } + [ Error ] + |e| { + format!("could not retrieve signer from src chain {}", e.chain_id) + }, - #[error("failed during query to chain id {0} with underlying error: {1}")] - QueryError(ChainId, Error), + DecrementHeight + { height: Height } + [ Ics02Error ] + |e| { + format!("Cannot clear packets @height {}, because this height cannot be decremented", e.height) + }, - #[error("connection error: {0}:")] - ConnectionError(#[from] ConnectionError), + UnexpectedEvent + { event: IbcEvent } + |e| { + format!("unexpected query tx response: {}", e.event) + }, - #[error("channel error: {0}:")] - ChannelError(#[from] ChannelError), + InvalidChannelState + { + channel_id: ChannelId, + chain_id: ChainId, + } + |e| { + format!("channel {} on chain {} not in open or close state when packets and timeouts can be relayed", + e.channel_id, e.chain_id) + }, - #[error("failed during a client operation: {0}:")] - ClientError(ForeignClientError), + ChannelNotOpened + { + channel_id: ChannelId, + chain_id: ChainId, + } + |e| { + format!("connection for channel {} on chain {} is not in open state", + e.channel_id, e.chain_id) + }, - #[error("packet error: {0}:")] - PacketError(#[from] PacketError), + CounterpartyChannelNotFound + { + channel_id: ChannelId, + } + |e| { + format!("counterparty channel id not found for {}", + e.channel_id) + }, - #[error("clearing of old packets failed")] - OldPacketClearingFailed, + NoConnectionHop + { + channel_id: ChannelId, + chain_id: ChainId, + } + |e| { + format!("channel {} on chain {} has no connection hops", + e.channel_id, e.chain_id) + }, - #[error("chain error when sending messages: {0}")] - SendError(Box), + } } diff --git a/relayer/src/link/relay_path.rs b/relayer/src/link/relay_path.rs index cca77dfe18..de1c90ebb1 100644 --- a/relayer/src/link/relay_path.rs +++ b/relayer/src/link/relay_path.rs @@ -7,7 +7,6 @@ use prost_types::Any; use tracing::{debug, error, info, trace}; use ibc::{ - downcast, events::{IbcEvent, IbcEventType, PrettyEvents}, ics04_channel::{ channel::{ChannelEnd, Order, QueryPacketEventDataRequest, State as ChannelState}, @@ -31,10 +30,11 @@ use ibc_proto::ibc::core::channel::v1::{ }; use crate::chain::handle::ChainHandle; -use crate::channel::{Channel, ChannelError}; +use crate::channel::error::ChannelError; +use crate::channel::Channel; use crate::event::monitor::EventBatch; use crate::foreign_client::{ForeignClient, ForeignClientError}; -use crate::link::error::LinkError; +use crate::link::error::{self, LinkError}; use crate::link::operational_data::{OperationalData, OperationalDataTarget, TransitMessage}; use crate::link::relay_summary::RelaySummary; @@ -100,21 +100,15 @@ impl RelayPath { } pub fn src_channel_id(&self) -> Result<&ChannelId, LinkError> { - self.channel.src_channel_id().ok_or_else(|| { - LinkError::Failed(format!( - "channel_id on source chain '{}' is 'None'", - self.src_chain().id() - )) - }) + self.channel + .src_channel_id() + .ok_or_else(|| LinkError::missing_channel_id(self.src_chain().id())) } pub fn dst_channel_id(&self) -> Result<&ChannelId, LinkError> { - self.channel.dst_channel_id().ok_or_else(|| { - LinkError::Failed(format!( - "channel_id on destination chain '{}' is 'None'", - self.dst_chain().id() - )) - }) + self.channel + .dst_channel_id() + .ok_or_else(|| LinkError::missing_channel_id(self.dst_chain().id())) } pub fn channel(&self) -> &Channel { @@ -122,43 +116,33 @@ impl RelayPath { } fn src_channel(&self, height: Height) -> Result { - Ok(self - .src_chain() + self.src_chain() .query_channel(self.src_port_id(), self.src_channel_id()?, height) - .map_err(|e| ChannelError::QueryError(self.src_chain().id(), e))?) + .map_err(|e| LinkError::channel(ChannelError::query(self.src_chain().id(), e))) } fn dst_channel(&self, height: Height) -> Result { - Ok(self - .dst_chain() + self.dst_chain() .query_channel(self.dst_port_id(), self.dst_channel_id()?, height) - .map_err(|e| ChannelError::QueryError(self.src_chain().id(), e))?) + .map_err(|e| LinkError::channel(ChannelError::query(self.src_chain().id(), e))) } fn src_signer(&self) -> Result { - self.src_chain().get_signer().map_err(|e| { - LinkError::Failed(format!( - "could not retrieve signer from src chain {} with error: {}", - self.src_chain().id(), - e - )) - }) + self.src_chain() + .get_signer() + .map_err(|e| LinkError::signer(self.src_chain().id(), e)) } fn dst_signer(&self) -> Result { - self.dst_chain().get_signer().map_err(|e| { - LinkError::Failed(format!( - "could not retrieve signer from dst chain {} with error: {}", - self.dst_chain().id(), - e - )) - }) + self.dst_chain() + .get_signer() + .map_err(|e| LinkError::signer(self.dst_chain().id(), e)) } pub fn dst_latest_height(&self) -> Result { self.dst_chain() .query_latest_height() - .map_err(|e| LinkError::QueryError(self.dst_chain().id(), e)) + .map_err(|e| LinkError::query(self.dst_chain().id(), e)) } fn unordered_channel(&self) -> bool { @@ -173,14 +157,14 @@ impl RelayPath { let client = self.restore_dst_client(); client .build_update_client(height) - .map_err(LinkError::ClientError) + .map_err(LinkError::client) } pub fn build_update_client_on_src(&self, height: Height) -> Result, LinkError> { let client = self.restore_src_client(); client .build_update_client(height) - .map_err(LinkError::ClientError) + .map_err(LinkError::client) } fn build_chan_close_confirm_from_event(&self, event: &IbcEvent) -> Result { @@ -188,7 +172,7 @@ impl RelayPath { let proofs = self .src_chain() .build_channel_proofs(self.src_port_id(), src_channel_id, event.height()) - .map_err(|e| ChannelError::Failed(format!("failed to build channel proofs: {}", e)))?; + .map_err(|e| LinkError::channel(ChannelError::channel_proof(e)))?; // Build the domain type message let new_msg = MsgChannelCloseConfirm { @@ -259,8 +243,7 @@ impl RelayPath { return Ok(()); } } - - Err(LinkError::OldPacketClearingFailed) + Err(LinkError::old_packet_clearing_failed()) } /// Clears any packets that were sent before `height`, either if the `clear_packets` flag @@ -275,17 +258,9 @@ impl RelayPath { // Clearing may still happen: upon new blocks, when `force = true`. self.clear_packets = false; - let clear_height = if let Some(height) = height { - Some(height.decrement().map_err(|e| { - LinkError::Failed(format!( - "Cannot clear packets at height {}, because this height cannot be decremented: {}", - height, - e.to_string() - )) - })?) - } else { - None - }; + let clear_height = height + .map(|h| h.decrement().map_err(|e| LinkError::decrement_height(h, e))) + .transpose()?; info!(height = ?clear_height, "[{}] clearing pending packets", self); @@ -480,9 +455,9 @@ impl RelayPath { return Ok(summary); } - Err(LinkError::SendError(ev)) => { + Err(LinkError(error::LinkErrorDetail::Send(e), _)) => { // This error means we could retry - error!("[{}] error {}", self, ev); + error!("[{}] error {}", self, e.event); if i + 1 == MAX_RETRIES { error!( "[{}] {}/{} retries exhausted. giving up", @@ -605,7 +580,7 @@ impl RelayPath { let msgs = odata.assemble_msgs(self)?; - let tx_events = target.send_msgs(msgs)?; + let tx_events = target.send_msgs(msgs).map_err(LinkError::relayer)?; info!("[{}] result {}\n", self, PrettyEvents(&tx_events)); let ev = tx_events @@ -614,20 +589,21 @@ impl RelayPath { .find(|event| matches!(event, IbcEvent::ChainError(_))); match ev { - Some(ev) => Err(LinkError::SendError(Box::new(ev))), + Some(ev) => Err(LinkError::send(ev)), None => Ok(RelaySummary::from_events(tx_events)), } } /// Checks if a sent packet has been received on destination. fn send_packet_received_on_dst(&self, packet: &Packet) -> Result { - let unreceived_packet = - self.dst_chain() - .query_unreceived_packets(QueryUnreceivedPacketsRequest { - port_id: self.dst_port_id().to_string(), - channel_id: self.dst_channel_id()?.to_string(), - packet_commitment_sequences: vec![packet.sequence.into()], - })?; + let unreceived_packet = self + .dst_chain() + .query_unreceived_packets(QueryUnreceivedPacketsRequest { + port_id: self.dst_port_id().to_string(), + channel_id: self.dst_channel_id()?.to_string(), + packet_commitment_sequences: vec![packet.sequence.into()], + }) + .map_err(LinkError::relayer)?; Ok(unreceived_packet.is_empty()) } @@ -635,13 +611,16 @@ impl RelayPath { /// Checks if a packet commitment has been cleared on source. /// The packet commitment is cleared when either an acknowledgment or a timeout is received on source. fn send_packet_commitment_cleared_on_src(&self, packet: &Packet) -> Result { - let (bytes, _) = self.src_chain().build_packet_proofs( - PacketMsgType::Recv, - self.src_port_id(), - self.src_channel_id()?, - packet.sequence, - Height::zero(), - )?; + let (bytes, _) = self + .src_chain() + .build_packet_proofs( + PacketMsgType::Recv, + self.src_port_id(), + self.src_channel_id()?, + packet.sequence, + Height::zero(), + ) + .map_err(LinkError::relayer)?; Ok(bytes.is_empty()) } @@ -656,13 +635,14 @@ impl RelayPath { /// source chain of the packet, ie. the destination chain of the relay path /// that sends the acknowledgment. fn recv_packet_acknowledged_on_src(&self, packet: &Packet) -> Result { - let unreceived_ack = - self.dst_chain() - .query_unreceived_acknowledgement(QueryUnreceivedAcksRequest { - port_id: self.dst_port_id().to_string(), - channel_id: self.dst_channel_id()?.to_string(), - packet_ack_sequences: vec![packet.sequence.into()], - })?; + let unreceived_ack = self + .dst_chain() + .query_unreceived_acknowledgement(QueryUnreceivedAcksRequest { + port_id: self.dst_port_id().to_string(), + channel_id: self.dst_channel_id()?.to_string(), + packet_ack_sequences: vec![packet.sequence.into()], + }) + .map_err(LinkError::relayer)?; Ok(unreceived_ack.is_empty()) } @@ -701,7 +681,10 @@ impl RelayPath { i + 1, MAX_RETRIES, ); - let dst_tx_events = self.dst_chain().send_msgs(dst_update)?; + let dst_tx_events = self + .dst_chain() + .send_msgs(dst_update) + .map_err(LinkError::relayer)?; info!("[{}] result {}\n", self, PrettyEvents(&dst_tx_events)); dst_err_ev = dst_tx_events @@ -713,12 +696,9 @@ impl RelayPath { } } - Err(LinkError::ClientError(ForeignClientError::ClientUpdate( - format!( - "Failed to update client on destination {} with err: {}", - self.dst_chain().id(), - dst_err_ev.unwrap() - ), + Err(LinkError::client(ForeignClientError::chain_error_event( + self.dst_chain().id(), + dst_err_ev.unwrap(), ))) } @@ -742,7 +722,10 @@ impl RelayPath { dst_chain_height, ); - let src_tx_events = self.src_chain().send_msgs(src_update)?; + let src_tx_events = self + .src_chain() + .send_msgs(src_update) + .map_err(LinkError::relayer)?; info!("[{}] result {}\n", self, PrettyEvents(&src_tx_events)); src_err_ev = src_tx_events @@ -754,12 +737,9 @@ impl RelayPath { } } - Err(LinkError::ClientError(ForeignClientError::ClientUpdate( - format!( - "Failed to update client on source {} with err: {}", - self.src_chain().id(), - src_err_ev.unwrap() - ), + Err(LinkError::client(ForeignClientError::chain_error_event( + self.src_chain().id(), + src_err_ev.unwrap(), ))) } @@ -779,8 +759,10 @@ impl RelayPath { channel_id: src_channel_id.to_string(), pagination: ibc_proto::cosmos::base::query::pagination::all(), }; - let (packet_commitments, src_response_height) = - self.src_chain().query_packet_commitments(pc_request)?; + let (packet_commitments, src_response_height) = self + .src_chain() + .query_packet_commitments(pc_request) + .map_err(LinkError::relayer)?; let query_height = opt_query_height.unwrap_or(src_response_height); @@ -798,7 +780,8 @@ impl RelayPath { let sequences: Vec = self .dst_chain() - .query_unreceived_packets(request)? + .query_unreceived_packets(request) + .map_err(LinkError::relayer)? .into_iter() .map(From::from) .collect(); @@ -833,16 +816,22 @@ impl RelayPath { height: query_height, }); - events_result = self.src_chain().query_txs(query)?; + events_result = self + .src_chain() + .query_txs(query) + .map_err(LinkError::relayer)?; let mut packet_sequences = vec![]; for event in events_result.iter() { - let send_event = downcast!(event => IbcEvent::SendPacket) - .ok_or_else(|| LinkError::Failed("unexpected query tx response".into()))?; - packet_sequences.push(send_event.packet.sequence); - if packet_sequences.len() > 10 { - // Enough to print the first 10 - break; + match event { + IbcEvent::SendPacket(send_event) => { + packet_sequences.push(send_event.packet.sequence); + if packet_sequences.len() > 10 { + // Enough to print the first 10 + break; + } + } + _ => return Err(LinkError::unexpected_event(event.clone())), } } info!( @@ -875,7 +864,7 @@ impl RelayPath { let (acks_on_source, src_response_height) = self .src_chain() .query_packet_acknowledgements(pc_request) - .map_err(|e| LinkError::QueryError(self.src_chain().id(), e))?; + .map_err(|e| LinkError::query(self.src_chain().id(), e))?; let query_height = opt_query_height.unwrap_or(src_response_height); @@ -895,7 +884,7 @@ impl RelayPath { let sequences: Vec = self .dst_chain() .query_unreceived_acknowledgement(request) - .map_err(|e| LinkError::QueryError(self.dst_chain().id(), e))? + .map_err(|e| LinkError::query(self.dst_chain().id(), e))? .into_iter() .map(From::from) .collect(); @@ -932,16 +921,21 @@ impl RelayPath { sequences, height: query_height, })) - .map_err(|e| LinkError::QueryError(self.src_chain().id(), e))?; + .map_err(|e| LinkError::query(self.src_chain().id(), e))?; let mut packet_sequences = vec![]; for event in events_result.iter() { - let write_ack_event = downcast!(event => IbcEvent::WriteAcknowledgement) - .ok_or_else(|| LinkError::Failed("unexpected query tx response".into()))?; - packet_sequences.push(write_ack_event.packet.sequence); - if packet_sequences.len() > 10 { - // Enough to print the first 10 - break; + match event { + IbcEvent::WriteAcknowledgement(write_ack_event) => { + packet_sequences.push(write_ack_event.packet.sequence); + if packet_sequences.len() > 10 { + // Enough to print the first 10 + break; + } + } + _ => { + return Err(LinkError::unexpected_event(event.clone())); + } } } info!("[{}] found unprocessed WriteAcknowledgement events for {:?} (first 10 shown here; total={})", self, packet_sequences, events_result.len()); @@ -1010,7 +1004,7 @@ impl RelayPath { packet.sequence, height, ) - .map_err(|e| LinkError::PacketProofsConstructor(self.src_chain().id(), e))?; + .map_err(|e| LinkError::packet_proofs_constructor(self.src_chain().id(), e))?; let msg = MsgRecvPacket::new(packet.clone(), proofs.clone(), self.dst_signer()?); @@ -1039,7 +1033,7 @@ impl RelayPath { packet.sequence, event.height, ) - .map_err(|e| LinkError::PacketProofsConstructor(self.src_chain().id(), e))?; + .map_err(|e| LinkError::packet_proofs_constructor(self.src_chain().id(), e))?; let msg = MsgAcknowledgement::new( packet, @@ -1072,7 +1066,7 @@ impl RelayPath { port_id: self.dst_port_id().to_string(), channel_id: dst_channel_id.to_string(), }) - .map_err(|e| ChannelError::QueryError(self.dst_chain().id(), e))?; + .map_err(|e| LinkError::query(self.dst_chain().id(), e))?; (PacketMsgType::TimeoutOrdered, next_seq) } else { (PacketMsgType::TimeoutUnordered, packet.sequence) @@ -1087,7 +1081,7 @@ impl RelayPath { next_sequence_received, height, ) - .map_err(|e| LinkError::PacketProofsConstructor(self.dst_chain().id(), e))?; + .map_err(|e| LinkError::packet_proofs_constructor(self.dst_chain().id(), e))?; let msg = MsgTimeout::new( packet.clone(), @@ -1120,7 +1114,7 @@ impl RelayPath { packet.sequence, height, ) - .map_err(|e| LinkError::PacketProofsConstructor(self.dst_chain().id(), e))?; + .map_err(|e| LinkError::packet_proofs_constructor(self.dst_chain().id(), e))?; let msg = MsgTimeoutOnClose::new( packet.clone(), diff --git a/relayer/src/object.rs b/relayer/src/object.rs index 285f54ad68..8ebf92b1cc 100644 --- a/relayer/src/object.rs +++ b/relayer/src/object.rs @@ -1,4 +1,4 @@ -use anomaly::BoxError; +use flex_error::define_error; use serde::{Deserialize, Serialize}; use ibc::{ @@ -18,6 +18,8 @@ use crate::chain::{ }, handle::ChainHandle, }; +use crate::error::Error as RelayerError; +use crate::supervisor::Error as SupervisorError; /// Client #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] @@ -146,6 +148,42 @@ pub enum Object { Packet(Packet), } +define_error! { + ObjectError { + Relayer + [ RelayerError ] + | _ | { "relayer error" }, + + Supervisor + [ SupervisorError ] + | _ | { "supervisor error" }, + + RefreshNotRequired + { + client_id: ClientId, + chain_id: ChainId, + } + | e | { + format!("client '{}' on chain {} does not require refresh", + e.client_id, e.chain_id) + }, + + MissingChannelId + { event: Attributes } + | e | { + format!("channel_id missing in channel open event '{:?}'", + e.event) + }, + + MissingConnectionId + { event: ConnectionAttributes } + | e | { + format!("connection_id missing from connection handshake event '{:?}'", + e.event) + }, + } +} + impl Object { /// Returns `true` if this [`Object`] is for a [`Worker`] which is interested /// in new block events originating from the chain with the given [`ChainId`]. @@ -245,15 +283,16 @@ impl Object { pub fn for_update_client( e: &UpdateClient, dst_chain: &dyn ChainHandle, - ) -> Result { - let client_state = dst_chain.query_client_state(e.client_id(), Height::zero())?; + ) -> Result { + let client_state = dst_chain + .query_client_state(e.client_id(), Height::zero()) + .map_err(ObjectError::relayer)?; + if client_state.refresh_period().is_none() { - return Err(format!( - "client '{}' on chain {} does not require refresh", - e.client_id(), - dst_chain.id() - ) - .into()); + return Err(ObjectError::refresh_not_required( + e.client_id().clone(), + dst_chain.id(), + )); } let src_chain_id = client_state.chain_id(); @@ -270,19 +309,20 @@ impl Object { pub fn client_from_chan_open_events( e: &Attributes, // The attributes of the emitted event chain: &dyn ChainHandle, // The chain which emitted the event - ) -> Result { + ) -> Result { let channel_id = e .channel_id() - .ok_or_else(|| format!("channel_id missing in channel open event '{:?}'", e))?; + .ok_or_else(|| ObjectError::missing_channel_id(e.clone()))?; + + let client = channel_connection_client(chain, e.port_id(), channel_id) + .map_err(ObjectError::supervisor)? + .client; - let client = channel_connection_client(chain, e.port_id(), channel_id)?.client; if client.client_state.refresh_period().is_none() { - return Err(format!( - "client '{}' on chain {} does not require refresh", + return Err(ObjectError::refresh_not_required( client.client_id, - chain.id() - ) - .into()); + chain.id(), + )); } Ok(Client { @@ -297,18 +337,14 @@ impl Object { pub fn connection_from_conn_open_events( e: &ConnectionAttributes, src_chain: &dyn ChainHandle, - ) -> Result { - let connection_id = e.connection_id.as_ref().ok_or_else(|| { - format!( - "connection_id missing from connection handshake event '{:?}'", - e - ) - })?; + ) -> Result { + let connection_id = e + .connection_id + .as_ref() + .ok_or_else(|| ObjectError::missing_connection_id(e.clone()))?; - let dst_chain_id = - counterparty_chain_from_connection(src_chain, &connection_id).map_err(|_| { - "destination chain id not found during conn open handshake step".to_string() - })?; + let dst_chain_id = counterparty_chain_from_connection(src_chain, &connection_id) + .map_err(ObjectError::supervisor)?; Ok(Connection { dst_chain_id, @@ -322,20 +358,14 @@ impl Object { pub fn channel_from_chan_open_events( attributes: &Attributes, src_chain: &dyn ChainHandle, - ) -> Result { + ) -> Result { let channel_id = attributes .channel_id() - .ok_or_else(|| format!("channel_id missing in event attributes'{:?}'", attributes))?; + .ok_or_else(|| ObjectError::missing_channel_id(attributes.clone()))?; let dst_chain_id = - counterparty_chain_from_channel(src_chain, channel_id, &attributes.port_id()).map_err( - |err| { - format!( - "cannot identify destination chain from event attributes {:?}: {}", - attributes, err - ) - }, - )?; + counterparty_chain_from_channel(src_chain, channel_id, &attributes.port_id()) + .map_err(ObjectError::supervisor)?; Ok(Channel { dst_chain_id, @@ -350,20 +380,14 @@ impl Object { pub fn packet_from_chan_open_events( attributes: &Attributes, src_chain: &dyn ChainHandle, - ) -> Result { + ) -> Result { let channel_id = attributes .channel_id() - .ok_or_else(|| format!("channel_id missing in event attributes'{:?}'", attributes))?; + .ok_or_else(|| ObjectError::missing_channel_id(attributes.clone()))?; let dst_chain_id = - counterparty_chain_from_channel(src_chain, channel_id, &attributes.port_id()).map_err( - |err| { - format!( - "cannot identify destination chain from event attributes {:?}: {}", - attributes, err - ) - }, - )?; + counterparty_chain_from_channel(src_chain, channel_id, &attributes.port_id()) + .map_err(ObjectError::supervisor)?; Ok(Packet { dst_chain_id, @@ -375,12 +399,16 @@ impl Object { } /// Build the object associated with the given [`SendPacket`] event. - pub fn for_send_packet(e: &SendPacket, src_chain: &dyn ChainHandle) -> Result { + pub fn for_send_packet( + e: &SendPacket, + src_chain: &dyn ChainHandle, + ) -> Result { let dst_chain_id = counterparty_chain_from_channel( src_chain, &e.packet.source_channel, &e.packet.source_port, - )?; + ) + .map_err(ObjectError::supervisor)?; Ok(Packet { dst_chain_id, @@ -395,12 +423,13 @@ impl Object { pub fn for_write_ack( e: &WriteAcknowledgement, src_chain: &dyn ChainHandle, - ) -> Result { + ) -> Result { let dst_chain_id = counterparty_chain_from_channel( src_chain, &e.packet.destination_channel, &e.packet.destination_port, - )?; + ) + .map_err(ObjectError::supervisor)?; Ok(Packet { dst_chain_id, @@ -415,12 +444,13 @@ impl Object { pub fn for_timeout_packet( e: &TimeoutPacket, src_chain: &dyn ChainHandle, - ) -> Result { + ) -> Result { let dst_chain_id = counterparty_chain_from_channel( src_chain, &e.packet.source_channel, &e.packet.source_port, - )?; + ) + .map_err(ObjectError::supervisor)?; Ok(Packet { dst_chain_id, @@ -435,9 +465,9 @@ impl Object { pub fn for_close_init_channel( e: &CloseInit, src_chain: &dyn ChainHandle, - ) -> Result { - let dst_chain_id = - counterparty_chain_from_channel(src_chain, e.channel_id(), &e.port_id())?; + ) -> Result { + let dst_chain_id = counterparty_chain_from_channel(src_chain, e.channel_id(), &e.port_id()) + .map_err(ObjectError::supervisor)?; Ok(Packet { dst_chain_id, diff --git a/relayer/src/registry.rs b/relayer/src/registry.rs index f168a0d3d7..b980ec4377 100644 --- a/relayer/src/registry.rs +++ b/relayer/src/registry.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, sync::Arc}; -use anomaly::BoxError; +use flex_error::define_error; use tokio::runtime::Runtime as TokioRuntime; use tracing::{trace, warn}; @@ -11,6 +11,7 @@ use ibc::ics24_host::identifier::ChainId; use crate::{ chain::{handle::ChainHandle, runtime::ChainRuntime, CosmosSdkChain}, config::Config, + error::Error as RelayerError, supervisor::RwArc, }; @@ -23,6 +24,24 @@ pub struct Registry { rt: Arc, } +define_error! { + SpawnError { + Relayer + [ RelayerError ] + | _ | { "relayer error" }, + + RuntimeNotFound + | _ | { "expected runtime to be found in registry" }, + + MissingChain + { chain_id: ChainId } + | e | { + format_args!("missing chain for id ({}) in configuration file", + e.chain_id) + } + } +} + impl Registry { /// Construct a new [`Registry`] using the provided [`Config`] pub fn new(config: RwArc) -> Self { @@ -47,7 +66,7 @@ impl Registry { /// /// If there is no handle yet, this will first spawn the runtime and then /// return its handle. - pub fn get_or_spawn(&mut self, chain_id: &ChainId) -> Result, BoxError> { + pub fn get_or_spawn(&mut self, chain_id: &ChainId) -> Result, SpawnError> { self.spawn(chain_id)?; let handle = self @@ -62,7 +81,7 @@ impl Registry { /// only if the registry does not contain a handle for that runtime already. /// /// Returns whether or not the runtime was actually spawned. - pub fn spawn(&mut self, chain_id: &ChainId) -> Result { + pub fn spawn(&mut self, chain_id: &ChainId) -> Result { if !self.handles.contains_key(chain_id) { let handle = spawn_chain_runtime(&self.config, chain_id, self.rt.clone())?; self.handles.insert(chain_id.clone(), handle); @@ -89,15 +108,16 @@ pub fn spawn_chain_runtime( config: &RwArc, chain_id: &ChainId, rt: Arc, -) -> Result, BoxError> { +) -> Result, SpawnError> { let chain_config = config .read() .expect("poisoned lock") .find_chain(chain_id) .cloned() - .ok_or_else(|| format!("missing chain for id ({}) in configuration file", chain_id))?; + .ok_or_else(|| SpawnError::missing_chain(chain_id.clone()))?; - let handle = ChainRuntime::::spawn(chain_config, rt)?; + let handle = + ChainRuntime::::spawn(chain_config, rt).map_err(SpawnError::relayer)?; Ok(handle) } diff --git a/relayer/src/sdk_error.rs b/relayer/src/sdk_error.rs new file mode 100644 index 0000000000..74009ef1c4 --- /dev/null +++ b/relayer/src/sdk_error.rs @@ -0,0 +1,168 @@ +use flex_error::define_error; +use tendermint::abci::Code; +use tendermint_rpc::endpoint::broadcast::tx_commit::TxResult; + +// Provides mapping for errors returned from ibc-go and cosmos-sdk +define_error! { + SdkError { + Client + [ ClientError ] + |_| { "ICS02 Client Error" }, + + UnexpectedOk + |_| { "Expected error code, instead got Ok" }, + + UnknownSdk + { code: u32 } + |e| { format!("unknown SDK error: {}", e.code) }, + } +} + +define_error! { + ClientError { + LightClientAlreadyExists + |_| { "light client already exists" }, + + InvalidLightClient + |_| { "light client already exists" }, + + LightClientNotFound + |_| { "light client already exists" }, + + FrozenLightClient + |_| { "light client already exists" }, + + InvalidClientMetadata + |_| { "light client already exists" }, + + ConsensusStateNotFound + |_| { "light client already exists" }, + + InvalidConsensusState + |_| { "light client already exists" }, + + ClientTypeNotFound + |_| { "light client already exists" }, + + InvalidClientType + |_| { "light client already exists" }, + + CommitmentRootNotFound + |_| { "light client already exists" }, + + InvalidClientHeader + |_| { "light client already exists" }, + + InvalidLightClientMisbehavior + |_| { "light client already exists" }, + + ClientStateVerificationFailed + |_| { "light client already exists" }, + + ClientConsensusStateVerificationFailed + |_| { "light client already exists" }, + + ConnectionStateVerificationFailed + |_| { "light client already exists" }, + + ChannelStateVerificationFailed + |_| { "light client already exists" }, + + PacketCommitmentVerificationFailed + |_| { "light client already exists" }, + + PacketAcknowledgementVerificationFailed + |_| { "light client already exists" }, + + PacketReceiptVerificationFailed + |_| { "light client already exists" }, + + NextSequenceReceiveVerificationFailed + |_| { "light client already exists" }, + + SelfConsensusStateNotFound + |_| { "light client already exists" }, + + UpdateLightClientFailed + |_| { "light client already exists" }, + + InvalidUpdateClientProposal + |_| { "light client already exists" }, + + InvalidClientUpgrade + |_| { "light client already exists" }, + + InvalidHeight + |_| { "light client already exists" }, + + InvalidClientStateSubstitute + |_| { "light client already exists" }, + + InvalidUpgradeProposal + |_| { "light client already exists" }, + + InactiveClient + |_| { "light client already exists" }, + + UnknownClient + { code: u32 } + |e| { format!("unknown client error: {}", e.code) }, + } +} + +// The error code mapping follows the Go code at +// ibc-go/modules/core/02-client/types/errors.go +fn client_error_from_code(code: u32) -> ClientError { + match code { + 2 => ClientError::light_client_already_exists(), + 3 => ClientError::invalid_light_client(), + 4 => ClientError::light_client_not_found(), + 5 => ClientError::frozen_light_client(), + 6 => ClientError::invalid_client_metadata(), + 7 => ClientError::consensus_state_not_found(), + 8 => ClientError::invalid_consensus_state(), + 9 => ClientError::client_type_not_found(), + 10 => ClientError::invalid_client_type(), + 11 => ClientError::commitment_root_not_found(), + 12 => ClientError::invalid_client_header(), + 13 => ClientError::invalid_light_client_misbehavior(), + 14 => ClientError::client_state_verification_failed(), + 15 => ClientError::client_consensus_state_verification_failed(), + 16 => ClientError::connection_state_verification_failed(), + 17 => ClientError::client_state_verification_failed(), + 18 => ClientError::packet_commitment_verification_failed(), + 19 => ClientError::packet_acknowledgement_verification_failed(), + 20 => ClientError::packet_receipt_verification_failed(), + 21 => ClientError::next_sequence_receive_verification_failed(), + 22 => ClientError::self_consensus_state_not_found(), + 23 => ClientError::update_light_client_failed(), + 24 => ClientError::invalid_update_client_proposal(), + 25 => ClientError::invalid_client_upgrade(), + 26 => ClientError::invalid_height(), + 27 => ClientError::invalid_client_state_substitute(), + 28 => ClientError::invalid_upgrade_proposal(), + 29 => ClientError::inactive_client(), + _ => ClientError::unknown_client(code), + } +} + +// Converts the error in a TxResult into SdkError with the same +// mapping as defined in ibc-go and cosmos-sdk. This assumes the +// target chain we are interacting with are using cosmos-sdk and ibc-go. +// +// TODO: investigate ways to automatically generate the mapping by parsing +// the errors.go source code directly +pub fn sdk_error_from_tx_result(result: &TxResult) -> SdkError { + match result.code { + Code::Ok => SdkError::unexpected_ok(), + Code::Err(code) => { + let codespace = result.codespace.to_string(); + if codespace == "client" { + SdkError::client(client_error_from_code(code)) + } else { + // TODO: Implement mapping for other codespaces in ibc-go + SdkError::unknown_sdk(code) + } + } + } +} diff --git a/relayer/src/supervisor.rs b/relayer/src/supervisor.rs index 9346dc34d1..a8c3e0c4cd 100644 --- a/relayer/src/supervisor.rs +++ b/relayer/src/supervisor.rs @@ -1,10 +1,10 @@ use std::{ collections::HashMap, + ops::Deref, sync::{Arc, RwLock}, time::Duration, }; -use anomaly::BoxError; use crossbeam_channel::{Receiver, Sender}; use itertools::Itertools; use tracing::{debug, error, info, trace, warn}; @@ -19,7 +19,7 @@ use crate::{ chain::handle::ChainHandle, config::{ChainConfig, Config}, event, - event::monitor::{Error as EventError, EventBatch, UnwrapOrClone}, + event::monitor::{Error as EventError, ErrorDetail as EventErrorDetail, EventBatch}, object::Object, registry::Registry, telemetry::Telemetry, @@ -28,11 +28,11 @@ use crate::{ }; pub mod client_state_filter; -mod error; +pub mod error; use client_state_filter::{FilterPolicy, Permission}; -pub use error::Error; +pub use error::{Error, ErrorDetail}; pub mod dump_state; use dump_state::SupervisorState; @@ -178,9 +178,9 @@ impl Supervisor { pub fn collect_events( &self, src_chain: &dyn ChainHandle, - batch: EventBatch, + batch: &EventBatch, ) -> CollectedEvents { - let mut collected = CollectedEvents::new(batch.height, batch.chain_id); + let mut collected = CollectedEvents::new(batch.height, batch.chain_id.clone()); let handshake_enabled = self .config @@ -188,16 +188,20 @@ impl Supervisor { .expect("poisoned lock") .handshake_enabled(); - for event in batch.events { + for event in &batch.events { match event { IbcEvent::NewBlock(_) => { - collected.new_block = Some(event); + collected.new_block = Some(event.clone()); } IbcEvent::UpdateClient(ref update) => { if let Ok(object) = Object::for_update_client(update, src_chain) { // Collect update client events only if the worker exists if self.workers.contains(&object) { - collected.per_object.entry(object).or_default().push(event); + collected + .per_object + .entry(object) + .or_default() + .push(event.clone()); } } } @@ -213,7 +217,11 @@ impl Supervisor { .map(|attr| Object::connection_from_conn_open_events(attr, src_chain)); if let Some(Ok(object)) = object { - collected.per_object.entry(object).or_default().push(event); + collected + .per_object + .entry(object) + .or_default() + .push(event.clone()); } } IbcEvent::OpenInitChannel(..) | IbcEvent::OpenTryChannel(..) => { @@ -226,7 +234,11 @@ impl Supervisor { .map(|attr| Object::channel_from_chan_open_events(attr, src_chain)); if let Some(Ok(object)) = object { - collected.per_object.entry(object).or_default().push(event); + collected + .per_object + .entry(object) + .or_default() + .push(event.clone()); } } IbcEvent::OpenAckChannel(ref open_ack) => { @@ -260,7 +272,7 @@ impl Supervisor { .per_object .entry(channel_object) .or_default() - .push(event); + .push(event.clone()); } } } @@ -287,22 +299,38 @@ impl Supervisor { } IbcEvent::SendPacket(ref packet) => { if let Ok(object) = Object::for_send_packet(packet, src_chain) { - collected.per_object.entry(object).or_default().push(event); + collected + .per_object + .entry(object) + .or_default() + .push(event.clone()); } } IbcEvent::TimeoutPacket(ref packet) => { if let Ok(object) = Object::for_timeout_packet(packet, src_chain) { - collected.per_object.entry(object).or_default().push(event); + collected + .per_object + .entry(object) + .or_default() + .push(event.clone()); } } IbcEvent::WriteAcknowledgement(ref packet) => { if let Ok(object) = Object::for_write_ack(packet, src_chain) { - collected.per_object.entry(object).or_default().push(event); + collected + .per_object + .entry(object) + .or_default() + .push(event.clone()); } } IbcEvent::CloseInitChannel(ref packet) => { if let Ok(object) = Object::for_close_init_channel(packet, src_chain) { - collected.per_object.entry(object).or_default().push(event); + collected + .per_object + .entry(object) + .or_default() + .push(event.clone()); } } _ => (), @@ -330,7 +358,7 @@ impl Supervisor { } /// Run the supervisor event loop. - pub fn run(mut self) -> Result<(), BoxError> { + pub fn run(mut self) -> Result<(), Error> { self.spawn_workers(SpawnMode::Startup); let mut subscriptions = self.init_subscriptions()?; @@ -352,8 +380,8 @@ impl Supervisor { Ok(subs) => { subscriptions = subs; } - Err(Error::NoChainsAvailable) => (), - Err(e) => return Err(e.into()), + Err(Error(ErrorDetail::NoChainsAvailable(_), _)) => (), + Err(e) => return Err(e), } } } @@ -392,7 +420,7 @@ impl Supervisor { // At least one chain runtime should be available, otherwise the supervisor // cannot do anything and will hang indefinitely. if self.registry.size() == 0 { - return Err(Error::NoChainsAvailable); + return Err(Error::no_chains_available()); } Ok(subscriptions) @@ -534,17 +562,25 @@ impl Supervisor { fn handle_batch(&mut self, chain: Box, batch: ArcBatch) { let chain_id = chain.id(); - let result = match batch.unwrap_or_clone() { - Ok(batch) => self.process_batch(chain, batch), - Err(EventError::SubscriptionCancelled(_)) => { - warn!(chain.id = %chain_id, "event subscription was cancelled, clearing pending packets"); - self.clear_pending_packets(&chain_id) + match batch.deref() { + Ok(batch) => { + let _ = self + .process_batch(chain, batch) + .map_err(|e| error!("[{}] error during batch processing: {}", chain_id, e)); } - Err(e) => Err(e.into()), - }; + Err(EventError(EventErrorDetail::SubscriptionCancelled(_), _)) => { + warn!(chain.id = %chain_id, "event subscription was cancelled, clearing pending packets"); - if let Err(e) = result { - error!("[{}] error during batch processing: {}", chain_id, e); + let _ = self.clear_pending_packets(&chain_id).map_err(|e| { + error!( + "[{}] error during clearing pending packets: {}", + chain_id, e + ) + }); + } + Err(e) => { + error!("[{}] error in receiving event batch: {}", chain_id, e) + } } } @@ -552,8 +588,8 @@ impl Supervisor { fn process_batch( &mut self, src_chain: Box, - batch: EventBatch, - ) -> Result<(), BoxError> { + batch: &EventBatch, + ) -> Result<(), Error> { assert_eq!(src_chain.id(), batch.chain_id); let height = batch.height; @@ -576,30 +612,41 @@ impl Supervisor { continue; } - let src = self.registry.get_or_spawn(object.src_chain_id())?; - let dst = self.registry.get_or_spawn(object.dst_chain_id())?; + let src = self + .registry + .get_or_spawn(object.src_chain_id()) + .map_err(Error::spawn)?; + + let dst = self + .registry + .get_or_spawn(object.dst_chain_id()) + .map_err(Error::spawn)?; let worker = { let config = self.config.read().expect("poisoned lock"); self.workers.get_or_spawn(object, src, dst, &config) }; - worker.send_events(height, events, chain_id.clone())? + worker + .send_events(height, events, chain_id.clone()) + .map_err(Error::worker)? } // If there is a NewBlock event, forward the event to any workers affected by it. if let Some(IbcEvent::NewBlock(new_block)) = collected.new_block { for worker in self.workers.to_notify(&src_chain.id()) { - worker.send_new_block(height, new_block)?; + worker + .send_new_block(height, new_block) + .map_err(Error::worker)? } } Ok(()) } - fn clear_pending_packets(&mut self, chain_id: &ChainId) -> Result<(), BoxError> { + fn clear_pending_packets(&mut self, chain_id: &ChainId) -> Result<(), Error> { for worker in self.workers.workers_for_chain(chain_id) { - worker.clear_pending_packets()?; + worker.clear_pending_packets().map_err(Error::worker)?; } Ok(()) diff --git a/relayer/src/supervisor/client_state_filter.rs b/relayer/src/supervisor/client_state_filter.rs index bca1b3ba6b..48210005dc 100644 --- a/relayer/src/supervisor/client_state_filter.rs +++ b/relayer/src/supervisor/client_state_filter.rs @@ -1,17 +1,18 @@ use std::collections::HashMap; -use anomaly::BoxError; +use flex_error::define_error; use tendermint_light_client::types::TrustThreshold; use tracing::{debug, trace}; use ibc::ics02_client::client_state::{AnyClientState, ClientState}; use ibc::ics03_connection::connection::ConnectionEnd; -use ibc::ics04_channel::error::Kind; +use ibc::ics04_channel::error::Error as ChannelError; use ibc::ics24_host::identifier::{ChainId, ChannelId, ClientId, ConnectionId, PortId}; use ibc::Height; +use crate::error::Error as RelayerError; use crate::object; -use crate::registry::Registry; +use crate::registry::{Registry, SpawnError}; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Permission { @@ -36,6 +37,23 @@ enum CacheKey { Connection(ChainId, ConnectionId), } +define_error! { + FilterError { + Spawn + [ SpawnError ] + | _ | { "spawn error" }, + + Relayer + [ RelayerError ] + | _ | { "relayer error" }, + + Channel + [ ChannelError ] + | _ | { "channel error" }, + + } +} + /// A cache storing filtering status (allow or deny) for /// arbitrary identifiers. #[derive(Default, Debug)] @@ -62,7 +80,7 @@ impl FilterPolicy { client_state: &AnyClientState, connection: &ConnectionEnd, connection_id: &ConnectionId, - ) -> Result { + ) -> Result { let identifier = CacheKey::Connection(chain_id.clone(), connection_id.clone()); trace!( @@ -78,10 +96,13 @@ impl FilterPolicy { // Fetch the details of the client on counterparty chain. let counterparty_chain_id = client_state.chain_id(); - let counterparty_chain = registry.get_or_spawn(&counterparty_chain_id)?; + let counterparty_chain = registry + .get_or_spawn(&counterparty_chain_id) + .map_err(FilterError::spawn)?; let counterparty_client_id = connection.counterparty().client_id(); - let counterparty_client_state = - counterparty_chain.query_client_state(&counterparty_client_id, Height::zero())?; + let counterparty_client_state = counterparty_chain + .query_client_state(&counterparty_client_id, Height::zero()) + .map_err(FilterError::relayer)?; // Control both clients, cache their results. let client_permission = @@ -166,7 +187,7 @@ impl FilterPolicy { &mut self, registry: &mut Registry, obj: &object::Client, - ) -> Result { + ) -> Result { let identifier = CacheKey::Client(obj.dst_chain_id.clone(), obj.dst_client_id.clone()); trace!( @@ -180,7 +201,9 @@ impl FilterPolicy { return Ok(*p); } - let chain = registry.get_or_spawn(&obj.dst_chain_id)?; + let chain = registry + .get_or_spawn(&obj.dst_chain_id) + .map_err(FilterError::spawn)?; trace!( "[client filter] deciding if to relay on {:?} hosted chain {}", @@ -188,7 +211,10 @@ impl FilterPolicy { obj.dst_chain_id ); - let client_state = chain.query_client_state(&obj.dst_client_id, Height::zero())?; + let client_state = chain + .query_client_state(&obj.dst_client_id, Height::zero()) + .map_err(FilterError::relayer)?; + Ok(self.control_client(&obj.dst_chain_id, &obj.dst_client_id, &client_state)) } @@ -196,7 +222,7 @@ impl FilterPolicy { &mut self, registry: &mut Registry, obj: &object::Connection, - ) -> Result { + ) -> Result { let identifier = CacheKey::Connection(obj.src_chain_id.clone(), obj.src_connection_id.clone()); @@ -211,16 +237,23 @@ impl FilterPolicy { return Ok(*p); } - let src_chain = registry.get_or_spawn(&obj.src_chain_id)?; + let src_chain = registry + .get_or_spawn(&obj.src_chain_id) + .map_err(FilterError::spawn)?; + trace!( "[client filter] deciding if to relay on {:?} hosted on chain {}", obj, obj.src_chain_id ); - let connection_end = src_chain.query_connection(&obj.src_connection_id, Height::zero())?; - let client_state = - src_chain.query_client_state(&connection_end.client_id(), Height::zero())?; + let connection_end = src_chain + .query_connection(&obj.src_connection_id, Height::zero()) + .map_err(FilterError::relayer)?; + + let client_state = src_chain + .query_client_state(&connection_end.client_id(), Height::zero()) + .map_err(FilterError::relayer)?; self.control_connection_end_and_client( registry, @@ -237,7 +270,7 @@ impl FilterPolicy { chain_id: &ChainId, port_id: &PortId, channel_id: &ChannelId, - ) -> Result { + ) -> Result { let identifier = CacheKey::Channel(chain_id.clone(), port_id.clone(), channel_id.clone()); trace!( @@ -251,14 +284,28 @@ impl FilterPolicy { return Ok(*p); } - let src_chain = registry.get_or_spawn(&chain_id)?; - let channel_end = src_chain.query_channel(&port_id, &channel_id, Height::zero())?; + let src_chain = registry + .get_or_spawn(&chain_id) + .map_err(FilterError::spawn)?; + + let channel_end = src_chain + .query_channel(&port_id, &channel_id, Height::zero()) + .map_err(FilterError::relayer)?; + let conn_id = channel_end.connection_hops.first().ok_or_else(|| { - Kind::InvalidConnectionHopsLength(1, channel_end.connection_hops().len()) + FilterError::channel(ChannelError::invalid_connection_hops_length( + 1, + channel_end.connection_hops().len(), + )) })?; - let connection_end = src_chain.query_connection(conn_id, Height::zero())?; - let client_state = - src_chain.query_client_state(&connection_end.client_id(), Height::zero())?; + + let connection_end = src_chain + .query_connection(conn_id, Height::zero()) + .map_err(FilterError::relayer)?; + + let client_state = src_chain + .query_client_state(&connection_end.client_id(), Height::zero()) + .map_err(FilterError::relayer)?; let permission = self.control_connection_end_and_client( registry, @@ -284,7 +331,7 @@ impl FilterPolicy { &mut self, registry: &mut Registry, obj: &object::Channel, - ) -> Result { + ) -> Result { self.control_channel( registry, &obj.src_chain_id, @@ -297,7 +344,7 @@ impl FilterPolicy { &mut self, registry: &mut Registry, obj: &object::Packet, - ) -> Result { + ) -> Result { self.control_channel( registry, &obj.src_chain_id, diff --git a/relayer/src/supervisor/error.rs b/relayer/src/supervisor/error.rs index e2d73192a9..14fe540693 100644 --- a/relayer/src/supervisor/error.rs +++ b/relayer/src/supervisor/error.rs @@ -1,28 +1,70 @@ -use thiserror::Error; +use flex_error::define_error; use ibc::ics03_connection::connection::Counterparty; use ibc::ics24_host::identifier::{ChainId, ChannelId, ConnectionId, PortId}; -#[derive(Clone, Debug, Error, PartialEq, Eq)] -pub enum Error { - #[error("port/channel {0}/{1} on chain {1} is not initialized")] - ChannelUninitialized(PortId, ChannelId, ChainId), +use crate::error::Error as RelayerError; +use crate::registry::SpawnError; +use crate::worker::WorkerError; - #[error("channel {0} on chain {1} has a connection with uninitialized counterparty {:2}")] - ChannelConnectionUninitialized(ChannelId, ChainId, Counterparty), +define_error! { + Error { + ChannelUninitialized + { + port_id: PortId, + channel_id: ChannelId, + chain_id: ChainId, + } + |e| { + format_args!("channel {0} on chain {1} is not open", + e.channel_id, e.chain_id) + }, - #[error("connection {0} (underlying channel {1}) on chain {2} is not open")] - ConnectionNotOpen(ConnectionId, ChannelId, ChainId), + ChannelConnectionUninitialized + { + channel_id: ChannelId, + chain_id: ChainId, + counterparty: Counterparty + } + |e| { + format_args!("channel {} on chain {} has a connection with uninitialized counterparty {:?}", + e.channel_id, e.chain_id, e.counterparty) + }, - #[error("channel {0} on chain {1} has no connection hops specified")] - MissingConnectionHops(ChannelId, ChainId), + ConnectionNotOpen + { + connection_id: ConnectionId, + channel_id: ChannelId, + chain_id: ChainId, + } + |e| { + format_args!("connection {0} (underlying channel {1}) on chain {2} is not open", + e.connection_id, e.channel_id, e.chain_id) + }, - #[error("query failed with error: {0}")] - QueryFailed(String), + MissingConnectionHops + { + channel_id: ChannelId, + chain_id: ChainId, + } + |e| { + format_args!("channel {0} on chain {1} has no connection hops specified", + e.channel_id, e.chain_id) + }, - #[error("supervisor was not able to connect to any chains")] - NoChainsAvailable, + Relayer + [ RelayerError ] + |_| { "relayer error" }, - #[error("failed to spawn chain runtime: {0}")] - FailedToSpawnChainRuntime(String), + NoChainsAvailable + |_| { "supervisor was not able to connect to any chains" }, + + Spawn + [ SpawnError ] + |_| { "supervisor was not able to connect to any chains" }, + + Worker + [ WorkerError ] + |_| { "worker error" }, + } } diff --git a/relayer/src/supervisor/spawn.rs b/relayer/src/supervisor/spawn.rs index 6df7bf7bb0..47ebc72ebf 100644 --- a/relayer/src/supervisor/spawn.rs +++ b/relayer/src/supervisor/spawn.rs @@ -1,4 +1,3 @@ -use anomaly::BoxError; use itertools::Itertools; use tracing::{debug, error, warn}; @@ -24,6 +23,7 @@ use crate::{ object::{Channel, Client, Connection, Object, Packet}, registry::Registry, supervisor::client_state_filter::{FilterPolicy, Permission}, + supervisor::error::Error as SupervisorError, worker::WorkerMap, }; @@ -383,15 +383,13 @@ impl<'a> SpawnContext<'a> { &mut self, client: IdentifiedAnyClientState, connection: IdentifiedConnectionEnd, - ) -> Result { + ) -> Result { let counterparty_chain = self .registry - .get_or_spawn(&client.client_state.chain_id())?; + .get_or_spawn(&client.client_state.chain_id()) + .map_err(Error::spawn)?; - Ok(connection_state_on_destination( - connection, - counterparty_chain.as_ref(), - )?) + connection_state_on_destination(connection, counterparty_chain.as_ref()) } fn spawn_connection_workers( @@ -399,7 +397,7 @@ impl<'a> SpawnContext<'a> { chain: Box, client: IdentifiedAnyClientState, connection: IdentifiedConnectionEnd, - ) -> Result<(), BoxError> { + ) -> Result<(), Error> { let handshake_enabled = self .config .read() @@ -408,7 +406,8 @@ impl<'a> SpawnContext<'a> { let counterparty_chain = self .registry - .get_or_spawn(&client.client_state.chain_id())?; + .get_or_spawn(&client.client_state.chain_id()) + .map_err(Error::spawn)?; let conn_state_src = connection.connection_end.state; let conn_state_dst = @@ -475,7 +474,7 @@ impl<'a> SpawnContext<'a> { let counterparty_chain = self .registry .get_or_spawn(&client.client_state.chain_id()) - .map_err(|e| Error::FailedToSpawnChainRuntime(e.to_string()))?; + .map_err(SupervisorError::spawn)?; let counterparty_channel = channel_on_destination(&channel, &connection, counterparty_chain.as_ref())?; diff --git a/relayer/src/transfer.rs b/relayer/src/transfer.rs index aa3329fa5b..f5f360b64c 100644 --- a/relayer/src/transfer.rs +++ b/relayer/src/transfer.rs @@ -1,12 +1,10 @@ use std::time::Duration; -use thiserror::Error; -use tracing::error; - +use flex_error::{define_error, DetailOnly}; use ibc::application::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; use ibc::events::IbcEvent; use ibc::ics24_host::identifier::{ChainId, ChannelId, PortId}; -use ibc::timestamp::Timestamp; +use ibc::timestamp::{Timestamp, TimestampOverflowError}; use ibc::tx_msg::Msg; use ibc::Height; @@ -14,21 +12,42 @@ use crate::chain::handle::ChainHandle; use crate::config::ChainConfig; use crate::error::Error; -#[derive(Debug, Error)] -pub enum PacketError { - #[error("failed with underlying cause: {0}")] - Failed(String), - - #[error("key error with underlying cause: {0}")] - Key(Error), - - #[error( - "failed while submitting the Transfer message to chain {0} with underlying error: {1}" - )] - Submit(ChainId, Error), - - #[error("timestamp overflow")] - TimestampOverflow, +define_error! { + PacketError { + Relayer + [ Error ] + |_| { "relayer error" }, + + Key + [ Error ] + |_| { "key error" }, + + Submit + { chain_id: ChainId } + [ Error ] + |e| { + format!("failed while submitting the Transfer message to chain {0}", + e.chain_id) + }, + + TimestampOverflow + [ DetailOnly ] + |_| { "timestamp overflow" }, + + TxResponse + { event: String } + |e| { + format!("tx response event consists of an error: {}", + e.event) + }, + + UnexpectedEvent + { event: IbcEvent } + |e| { + format!("internal error, expected IBCEvent::ChainError, got {:?}", + e.event) + }, + } } #[derive(Clone, Debug)] @@ -54,14 +73,14 @@ pub fn build_and_send_transfer_messages( None => packet_dst_chain.get_signer(), Some(r) => Ok(r.clone().into()), } - .map_err(PacketError::Key)?; + .map_err(PacketError::key)?; - let sender = packet_src_chain.get_signer().map_err(PacketError::Key)?; + let sender = packet_src_chain.get_signer().map_err(PacketError::key)?; let timeout_timestamp = if opts.timeout_seconds == Duration::from_secs(0) { Timestamp::none() } else { - (Timestamp::now() + opts.timeout_seconds).map_err(|_| PacketError::TimestampOverflow)? + (Timestamp::now() + opts.timeout_seconds).map_err(PacketError::timestamp_overflow)? }; let timeout_height = if opts.timeout_height_offset == 0 { @@ -69,7 +88,7 @@ pub fn build_and_send_transfer_messages( } else { packet_dst_chain .query_latest_height() - .map_err(|_| PacketError::Failed("Height error".to_string()))? + .map_err(PacketError::relayer)? .add(opts.timeout_height_offset) }; @@ -91,7 +110,7 @@ pub fn build_and_send_transfer_messages( let events = packet_src_chain .send_msgs(msgs) - .map_err(|e| PacketError::Submit(packet_src_chain.id(), e))?; + .map_err(|e| PacketError::submit(packet_src_chain.id(), e))?; // Check if the chain rejected the transaction let result = events @@ -102,7 +121,7 @@ pub fn build_and_send_transfer_messages( None => Ok(events), Some(err) => { if let IbcEvent::ChainError(err) = err { - Err(PacketError::Failed(err.to_string())) + Err(PacketError::tx_response(err.clone())) } else { panic!( "internal error, expected IBCEvent::ChainError, got {:?}", diff --git a/relayer/src/upgrade_chain.rs b/relayer/src/upgrade_chain.rs index ee6428a7e4..f99cb6831e 100644 --- a/relayer/src/upgrade_chain.rs +++ b/relayer/src/upgrade_chain.rs @@ -1,32 +1,40 @@ use std::time::Duration; -use prost_types::Any; -use thiserror::Error; -use tracing::error; - +use flex_error::define_error; use ibc::ics02_client::client_state::AnyClientState; use ibc::ics02_client::height::Height; use ibc::ics24_host::identifier::{ChainId, ClientId}; use ibc::{events::IbcEvent, ics07_tendermint::client_state::ClientState}; use ibc_proto::cosmos::gov::v1beta1::MsgSubmitProposal; use ibc_proto::cosmos::upgrade::v1beta1::{Plan, SoftwareUpgradeProposal}; +use prost_types::Any; use crate::chain::{Chain, CosmosSdkChain}; use crate::config::ChainConfig; use crate::error::Error; -#[derive(Debug, Error)] -pub enum UpgradeChainError { - #[error("failed with underlying cause: {0}")] - Failed(String), +define_error! { + UpgradeChainError { + Key + [ Error ] + |_| { "key error" }, + + Submit + { chain_id: ChainId } + [ Error ] + |e| { + format!("failed while submitting the Transfer message to chain {0}", + e.chain_id) + }, + + TxResponse + { event: String } + |e| { + format!("tx response event consists of an error: {}", + e.event) + }, - #[error("key error with underlying cause: {0}")] - KeyError(Error), - - #[error( - "failed during a transaction submission step to chain id {0} with underlying error: {1}" - )] - SubmitError(ChainId, Error), + } } #[derive(Clone, Debug)] @@ -82,9 +90,7 @@ pub fn build_and_send_upgrade_chain_message( }; // build the msg submit proposal - let proposer = dst_chain - .get_signer() - .map_err(UpgradeChainError::KeyError)?; + let proposer = dst_chain.get_signer().map_err(UpgradeChainError::key)?; let coins = ibc_proto::cosmos::base::v1beta1::Coin { denom: "stake".to_string(), @@ -106,7 +112,7 @@ pub fn build_and_send_upgrade_chain_message( let events = dst_chain .send_msgs(vec![any_msg]) - .map_err(|e| UpgradeChainError::SubmitError(dst_chain.id().clone(), e))?; + .map_err(|e| UpgradeChainError::submit(dst_chain.id().clone(), e))?; // Check if the chain rejected the transaction let result = events.iter().find_map(|event| match event { @@ -116,6 +122,6 @@ pub fn build_and_send_upgrade_chain_message( match result { None => Ok(events), - Some(reason) => Err(UpgradeChainError::Failed(reason)), + Some(reason) => Err(UpgradeChainError::tx_response(reason)), } } diff --git a/relayer/src/util.rs b/relayer/src/util.rs index be2367c749..dc9e41a85e 100644 --- a/relayer/src/util.rs +++ b/relayer/src/util.rs @@ -2,7 +2,7 @@ mod block_on; pub use block_on::block_on; mod recv_multiple; -pub use recv_multiple::{recv_multiple, try_recv_multiple}; +pub use recv_multiple::try_recv_multiple; pub mod diff; pub mod iter; diff --git a/relayer/src/util/recv_multiple.rs b/relayer/src/util/recv_multiple.rs index f0992dedf5..74dc0988a6 100644 --- a/relayer/src/util/recv_multiple.rs +++ b/relayer/src/util/recv_multiple.rs @@ -1,4 +1,3 @@ -use anomaly::BoxError; use crossbeam_channel::{Receiver, Select}; pub fn try_recv_multiple(rs: &[(K, Receiver)]) -> Option<(&K, T)> { @@ -20,23 +19,3 @@ pub fn try_recv_multiple(rs: &[(K, Receiver)]) -> Option<(&K, T)> { Some((k, result)) } - -pub fn recv_multiple(rs: &[(K, Receiver)]) -> Result<(&K, T), BoxError> { - // Build a list of operations. - let mut sel = Select::new(); - for (_, r) in rs { - sel.recv(r); - } - - // Complete the selected operation. - let oper = sel.select(); - let index = oper.index(); - - // Get the receiver who is ready - let (k, r) = &rs[index]; - - // Receive the message - let result = oper.recv(r)?; - - Ok((k, result)) -} diff --git a/relayer/src/util/sled.rs b/relayer/src/util/sled.rs index 1726d3b0f8..e7fbe2d6f4 100644 --- a/relayer/src/util/sled.rs +++ b/relayer/src/util/sled.rs @@ -1,7 +1,7 @@ use serde::{de::DeserializeOwned, Serialize}; use std::marker::PhantomData; -use crate::error; +use crate::error::Error; pub fn single(prefix: impl Into>) -> SingleDb { SingleDb::new(prefix) @@ -17,11 +17,11 @@ impl SingleDb where V: Serialize + DeserializeOwned, { - pub fn get(&self, db: &sled::Db) -> Result, error::Error> { + pub fn get(&self, db: &sled::Db) -> Result, Error> { self.fetch(db, &()) } - pub fn set(&self, db: &sled::Db, value: &V) -> Result<(), error::Error> { + pub fn set(&self, db: &sled::Db, value: &V) -> Result<(), Error> { self.insert(db, &(), value) } } @@ -52,19 +52,16 @@ where prefix_bytes } - pub fn fetch(&self, db: &sled::Db, key: &K) -> Result, error::Error> { - let key_bytes = serde_cbor::to_vec(&key).map_err(|e| error::Kind::Store.context(e))?; + pub fn fetch(&self, db: &sled::Db, key: &K) -> Result, Error> { + let key_bytes = serde_cbor::to_vec(&key).map_err(Error::cbor)?; let prefixed_key_bytes = self.prefixed_key(key_bytes); - let value_bytes = db - .get(prefixed_key_bytes) - .map_err(|e| error::Kind::Store.context(e))?; + let value_bytes = db.get(prefixed_key_bytes).map_err(Error::store)?; match value_bytes { Some(bytes) => { - let value = - serde_cbor::from_slice(&bytes).map_err(|e| error::Kind::Store.context(e))?; + let value = serde_cbor::from_slice(&bytes).map_err(Error::cbor)?; Ok(value) } None => Ok(None), @@ -82,16 +79,16 @@ where // Ok(exists) // } - pub fn insert(&self, db: &sled::Db, key: &K, value: &V) -> Result<(), error::Error> { - let key_bytes = serde_cbor::to_vec(&key).map_err(|e| error::Kind::Store.context(e))?; + pub fn insert(&self, db: &sled::Db, key: &K, value: &V) -> Result<(), Error> { + let key_bytes = serde_cbor::to_vec(&key).map_err(Error::cbor)?; let prefixed_key_bytes = self.prefixed_key(key_bytes); - let value_bytes = serde_cbor::to_vec(&value).map_err(|e| error::Kind::Store.context(e))?; + let value_bytes = serde_cbor::to_vec(&value).map_err(Error::cbor)?; db.insert(prefixed_key_bytes, value_bytes) .map(|_| ()) - .map_err(|e| error::Kind::Store.context(e))?; + .map_err(Error::store)?; Ok(()) } diff --git a/relayer/src/worker.rs b/relayer/src/worker.rs index c13f344243..31904f1b6d 100644 --- a/relayer/src/worker.rs +++ b/relayer/src/worker.rs @@ -8,6 +8,9 @@ use crate::{chain::handle::ChainHandlePair, config::Config, object::Object, tele pub mod retry_strategy; +mod error; +pub use error::WorkerError; + mod handle; pub use handle::WorkerHandle; diff --git a/relayer/src/worker/channel.rs b/relayer/src/worker/channel.rs index 5403099c37..e271c34f21 100644 --- a/relayer/src/worker/channel.rs +++ b/relayer/src/worker/channel.rs @@ -1,6 +1,5 @@ use std::{thread, time::Duration}; -use anomaly::BoxError; use crossbeam_channel::Receiver; use tracing::{debug, info, warn}; @@ -11,6 +10,7 @@ use crate::{ worker::retry_strategy, }; +use super::error::RunError; use super::WorkerCmd; pub struct ChannelWorker { @@ -38,7 +38,7 @@ impl ChannelWorker { } /// Run the event loop for events associated with a [`Channel`]. - pub(crate) fn run(self) -> Result<(), BoxError> { + pub(crate) fn run(self) -> Result<(), RunError> { let a_chain = self.chains.a.clone(); let b_chain = self.chains.b.clone(); @@ -66,7 +66,8 @@ impl ChannelWorker { a_chain.clone(), b_chain.clone(), event.clone(), - )?; + ) + .map_err(RunError::channel)?; retry_with_index( retry_strategy::worker_default_strategy(), @@ -89,14 +90,15 @@ impl ChannelWorker { "Channel worker starts processing block event at {:#?}", current_height ); - let height = current_height.decrement()?; + let height = current_height.decrement().map_err(RunError::ics02)?; let (mut handshake_channel, state) = RelayChannel::restore_from_state( a_chain.clone(), b_chain.clone(), self.channel.clone(), height, - )?; + ) + .map_err(RunError::channel)?; retry_with_index(retry_strategy::worker_default_strategy(), |index| { handshake_channel.step_state(state, index) diff --git a/relayer/src/worker/client.rs b/relayer/src/worker/client.rs index 1301d17e48..ee0976449f 100644 --- a/relayer/src/worker/client.rs +++ b/relayer/src/worker/client.rs @@ -1,6 +1,5 @@ use std::{thread, time::Duration}; -use anomaly::BoxError; use crossbeam_channel::Receiver; use tracing::{debug, info, trace, warn}; @@ -8,12 +7,13 @@ use ibc::{events::IbcEvent, ics02_client::events::UpdateClient}; use crate::{ chain::handle::ChainHandlePair, - foreign_client::{ForeignClient, ForeignClientError, MisbehaviourResults}, + foreign_client::{ForeignClient, ForeignClientErrorDetail, MisbehaviourResults}, object::Client, telemetry, telemetry::Telemetry, }; +use super::error::RunError; use super::WorkerCmd; pub struct ClientWorker { @@ -41,7 +41,7 @@ impl ClientWorker { } /// Run the event loop for events associated with a [`Client`]. - pub fn run(self) -> Result<(), BoxError> { + pub fn run(self) -> Result<(), RunError> { let mut client = ForeignClient::restore( self.client.dst_client_id.clone(), self.chains.b.clone(), @@ -70,12 +70,14 @@ impl ClientWorker { ) }; } - Err(e @ ForeignClientError::ExpiredOrFrozen(..)) => { - warn!("[{}] failed to refresh client: {}", client, e); + Err(e) => { + if let ForeignClientErrorDetail::ExpiredOrFrozen(_) = e.detail() { + warn!("failed to refresh client '{}': {}", client, e); - // This worker has completed its job as the client cannot be refreshed any - // further, and can therefore exit without an error. - return Ok(()); + // This worker has completed its job as the client cannot be refreshed any + // further, and can therefore exit without an error. + return Ok(()); + } } _ => (), }; diff --git a/relayer/src/worker/connection.rs b/relayer/src/worker/connection.rs index 8464541670..ddf5dc170c 100644 --- a/relayer/src/worker/connection.rs +++ b/relayer/src/worker/connection.rs @@ -1,6 +1,5 @@ use std::{thread, time::Duration}; -use anomaly::BoxError; use crossbeam_channel::Receiver; use tracing::{debug, info, warn}; @@ -11,6 +10,7 @@ use crate::{ worker::retry_strategy, }; +use super::error::RunError; use super::WorkerCmd; pub struct ConnectionWorker { @@ -38,7 +38,7 @@ impl ConnectionWorker { } /// Run the event loop for events associated with a [`Connection`]. - pub(crate) fn run(self) -> Result<(), BoxError> { + pub(crate) fn run(self) -> Result<(), RunError> { let a_chain = self.chains.a.clone(); let b_chain = self.chains.b.clone(); @@ -67,7 +67,8 @@ impl ConnectionWorker { a_chain.clone(), b_chain.clone(), event.clone(), - )?; + ) + .map_err(RunError::connection)?; retry_with_index( retry_strategy::worker_default_strategy(), @@ -92,7 +93,7 @@ impl ConnectionWorker { current_height ); - let height = current_height.decrement()?; + let height = current_height.decrement().map_err(RunError::ics02)?; let (mut handshake_connection, state) = RelayConnection::restore_from_state( @@ -100,7 +101,8 @@ impl ConnectionWorker { b_chain.clone(), self.connection.clone(), height, - )?; + ) + .map_err(RunError::connection)?; retry_with_index(retry_strategy::worker_default_strategy(), |index| { handshake_connection.step_state(state, index) diff --git a/relayer/src/worker/error.rs b/relayer/src/worker/error.rs new file mode 100644 index 0000000000..3aefeda5f8 --- /dev/null +++ b/relayer/src/worker/error.rs @@ -0,0 +1,50 @@ +use flex_error::define_error; + +use crate::channel::ChannelError; +use crate::connection::ConnectionError; +use crate::link::error::LinkError; +use ibc::ics02_client::error::Error as Ics02Error; + +define_error! { + RunError { + Ics02 + [ Ics02Error ] + | _ | { "client errror" }, + + Connection + [ ConnectionError ] + | _ | { "connection errror" }, + + Channel + [ ChannelError ] + | _ | { "channel errror" }, + + Link + [ LinkError ] + | _ | { "link errror" }, + + Retry + { retries: retry::Error } + | e | { + format_args!("Packet worker failed after {} retries", + e.retries) + } + } +} + +define_error! { + WorkerError { + ChannelSend + { reason: String } + |e| { + format_args!("error sending through crossbeam channel: {}", + e.reason) + }, + } +} + +impl WorkerError { + pub fn send(e: crossbeam_channel::SendError) -> WorkerError { + WorkerError::channel_send(format!("{}", e)) + } +} diff --git a/relayer/src/worker/handle.rs b/relayer/src/worker/handle.rs index 2d18420134..3780a782df 100644 --- a/relayer/src/worker/handle.rs +++ b/relayer/src/worker/handle.rs @@ -3,7 +3,6 @@ use std::{ thread::{self, JoinHandle}, }; -use anomaly::BoxError; use crossbeam_channel::Sender; use tracing::trace; @@ -13,6 +12,7 @@ use ibc::{ use crate::{event::monitor::EventBatch, object::Object}; +use super::error::WorkerError; use super::{WorkerCmd, WorkerId}; /// Handle to a [`Worker`], for sending [`WorkerCmd`]s to it. @@ -53,33 +53,35 @@ impl WorkerHandle { height: Height, events: Vec, chain_id: ChainId, - ) -> Result<(), BoxError> { + ) -> Result<(), WorkerError> { let batch = EventBatch { chain_id, height, events, }; - self.tx.send(WorkerCmd::IbcEvents { batch })?; - Ok(()) + self.tx + .send(WorkerCmd::IbcEvents { batch }) + .map_err(WorkerError::send) } - /// Notify the worker that a new block as been committed. - pub fn send_new_block(&self, height: Height, new_block: NewBlock) -> Result<(), BoxError> { - self.tx.send(WorkerCmd::NewBlock { height, new_block })?; - Ok(()) + /// Send a batch of [`NewBlock`] event to the worker. + pub fn send_new_block(&self, height: Height, new_block: NewBlock) -> Result<(), WorkerError> { + self.tx + .send(WorkerCmd::NewBlock { height, new_block }) + .map_err(WorkerError::send) } /// Instruct the worker to clear pending packets. - pub fn clear_pending_packets(&self) -> Result<(), BoxError> { - self.tx.send(WorkerCmd::ClearPendingPackets)?; - Ok(()) + pub fn clear_pending_packets(&self) -> Result<(), WorkerError> { + self.tx + .send(WorkerCmd::ClearPendingPackets) + .map_err(WorkerError::send) } /// Shutdown the worker. - pub fn shutdown(&self) -> Result<(), BoxError> { - self.tx.send(WorkerCmd::Shutdown)?; - Ok(()) + pub fn shutdown(&self) -> Result<(), WorkerError> { + self.tx.send(WorkerCmd::Shutdown).map_err(WorkerError::send) } /// Wait for the worker thread to finish. diff --git a/relayer/src/worker/packet.rs b/relayer/src/worker/packet.rs index 83f77e2b36..3b65a9a834 100644 --- a/relayer/src/worker/packet.rs +++ b/relayer/src/worker/packet.rs @@ -1,6 +1,5 @@ use std::time::Duration; -use anomaly::BoxError; use crossbeam_channel::Receiver; use tracing::{error, info, warn}; @@ -14,6 +13,7 @@ use crate::{ worker::retry_strategy, }; +use super::error::RunError; use super::WorkerCmd; enum Step { @@ -48,7 +48,7 @@ impl PacketWorker { } /// Run the event loop for events associated with a [`Packet`]. - pub fn run(self) -> Result<(), BoxError> { + pub fn run(self) -> Result<(), RunError> { let mut link = Link::new_from_opts( self.chains.a.clone(), self.chains.b.clone(), @@ -56,10 +56,13 @@ impl PacketWorker { src_port_id: self.path.src_port_id.clone(), src_channel_id: self.path.src_channel_id.clone(), }, - )?; + ) + .map_err(RunError::link)?; + + let is_closed = link.is_closed().map_err(RunError::link)?; // TODO: Do periodical checks that the link is closed (upon every retry in the loop). - if link.is_closed()? { + if is_closed { warn!("channel is closed, exiting"); return Ok(()); } @@ -89,7 +92,7 @@ impl PacketWorker { } Err(retries) => { - return Err(format!("Packet worker failed after {} retries", retries).into()); + return Err(RunError::retry(retries)); } } }