diff --git a/.changelog/epilogue.md b/.changelog/epilogue.md deleted file mode 100644 index fd39bdcc34..0000000000 --- a/.changelog/epilogue.md +++ /dev/null @@ -1,1081 +0,0 @@ -## v0.6.1 -*July 22nd, 2021* - -This minor release mainly improves the reliability of the relayer -by ensuring that pending packets are cleared on start, -and that Hermes can recover from the WebSocket subscriptions -being closed under its feet by Tendermint. - -Upgrading from version `0.6.0` to `0.6.1` requires no explicit steps. - -> **WARNING:** Due to a regression ([#1229]), the `upgrade client`, -> `tx raw upgrade-clients`, and `tx raw upgrade-chain` commands have -> been temporarily disabled in this version. -> These commands will be re-enabled in the next version. - -### FEATURES - -- [ibc] - - Enable `pub` access to verification methods of ICS 03 & 04 ([#1198]) - - Add `ics26_routing::handler::decode` function ([#1194]) - - Add a pseudo root to `MockConsensusState` ([#1215]) - -### IMPROVEMENTS - -- [ibc-relayer-cli] - - Add CLI git hash ([#1094]) - - Fix unwraps in `packet query` CLIs ([#1114]) - -### BUG FIXES - -- [ibc] - - Fix stack overflow in `MockHeader` implementation ([#1192]) - - Align `as_str` and `from_str` behavior in `ClientType` ([#1192]) - -- [ibc-relayer] - - Ensure pending packets are cleared on start ([#1200]) - - Recover from missed RPC events after WebSocket subscription is closed by Tendermint ([#1196]) - - -[#1094]: https://github.com/informalsystems/ibc-rs/issues/1094 -[#1114]: https://github.com/informalsystems/ibc-rs/issues/1114 -[#1192]: https://github.com/informalsystems/ibc-rs/issues/1192 -[#1194]: https://github.com/informalsystems/ibc-rs/issues/1194 -[#1196]: https://github.com/informalsystems/ibc-rs/issues/1196 -[#1198]: https://github.com/informalsystems/ibc-rs/issues/1198 -[#1200]: https://github.com/informalsystems/ibc-rs/issues/1200 -[#1215]: https://github.com/informalsystems/ibc-rs/issues/1215 -[#1229]: https://github.com/informalsystems/ibc-rs/issues/1229 - - -## v0.6.0 -*July 12th, 2021* - - -Many thanks to Fraccaroli Gianmarco (@Fraccaman) for helping us improve the -reliability of Hermes ([#697]). - -This release includes two major features to Hermes: (1) support for reloading -the chains from the configuration file at runtime, and (2) a filtering mechanism -to restrict Hermes activity based on predefined parameters (e.g., packet relaying -on certain ports and channels exclusively, and ignoring activity for clients -that have non-standard trust threshold). - -In addition to these two, we have also added a health checkup mechanism, plus new -`config validate` and `query channel ends` CLIs. - -### Upgrading from 0.5.0 to 0.6.0 - -When upgrading from Hermes v0.5.0 to v0.6.0, the most important -point to watch out for is the configuration file. -The Hermes config.toml configuration file has went through a few revisions, -with the changes described below. - -#### Added inline documentation for all options. - -Please have a look around the [config.toml](./config.toml) directly. - -#### Added a packet filtering mechanism based on channel/port identifiers - -This feature will restrict the channels on which Hermes relays packets. -There are two new options in the configuration file: - -1. A global `filter` parameter to enable or disable filtering globally. -2. A per-chain `.filters` option that expects a `policy` (either `allow` or - `deny`) plus a list of channel and - port identifiers. If policy is `allow`, then packet relaying will be restricted to this - list for the corresponding chain. If the policy is `deny`, then any packets - from this list will be ignored. - -#### Added filtering based on client state - -The global `filter` option additionally enables filtering of all activities -based on client state trust threshold. If enabled, Hermes will ignore all -activity for clients that have a trust threshold different than `1/3`. - -#### Added a packet clearing configuration option - -This will enable the parametrization of the frequency -at which Hermes will clear pending packets. This is a global option, called -`clear_packets_interval`, which applies to all chains in the configuration. - - -The full list of changes is described below. - -### FEATURES - -- [ibc-relayer] - - The chains configuration can be reloaded by sending the Hermes process a `SIGHUP` signal ([#1117]) - - Added support for filtering based on client state trust threshold ([#1165]) - -- [ibc-relayer-cli] - - Added `config validate` CLI to Hermes ([#600]) - - Added filtering capability to deny or allow for specific channels ([#1140], [#1141], [#69]) - - Added basic channel filter ([#1140]) - - Added `query channel ends` CLI command ([#1062]) - - Added a health checkup mechanism for Hermes ([#697, #1057]) - -### IMPROVEMENTS - -- Update to `tendermint-rs` v0.20.0 ([#1125]) -- Add inline documentation to config.toml ([#1127]) - -- [ibc-relayer] - - Hermes will now clear pending packets at a configurable interval ([#1124]) - -### BUG FIXES - -- [ibc-relayer] - - Fix for schedule refreshing bug ([#1143]) - - -[#69]: https://github.com/informalsystems/ibc-rs/issues/69 -[#600]: https://github.com/informalsystems/ibc-rs/issues/600 -[#697]: https://github.com/informalsystems/ibc-rs/issues/697 -[#1062]: https://github.com/informalsystems/ibc-rs/issues/1062 -[#1117]: https://github.com/informalsystems/ibc-rs/issues/1117 -[#1057]: https://github.com/informalsystems/ibc-rs/issues/1057 -[#1125]: https://github.com/informalsystems/ibc-rs/issues/1125 -[#1124]: https://github.com/informalsystems/ibc-rs/issues/1124 -[#1127]: https://github.com/informalsystems/ibc-rs/issues/1127 -[#1140]: https://github.com/informalsystems/ibc-rs/issues/1140 -[#1141]: https://github.com/informalsystems/ibc-rs/issues/1141 -[#1143]: https://github.com/informalsystems/ibc-rs/issues/1143 -[#1165]: https://github.com/informalsystems/ibc-rs/issues/1165 - - -## v0.5.0 -*June 22nd, 2021* - -This release brings a few features, and several improvements and bug fixes to the Hermes -relayer, notably the capability for Hermes to complete IBC connection handshakes when -it detects that one has been initialized, as well as the ability to detect chain -impersonation attacks and to dynamically estimate the gas needed to submit -a transaction. - -Moreover, the overall reliability and availability of the relayer has also been improved -substantially by switching over to `tx_broadcast_sync` for submitting transactions. - -### FEATURES - -- [ibc-relayer-cli] - - Add `--hd-path` option to `keys restore` and `keys add` commands to specify - derivation path when importing keys ([#1049]) - -- [ibc-relayer] - - Event-based handshake completion for IBC connections ([#821]) - - Enable TLS support for gRPC client ([#877]) - -### IMPROVEMENTS - -- [ibc-relayer-cli] - - Minor log output improvements: color enabled, reduced redundant information ([#1100]) - -- [ibc-relayer] - - Update the on-chain IBC client with supporting headers when light client verification - performs bisection when verifying a header for a client update or a misbehaviour detection ([#673]) - - Add mitigation for chain impersonation attacks ([#1038]) - - Determine gas fee dynamically per transaction ([#930]) - - Submit transactions with `broadcast_tx_sync` and keep track of account sequences ([#986]) - -### BUG FIXES - -- [gaiad-manager] - - Removed the testnet command as not all networks support it ([#1050]) - - Update for compatibility with Hermes's new `--hd-path` option - -- [ibc-relayer] - - Fix bug where channels were left partially open after `channel create` ([#1064]) - - Prevent account sequence mismatch errors in many cases ([#919], [#978]) - - Prevent timeouts when submitting transactins ([#977]) - -### BREAKING CHANGES - -- [ibc-relayer-cli] - - Removed `--coin-type` option from `keys restore` command. Use `--hd-path` instead ([#1049]) - -[#673]: https://github.com/informalsystems/ibc-rs/issues/673 -[#821]: https://github.com/informalsystems/ibc-rs/issues/821 -[#877]: https://github.com/informalsystems/ibc-rs/issues/877 -[#919]: https://github.com/informalsystems/ibc-rs/issues/919 -[#930]: https://github.com/informalsystems/ibc-rs/issues/930 -[#977]: https://github.com/informalsystems/ibc-rs/issues/977 -[#978]: https://github.com/informalsystems/ibc-rs/issues/978 -[#986]: https://github.com/informalsystems/ibc-rs/issues/986 -[#1038]: https://github.com/informalsystems/ibc-rs/issues/1038 -[#1049]: https://github.com/informalsystems/ibc-rs/issues/1049 -[#1050]: https://github.com/informalsystems/ibc-rs/issues/1050 -[#1064]: https://github.com/informalsystems/ibc-rs/issues/1064 -[#1100]: https://github.com/informalsystems/ibc-rs/issues/1100 - -## v0.4.0 -*June 3rd, 2021* - -- This release of Hermes features an internal [telemetry service][telemetry] - which can export metrics about the relayer to Prometheus. -- A new [relaying strategy][strategy] is now available, which enables Hermes to - complete channel handshakes in an event-based fashion. -- Hermes now checks if another relayer may have already processed a packet event, - and will not attempt to process it itself, which improves performance. -- The startup time of the relayer has been substantially improved. -- The `start-multi` command has been promoted to `start`, which means - that the worker-based relayer is not experimental anymore. -- A regression where Hermes would not recover after a node went down and up again was fixed. - -[telemetry]: https://hermes.informal.systems/telemetry.html -[strategy]: http://hermes.informal.systems/config.html?highlight=strategy#global - -> Special thanks to Colin Axnér (@colin-axner) and Jongwhan Lee (@leejw51crypto) -> for raising multiple issues that helped us improve the reliability of Hermes. - -### FEATURES - -- [ibc-relayer] - - Add telemetry and Prometheus endpoint ([#868], [#1032]) - - Add support for event based channel relaying ([#822]) - - Graceful handling of packet events in the presence of multiple relayers ([#983]) - -### IMPROVEMENTS - -- [ibc] - - Started `unwrap` cleanup ([#871]) - -- [ibc-relayer-cli] - - Include chain-id in `query clients` command, and sort output by client counter ([#992]) - - Improve config loading message ([#996]) - - Improve Hermes worker spawn time for `start` command ([#998]) - - Better Hermes help message when command is unrecognized ([#1003]) - -### BUG FIXES - -- [ibc-relayer] - - Fix client worker initialization error ([#972]) - - Fix `hermes start` panic when all chains are unreachable ([#972]) - - Ensure expired or frozen client worker logs message and terminates ([#1022]) - - Fix regression where Hermes would not recover after a node went down and up again ([#1026]) - -- [gaiad-manager] - - Import hermes keys properly even if wallet HD derivation path is set ([#975]) - - Apply default values to missing configuration parameters ([#993]) - - `gm hermes config` now creates hermes 0.4.0 compatible configuration ([#1039]) - -### BREAKING CHANGES - -- [ibc-relayer-cli] - - Promote `start-multi` command to `start` ([#911]) - -[#822]: https://github.com/informalsystems/ibc-rs/issues/822 -[#868]: https://github.com/informalsystems/ibc-rs/issues/868 -[#871]: https://github.com/informalsystems/ibc-rs/issues/871 -[#911]: https://github.com/informalsystems/ibc-rs/issues/911 -[#972]: https://github.com/informalsystems/ibc-rs/issues/972 -[#975]: https://github.com/informalsystems/ibc-rs/issues/975 -[#983]: https://github.com/informalsystems/ibc-rs/issues/983 -[#992]: https://github.com/informalsystems/ibc-rs/issues/992 -[#996]: https://github.com/informalsystems/ibc-rs/issues/996 -[#993]: https://github.com/informalsystems/ibc-rs/issues/993 -[#998]: https://github.com/informalsystems/ibc-rs/issues/998 -[#1003]: https://github.com/informalsystems/ibc-rs/issues/1003 -[#1022]: https://github.com/informalsystems/ibc-rs/issues/1022 -[#1026]: https://github.com/informalsystems/ibc-rs/issues/1026 -[#1032]: https://github.com/informalsystems/ibc-rs/issues/1032 -[gaiad-manager]: https://github.com/informalsystems/ibc-rs/blob/master/scripts/gm/README.md -[#1039]: https://github.com/informalsystems/ibc-rs/issues/1039 - -## v0.3.2 -*May 21st, 2021* - -This is minor release which brings substantial performance improvements -to the relayer (relaying 1000 packets now takes 2-5min instead of 1h+), -better UX for the `ft-transfer` command, and automatic deployment of -Docker images to Docker Hub. - -### FEATURES - -- [ibc-relayer-cli] - - Add a `--key` option to the tx raw ft-transfer command to override the account used for sending messages ([#963]) - -- [ibc-relayer] - - Add support for multiple keys to the keyring ([#963]) - -- [release] - - Released the official [Hermes image][hermes-docker] on Docker Hub ([#894]) - - Automatically deploy Docker Hub image during release ([#967]) - -### IMPROVEMENTS - -- [ibc-relayer] - - Batch together all events from all transactions included in a block ([#957]) - -### BUG FIXES - -- [ibc-relayer-cli] - - Prevent sending `ft-transfer` MsgTransfer on a non-Open channel ([#960]) - -### BREAKING CHANGES - -> Nothing - -[#868]: https://github.com/informalsystems/ibc-rs/issues/868 -[#894]: https://github.com/informalsystems/ibc-rs/pull/894 -[#957]: https://github.com/informalsystems/ibc-rs/issues/957 -[#960]: https://github.com/informalsystems/ibc-rs/issues/960 -[#963]: https://github.com/informalsystems/ibc-rs/issues/963 -[#967]: https://github.com/informalsystems/ibc-rs/issues/967 - -[hermes-docker]: https://hub.docker.com/r/informalsystems/hermes - -## v0.3.1 -*May 14h, 2021* - -This release improves the UX of a couple commands, fixes a bug related -to delay periods, and adds support for packet timeouts based on timestamps, -as well as support Protobuf-encoded keys. - -### FEATURES - -- [scripts] - - Created the Gaiad Manager `gm` CLI tool for managing gaiad instances on the local machine ([#902]) - -- [ibc-relayer] - - Add support for packet timeout based on timeout timestamp ([#937]) - - Added support for Protobuf-based Keyring ([#925]) - -### IMPROVEMENTS - -- [ibc-relayer-cli] - - Improve UX when querying non-existing connections and channels ([#875], [#920]) - - More details in error messages to increase debuggability ([#921], [#934]) - - Disallow creating a client with same source and destination chains ([#932]) - - Make packet worker more resilient to nodes being unreachable for a short amount of time ([#943]) - -### BUG FIXES - -- [ibc] - - Process raw `delay_period` field as nanoseconds instead of seconds. ([#927]) - -### BREAKING CHANGES - -> Nothing - - -[#875]: https://github.com/informalsystems/ibc-rs/issues/875 -[#920]: https://github.com/informalsystems/ibc-rs/issues/920 -[#902]: https://github.com/informalsystems/ibc-rs/issues/902 -[#921]: https://github.com/informalsystems/ibc-rs/issues/921 -[#925]: https://github.com/informalsystems/ibc-rs/issues/925 -[#927]: https://github.com/informalsystems/ibc-rs/issues/927 -[#932]: https://github.com/informalsystems/ibc-rs/issues/932 -[#934]: https://github.com/informalsystems/ibc-rs/issues/934 -[#937]: https://github.com/informalsystems/ibc-rs/issues/937 -[#943]: https://github.com/informalsystems/ibc-rs/issues/943 - - -## v0.3.0 -*May 7h, 2021* - -Special thanks to Jongwhan Lee (@leejw51crypto) for his contributions ([#878]). - -This release mostly focuses on improving the UX and the experimental multi-paths relayer (`start-multi` command), -which has been made more resilient against nodes going down, and is now able to clear pending packets -and periodically refresh IBC clients. The relayer now also supports [ICS 027 (Interchain Accounts)][ics27]. - -[ics27]: https://github.com/cosmos/ibc/blob/master/spec/app/ics-027-interchain-accounts/README.md - -### FEATURES - -- [ibc-relayer] - - Support for ICS27 ([#794]) - -- [ibc-relayer-cli] - - Added packet clearing and client refresh capabilities for the `start-multi` command ([#784], [#786]) - -### IMPROVEMENTS - -- [ibc] - - Reinstated `ics23` dependency ([#854]) - - Use proper Timestamp type to track time ([#758]) - -- [ibc-relayer] - - Change the default for client creation to allow governance recovery in case of expiration or misbehaviour ([#785]) - - Use a single supervisor in `start-multi` to subscribe to all configured chains ([#862]) - - The `start-multi` command is now more resilient to a node not being up or going down, and will attempt to reconnect ([#871]) - -### BUG FIXES - -- [ibc] - - Fix parsing in `chain_version` when chain identifier has multiple dashes ([#878]) - -- [ibc-relayer] - - Fix pagination in gRPC query for clients ([#811]) - - Fix relayer crash when hermes starts in the same time as packets are being sent ([#851]) - - Fix missing port information in `hermes query channels` ([#840]) - - Fix crash during initialization of event monitor when node is down ([#863]) - - Spawn a single Tokio runtime for the whole supervisor instead of one per chain ([#909]) - -- [ibc-relayer-cli] - - Fix for `ft-transfer` mismatching arguments ([#869]) - - Fix channel destination chain mismatch on unreceived-packets or unreceived-acks ([#873]) - -### BREAKING CHANGES - -- [ibc-relayer] - - `hermes -j query channels` command now returns `result` array with the format - `[{"channel_id":"channel-0","port_id":"transfer"}, ...]` instead of `["channel-0", ...]` ([#840]) - - -[#758]: https://github.com/informalsystems/ibc-rs/issues/758 -[#784]: https://github.com/informalsystems/ibc-rs/issues/784 -[#785]: https://github.com/informalsystems/ibc-rs/issues/785 -[#786]: https://github.com/informalsystems/ibc-rs/issues/786 -[#794]: https://github.com/informalsystems/ibc-rs/issues/794 -[#811]: https://github.com/informalsystems/ibc-rs/issues/811 -[#840]: https://github.com/informalsystems/ibc-rs/issues/840 -[#851]: https://github.com/informalsystems/ibc-rs/issues/851 -[#854]: https://github.com/informalsystems/ibc-rs/issues/854 -[#862]: https://github.com/informalsystems/ibc-rs/issues/862 -[#863]: https://github.com/informalsystems/ibc-rs/issues/863 -[#869]: https://github.com/informalsystems/ibc-rs/issues/869 -[#871]: https://github.com/informalsystems/ibc-rs/issues/871 -[#873]: https://github.com/informalsystems/ibc-rs/issues/873 -[#878]: https://github.com/informalsystems/ibc-rs/issues/878 -[#909]: https://github.com/informalsystems/ibc-rs/issues/909 - -## v0.2.0 -*April 14th, 2021* - -This release includes initial support for relaying over multiple paths from a single `hermes` instance. -Adds support for relayer restart, where pending packets are cleared. -Includes support for ordered channels, packet delay, misbehaviour detection and evidence submission, client upgrade after counterparty chain upgrades. - -This release brings improvements to the relayer UX by providing new and updated commands for keys, client, connection and channel management. -In addition, it simplifies the configuration of and integration with the light client. - -This release also finalizes the initial implementation of all the ICS 004 handlers. - -### FEATURES - -- Update to `tendermint-rs` v0.19.0 ([#798]) - -- [ibc] - - Added handler(s) for sending packets ([#695]), recv. and ack. packets ([#736]), and timeouts ([#362]) - -- [ibc-relayer] - - Support for relayer restart ([#561]) - - Add support for ordered channels ([#599]) - - Misbehaviour detection and evidence submission ([#632]) - - Use a stateless light client without a runtime ([#673]) - -- [ibc-relayer-cli] - - Added `create connection` and `create channel` CLIs ([#630], [#715]) - - Proposed ADR 006 to describe Hermes v0.2.0 use-cases ([#637]) - - Added `client-upgrade` CLI ([#357]) - - Added delay feature for packet relaying ([#640]) - - Update gaia to version 4.2.0 for e2e tests on CI ([#809]) - - Add `start-multi` command to relay on all paths defined in the configuration ([#748]) - - Add option to specify which events to listen for in `listen` command ([#550]) - - Add option to customise receiver address for `ft-transfer` command ([#806]) - - Add `keys restore` command to import a signing key from its mnemonic ([#813]) - -### IMPROVEMENTS - -- [ibc] - - Follow Rust guidelines naming conventions ([#689]) - - Per client structure modules ([#740]) - - MBT: use modelator crate ([#761]) - -- [ibc-relayer] - - Consistent identifier handling across ICS 02, 03 and 04 ([#622]) - -- [ibc-relayer-cli] - - Clarified success path for updating a client that is already up-to-date ([#734]) - - Added `create` and `update` wrappers for client raw commands ([#772]) - - Output by default is human-readable, and JSON is optional ([#805]) - -### BUG FIXES - -- [ibc] - - Fix overflow bug in ICS03 client consensus height verification method ([#685]) - - Allow a conn open ack to succeed in the happy case ([#699]) - -- [ibc-relayer] - - Replaced `rust-crypto` & `bitcoin-wallet` deprecated dependencies ([#352]) - - Fix for hard-coded account number ([#752]) - - Fix for chains that don't have `cosmos` account prefix ([#416]) - - Fix for building the `trusted_validator_set` for the header used in client updates ([#770]) - - Don't send `MsgAcknowledgment` if channel is closed ([#675]) - - Fix a bug where the keys addresses had their account prefix overriden by the prefix in the configuration ([#751]) - -- [ibc-relayer-cli] - - Hermes guide: improved installation guideline ([#672]) - - Make fee denom and amount configurable ([#754]) - -- [ibc-proto] - - Fix for proto files re-compilation bug ([#801]) - -### BREAKING CHANGES - -- [ibc] - - `MsgConnectionOpenAck.counterparty_connection_id` is now a `ConnectionId` instead of an `Option`([#700]) - -- [ibc-relayer] - - Remove the light client configuration from the global configuration ([#793]) - -- [ibc-relayer-cli] - - Remove the light add and light rm commands ([#793]) - - -[#352]: https://github.com/informalsystems/ibc-rs/issues/352 -[#362]: https://github.com/informalsystems/ibc-rs/issues/362 -[#357]: https://github.com/informalsystems/ibc-rs/issues/357 -[#416]: https://github.com/informalsystems/ibc-rs/issues/416 -[#561]: https://github.com/informalsystems/ibc-rs/issues/561 -[#550]: https://github.com/informalsystems/ibc-rs/issues/550 -[#599]: https://github.com/informalsystems/ibc-rs/issues/599 -[#630]: https://github.com/informalsystems/ibc-rs/issues/630 -[#632]: https://github.com/informalsystems/ibc-rs/issues/632 -[#640]: https://github.com/informalsystems/ibc-rs/issues/640 -[#672]: https://github.com/informalsystems/ibc-rs/issues/672 -[#673]: https://github.com/informalsystems/ibc-rs/issues/673 -[#675]: https://github.com/informalsystems/ibc-rs/issues/675 -[#685]: https://github.com/informalsystems/ibc-rs/issues/685 -[#689]: https://github.com/informalsystems/ibc-rs/issues/689 -[#695]: https://github.com/informalsystems/ibc-rs/issues/695 -[#699]: https://github.com/informalsystems/ibc-rs/issues/699 -[#700]: https://github.com/informalsystems/ibc-rs/pull/700 -[#715]: https://github.com/informalsystems/ibc-rs/issues/715 -[#734]: https://github.com/informalsystems/ibc-rs/issues/734 -[#736]: https://github.com/informalsystems/ibc-rs/issues/736 -[#740]: https://github.com/informalsystems/ibc-rs/issues/740 -[#748]: https://github.com/informalsystems/ibc-rs/issues/748 -[#751]: https://github.com/informalsystems/ibc-rs/issues/751 -[#752]: https://github.com/informalsystems/ibc-rs/issues/752 -[#754]: https://github.com/informalsystems/ibc-rs/issues/754 -[#761]: https://github.com/informalsystems/ibc-rs/issues/761 -[#772]: https://github.com/informalsystems/ibc-rs/issues/772 -[#770]: https://github.com/informalsystems/ibc-rs/issues/770 -[#793]: https://github.com/informalsystems/ibc-rs/pull/793 -[#798]: https://github.com/informalsystems/ibc-rs/issues/798 -[#801]: https://github.com/informalsystems/ibc-rs/issues/801 -[#805]: https://github.com/informalsystems/ibc-rs/issues/805 -[#806]: https://github.com/informalsystems/ibc-rs/issues/806 -[#809]: https://github.com/informalsystems/ibc-rs/issues/809 - - -## v0.1.1 -*February 17, 2021* - -This release brings a quick fix for a problem with a dependency of crate -`ibc-relayer`, which causes build & installation issues. Many thanks to -@Fraccaman for bringing this problem to our attention! ([#672]) - - -Additionally, this release also introduces initial implementation for most of -ICS 004 handlers, and several bug fixes and improvements, e.g., refactored -some CLI code, refactored the Height type in the IBC Events, and a bug fix -involving packet acks in a 3-chain setup. More details below. - -### FEATURES -- [ibc-relayer] - - Listen to channel close initialization event and perform the close handshake ([#560]) - - Updated to tendermint-rs `v0.18.1` ([#682], [#671]) - -### IMPROVEMENTS - -- [ibc] - - Change event height to ICS height ([#549]) - -- [ibc-relayer-cli] - - Cleanup CLI code ([#572]) - -### BUG FIXES - -- [ibc] - - Fix panic in conn open try when no connection id is provided ([#626]) - - Disable MBT tests if the "mocks" feature is not enabled ([#643]) - -- [ibc-relayer] - - Quick fix for `funty` breaking change bug ([#665]) - -- [ibc-relayer-cli] - - Fix wrong acks sent with `tx raw packet-ack` in a 3-chain setup ([#614]) - -### BREAKING CHANGES - -- [ibc] - - Implementation of the `ChanOpenAck`, `ChanOpenConfirm`, `ChanCloseInit`, and `ChanCloseConfirm` handlers ([#316]) - - Remove dependency on `tendermint-rpc` ([#624]) - -- [ibc-relayer-cli] - - Remove the `proof` option from CLI ([#572]) - -[#316]: https://github.com/informalsystems/ibc-rs/issues/316 -[#549]: https://github.com/informalsystems/ibc-rs/issues/549 -[#560]: https://github.com/informalsystems/ibc-rs/issues/560 -[#572]: https://github.com/informalsystems/ibc-rs/issues/572 -[#614]: https://github.com/informalsystems/ibc-rs/issues/614 -[#622]: https://github.com/informalsystems/ibc-rs/issues/622 -[#624]: https://github.com/informalsystems/ibc-rs/issues/624 -[#626]: https://github.com/informalsystems/ibc-rs/issues/626 -[#637]: https://github.com/informalsystems/ibc-rs/issues/637 -[#643]: https://github.com/informalsystems/ibc-rs/issues/643 -[#665]: https://github.com/informalsystems/ibc-rs/issues/665 -[#671]: https://github.com/informalsystems/ibc-rs/pull/671 -[#682]: https://github.com/informalsystems/ibc-rs/issues/682 - -[ibc]: https://github.com/informalsystems/ibc-rs/tree/master/modules -[ibc-relayer-cli]: https://github.com/informalsystems/ibc-rs/tree/master/relayer-cli - -## v0.1.0 -*February 4, 2021* - -🎉 This release brings the first publication of `ibc-relayer` and -`ibc-relayer-cli` to [crates.io](https://crates.io). - -Noteworthy changes in this release include: - -- The binary in the `ibc-relayer-cli` crate was given the name Hermes. -- We published a comprehensive guide for Hermes at [hermes.informal.systems](https://hermes.informal.systems). -- Major improvements to user experience, in particular at CLI level: JSON output, - configurable log output level, dedicated channel handshake command, as well as - overall improvements to error display and output. - -### FEATURES - -- Continous Integration (CI) end-to-end (e2e) testing with gaia v4 ([#32], [#582], [#602]) -- Add support for streamlining releases ([#507]) - -- [ibc-relayer-cli] - - Implement command to query the channels associated with a connection ([#505]) - - JSON output for queries and txs ([#500]) - - Added 'required' annotation for CLIs queries & txs; better error display ([#555]) - - Implement commands for channel close init and confirm ([#538]) - - Implement command to perform the handshake for a new channel ([#557]) - - Query all clients command ([#552]) - - Query all connections command ([#553]) - - Query all channels command ([#568]) - - Added a relayer binary guide ([#542]) - - Split the dev-env script in `setup_chains` and `init_clients` ([#577]) - -- [ibc-relayer] - - Added retry mechanism, restructured relayer ([#519]) - - Relay `MsgTimeoutOnClose` if counterparty channel state is `State::Closed` - -- [ibc] - - Add `MsgTimeoutOnClose` message type ([#563]) - - Implement `MsgChannelOpenTry` message handler ([#543]) - -### IMPROVEMENTS - -- Update to `tendermint-rs` v0.18.0 ([#517], [#583]) -- Update to `tokio` 1.0, `prost` 0.7 and `tonic` 0.4 ([#527]) - -- [ibc-relayer-cli] - - Replace `ChannelConfig` in `Channel::new` ([#511]) - - Add `packet-send` CLI ([#470]) - - UX improvements for relayer txs ([#536], [#540], [#554]) - - Allow running standalone commands concurrently to the main relayer loop ([#501]) - - Remove the simd-based integration tests ([#593]) - -- [ibc-relayer] - - Performance improvements ([#514], [#537]) - - Fix for mismatching `bitcoin` dep ([#525]) - -- [ibc] - - Clean the `validate_basic` method ([#94]) - - `MsgConnectionOpenAck` testing improvements ([#306]) - -### BUG FIXES: -- [ibc-relayer-cli] - - Help and usage commands show 'hermes' for executable name ([#590]) - -- [ibc] - - Fix for storing `ClientType` upon 'create-client' ([#513]) - -### BREAKING CHANGES: - -- [ibc] - - The `ibc::handler::Event` is removed and handlers now produce `ibc::events::IBCEvent`s ([#535]) - -[#32]: https://github.com/informalsystems/ibc-rs/issues/32 -[#94]: https://github.com/informalsystems/ibc-rs/issues/94 -[#306]: https://github.com/informalsystems/ibc-rs/issues/306 -[#470]: https://github.com/informalsystems/ibc-rs/issues/470 -[#500]: https://github.com/informalsystems/ibc-rs/issues/500 -[#501]: https://github.com/informalsystems/ibc-rs/issues/501 -[#505]: https://github.com/informalsystems/ibc-rs/issues/505 -[#507]: https://github.com/informalsystems/ibc-rs/issues/507 -[#511]: https://github.com/informalsystems/ibc-rs/pull/511 -[#513]: https://github.com/informalsystems/ibc-rs/issues/513 -[#514]: https://github.com/informalsystems/ibc-rs/issues/514 -[#517]: https://github.com/informalsystems/ibc-rs/issues/517 -[#519]: https://github.com/informalsystems/ibc-rs/issues/519 -[#525]: https://github.com/informalsystems/ibc-rs/issues/525 -[#527]: https://github.com/informalsystems/ibc-rs/issues/527 -[#535]: https://github.com/informalsystems/ibc-rs/issues/535 -[#536]: https://github.com/informalsystems/ibc-rs/issues/536 -[#537]: https://github.com/informalsystems/ibc-rs/issues/537 -[#538]: https://github.com/informalsystems/ibc-rs/issues/538 -[#540]: https://github.com/informalsystems/ibc-rs/issues/540 -[#542]: https://github.com/informalsystems/ibc-rs/issues/542 -[#543]: https://github.com/informalsystems/ibc-rs/issues/543 -[#552]: https://github.com/informalsystems/ibc-rs/issues/553 -[#553]: https://github.com/informalsystems/ibc-rs/issues/553 -[#554]: https://github.com/informalsystems/ibc-rs/issues/554 -[#555]: https://github.com/informalsystems/ibc-rs/issues/555 -[#557]: https://github.com/informalsystems/ibc-rs/issues/557 -[#563]: https://github.com/informalsystems/ibc-rs/issues/563 -[#568]: https://github.com/informalsystems/ibc-rs/issues/568 -[#577]: https://github.com/informalsystems/ibc-rs/issues/577 -[#582]: https://github.com/informalsystems/ibc-rs/issues/582 -[#583]: https://github.com/informalsystems/ibc-rs/issues/583 -[#590]: https://github.com/informalsystems/ibc-rs/issues/590 -[#593]: https://github.com/informalsystems/ibc-rs/issues/593 -[#602]: https://github.com/informalsystems/ibc-rs/issues/602 - -## v0.0.6 -*December 23, 2020* - -This release focuses on upgrading the relayer and ibc modules to the latest interfaces from the ecosystem: -tendermint-rs `v0.17`, which brings the protobuf changes from tendermint `v0.34.0`, plus alignment with -the latest cosmos proto versions from `v0.40.0-rc5` (sometimes called 'stargate-5'). - -### FEATURES -- Update to tendermint-rs version `0.17` ([#451]) -- Update to cosmos-sdk IBC proto version `v0.40.0-rc5` ([#451]) - -- [ibc-relayer] - -- [ibc-relayer-cli] - - Packet CLIs for recv_packet ([#443]) - - Packet CLIs for acknowledging packets ([#468]) - -### IMPROVEMENTS -- [ibc-relayer] - - Mock chain (implementing IBC handlers) and integration against CLI ([#158]) - - Relayer tests for client update (ping pong) against MockChain ([#381]) - - Relayer refactor to improve testing and add semantic dependencies ([#447]) - -[#158]: https://github.com/informalsystems/ibc-rs/issues/158 -[#379]: https://github.com/informalsystems/ibc-rs/issues/379 -[#381]: https://github.com/informalsystems/ibc-rs/issues/381 -[#443]: https://github.com/informalsystems/ibc-rs/issues/443 -[#447]: https://github.com/informalsystems/ibc-rs/issues/447 -[#451]: https://github.com/informalsystems/ibc-rs/issues/451 -[#468]: https://github.com/informalsystems/ibc-rs/issues/468 - - -## v0.0.5 -*December 2, 2020* - -This release focuses on implementing relayer and relayer-cli functionality towards a full v0 implementation. -We now have the full-stack implementation for supporting client creation & updates, as well as connection- and channel handshakes. -We also consolidated our TLA+ specs into an "IBC Core TLA+ specification," and added ICS 020 spec. - -Special thanks to external contributors for this release: @CharlyCst ([#347], [#419]). - -- [ibc-relayer-cli] - - Add `--all` option to `light rm` command to remove all peers for a given chain ([#431]) - -[#431]: https://github.com/informalsystems/ibc-rs/issues/431 - -### FEATURES - -- Update to tendermint-rs version `0.17-RC3` ([#403]) -- [changelog] Added "unreleased" section in `CHANGELOG.MD` to help streamline releases ([#274]) -- [ibc] - - Implement flexible connection id selection ([#332]) - - ICS 4 Domain Types for channel handshakes and packets ([#315], [#95]) - - Introduce LightBlock support for MockContext ([#389]) -- [ibc-relayer] - - Retrieve account sequence information from a chain using a GRPC client (#337) - - Implementation of chain runtime for v0 ([#330]) - - Integrate relayer spike into ibc-relayer crate ([#335]) - - Implement `query_header_at_height` via plain RPC queries (no light client verification) ([#336]) - - Implement the relayer logic for connection handshake messages ([#358], [#359], [#360]) - - Implement the relayer logic for channel handshake messages ([#371], [#372], [#373], [#374]) -- [ibc-relayer-cli] - - Merge light clients config in relayer config and add commands to add/remove light clients ([#348]) - - CLI for client update message ([#277]) - - Implement the relayer CLI for connection handshake messages ([#358], [#359], [#360]) - - Implement the relayer CLI for channel handshake messages ([#371], [#372], [#373], [#374]) - - Added basic client, connection, and channel lifecyle in relayer v0 ([#376], [#377], [#378]) - - Implement commands to add and list keys for a chain ([#363]) - - Allow overriding of peer_id, height and hash in light add command ([#428]) -- [proto-compiler] - - Refactor and allow specifying a commit at which the Cosmos SDK should be checked out ([#366]) - - Add a `--tag` option to the `clone-sdk` command to check out a tag instead of a commit ([#369]) - - Fix `--out` command line parameter (instead of `--path`) ([#419]) -- [ibc/relayer-spec] - - ICS 020 spec in TLA+ ([#386]) - - Prepare IBC Core TLA+ specs ([#404]) - -### IMPROVEMENTS - -- [ibc-relayer] - - Pin chain runtime against Tokio 0.2 by downgrading for 0.3 to avoid dependency hell ([#415], follow up to [#402]) -- [ibc-relayer-cli] - - Split tasks spawned by CLI commands into their own modules ([#331]) - - V0 command implementation ([#346]) -- [ibc] - - Split `msgs.rs` of ICS002 in separate modules ([#367]) - - Fixed inconsistent versioning for ICS003 and ICS004 ([#97]) - - Fixed `get_sign_bytes` method for messages ([#98]) - - Homogenize ConnectionReader trait so that all functions return owned objects ([#347]) - - Align with tendermint-rs in the domain type definition of `block::Id` ([#338]) - - -[#95]: https://github.com/informalsystems/ibc-rs/issues/95 -[#97]: https://github.com/informalsystems/ibc-rs/issues/97 -[#98]: https://github.com/informalsystems/ibc-rs/issues/98 -[#274]: https://github.com/informalsystems/ibc-rs/issues/274 -[#277]: https://github.com/informalsystems/ibc-rs/issues/277 -[#315]: https://github.com/informalsystems/ibc-rs/issues/315 -[#330]: https://github.com/informalsystems/ibc-rs/issues/330 -[#332]: https://github.com/informalsystems/ibc-rs/issues/332 -[#335]: https://github.com/informalsystems/ibc-rs/pull/335 -[#336]: https://github.com/informalsystems/ibc-rs/issues/336 -[#337]: https://github.com/informalsystems/ibc-rs/issues/337 -[#338]: https://github.com/informalsystems/ibc-rs/issues/338 -[#346]: https://github.com/informalsystems/ibc-rs/issues/346 -[#347]: https://github.com/informalsystems/ibc-rs/issues/347 -[#348]: https://github.com/informalsystems/ibc-rs/pull/348 -[#358]: https://github.com/informalsystems/ibc-rs/issues/358 -[#359]: https://github.com/informalsystems/ibc-rs/issues/359 -[#360]: https://github.com/informalsystems/ibc-rs/issues/360 -[#363]: https://github.com/informalsystems/ibc-rs/issues/363 -[#366]: https://github.com/informalsystems/ibc-rs/issues/366 -[#367]: https://github.com/informalsystems/ibc-rs/issues/367 -[#368]: https://github.com/informalsystems/ibc-rs/issues/368 -[#369]: https://github.com/informalsystems/ibc-rs/pull/369 -[#371]: https://github.com/informalsystems/ibc-rs/issues/371 -[#372]: https://github.com/informalsystems/ibc-rs/issues/372 -[#373]: https://github.com/informalsystems/ibc-rs/issues/373 -[#374]: https://github.com/informalsystems/ibc-rs/issues/374 -[#376]: https://github.com/informalsystems/ibc-rs/issues/376 -[#377]: https://github.com/informalsystems/ibc-rs/issues/377 -[#378]: https://github.com/informalsystems/ibc-rs/issues/378 -[#386]: https://github.com/informalsystems/ibc-rs/issues/386 -[#389]: https://github.com/informalsystems/ibc-rs/issues/389 -[#402]: https://github.com/informalsystems/ibc-rs/issues/402 -[#403]: https://github.com/informalsystems/ibc-rs/issues/403 -[#404]: https://github.com/informalsystems/ibc-rs/issues/404 -[#419]: https://github.com/informalsystems/ibc-rs/issues/419 -[#415]: https://github.com/informalsystems/ibc-rs/issues/415 -[#428]: https://github.com/informalsystems/ibc-rs/issues/428 -[changelog]: https://github.com/informalsystems/ibc-rs/tree/master/CHANGELOG.md -[proto-compiler]: https://github.com/informalsystems/ibc-rs/tree/master/proto-compiler - -## v0.0.4 -*October 19, 2020* - -This release focuses on alignment with the Cosmos ecosystem: adaptations to Tendermint-rs 0.16 and subsequently to 0.17 (`0.17.0-rc1`), and numerous protobuf updates following latest stargate releases. - -Additional highlights: -- Adding DomainTypes and (de)serialization capability to ICS02 and ICS03 messages and structures. -- Improvements of the IBC message processor framework (handlers, contexts and mocks). -- Added initial implementations for the ICS26 (routing module) and ICS18 (basic relayer algorithms module) for use in testing. -- Also added support for packet handling in the relayer algorithm specifications. - -### BREAKING CHANGES: -- [ibc-relayer] & [ibc] Alignment with ecosystem updates: - - Compatibility with the latest protobuf (Gaia stargate-3 and stargate-4) ([#191], [#272], [#273], [#278]) - - Adaptations to tendermint 0.17 ([#286], [#293], [#300], [#302], [#308]) -- [ibc-relayer] UX improvement: Remove proof option from client connections command ([#205]) - -### FEATURES: -- [ibc/ics03] ICS03 Ack and Confirm message processors ([#223]) -- [ibc-relayer-cli] - - Relayer CLIs for client messages ([#207]) - - Relayer CLIs for connection-open-init ([#206]) - - Queries for consensus state and client state ([#149], [#150]) -- [ibc] Routing module minimal implementation for MVP ([#159], [#232]) -- [ibc/relayer-spec] Relayer specification for packet handling ([#229], [#234], [#237]) -- [ibc/relayer-spec] Basic packet handling in TLA+([#124]) -- [ibc] Basic relayer functionality: a test with ClientUpdate ping-pong between two mocked chains ([#276]) - -### IMPROVEMENTS: -- [ibc] Implemented the `DomainType` trait for IBC proto structures ([#245], [#249]). -- [ibc] & [ibc-proto] Several improvements to message processors, among which ([#218]): - - ICS03 connection handshake protocol initial implementation and tests ([#160]) - - Add capability to decode from protobuf Any* type into Tendermint and Mock client states - - Cleanup Any* client wrappers related code - - Migrate handlers to newer protobuf definitions ([#226]) - - Extend client context mock ([#221]) - - Context mock simplifications and cleanup ([#269], [#295], [#296], [#297]) -- [ibc/ics03] Split `msgs.rs` in multiple files, implement `From` for all messages ([#253]) -- [ibc-proto] - - Move ibc-proto source code into ibc-rs ([#142]) and fixed code deduplication ([#282], [#284]) - - Consolidate proto-compiler logic [#241] -- [ibc/relayer-spec] Add support for APALACHE to the Relayer TLA+ spec ([#165]) -- [ibc-relayer] Update to tendermint v.0.16 and integrate with the new light client implementation ([#90], [#243]) - -### BUG FIXES: -- [ibc] Removed "Uninitialized" state from connection ([#217]) -- [ibc-relayer-cli] Fix for client query subcommands ([#231]) -- [disclosure-log] & [spec/connection-handshake] Disclosed bugs in ICS3 version negotiation and proposed a fix ([#209], [#213]) - -[#90]: https://github.com/informalsystems/ibc-rs/issues/90 -[#124]: https://github.com/informalsystems/ibc-rs/issues/124 -[#142]: https://github.com/informalsystems/ibc-rs/issues/142 -[#149]: https://github.com/informalsystems/ibc-rs/issues/149 -[#150]: https://github.com/informalsystems/ibc-rs/issues/150 -[#159]: https://github.com/informalsystems/ibc-rs/issues/159 -[#160]: https://github.com/informalsystems/ibc-rs/issues/160 -[#165]: https://github.com/informalsystems/ibc-rs/issues/165 -[#191]: https://github.com/informalsystems/ibc-rs/issues/191 -[#205]: https://github.com/informalsystems/ibc-rs/issues/205 -[#206]: https://github.com/informalsystems/ibc-rs/issues/206 -[#207]: https://github.com/informalsystems/ibc-rs/issues/207 -[#209]: https://github.com/informalsystems/ibc-rs/issues/209 -[#213]: https://github.com/informalsystems/ibc-rs/issues/213 -[#217]: https://github.com/informalsystems/ibc-rs/issues/217 -[#218]: https://github.com/informalsystems/ibc-rs/issues/218 -[#221]: https://github.com/informalsystems/ibc-rs/issues/221 -[#223]: https://github.com/informalsystems/ibc-rs/issues/223 -[#226]: https://github.com/informalsystems/ibc-rs/issues/226 -[#229]: https://github.com/informalsystems/ibc-rs/issues/229 -[#231]: https://github.com/informalsystems/ibc-rs/issues/231 -[#232]: https://github.com/informalsystems/ibc-rs/issues/232 -[#234]: https://github.com/informalsystems/ibc-rs/issues/234 -[#237]: https://github.com/informalsystems/ibc-rs/issues/237 -[#241]: https://github.com/informalsystems/ibc-rs/issues/241 -[#243]: https://github.com/informalsystems/ibc-rs/issues/243 -[#245]: https://github.com/informalsystems/ibc-rs/issues/245 -[#249]: https://github.com/informalsystems/ibc-rs/issues/249 -[#253]: https://github.com/informalsystems/ibc-rs/issues/253 -[#269]: https://github.com/informalsystems/ibc-rs/issues/269 -[#272]: https://github.com/informalsystems/ibc-rs/issues/272 -[#273]: https://github.com/informalsystems/ibc-rs/issues/273 -[#276]: https://github.com/informalsystems/ibc-rs/issues/276 -[#278]: https://github.com/informalsystems/ibc-rs/issues/278 -[#282]: https://github.com/informalsystems/ibc-rs/issues/282 -[#284]: https://github.com/informalsystems/ibc-rs/issues/284 -[#286]: https://github.com/informalsystems/ibc-rs/issues/286 -[#293]: https://github.com/informalsystems/ibc-rs/issues/293 -[#295]: https://github.com/informalsystems/ibc-rs/issues/295 -[#296]: https://github.com/informalsystems/ibc-rs/issues/296 -[#297]: https://github.com/informalsystems/ibc-rs/issues/297 -[#300]: https://github.com/informalsystems/ibc-rs/issues/300 -[#302]: https://github.com/informalsystems/ibc-rs/issues/302 -[#308]: https://github.com/informalsystems/ibc-rs/issues/308 -[ibc-proto]: https://github.com/informalsystems/ibc-rs/tree/master/proto -[disclosure-log]: https://github.com/informalsystems/ibc-rs/blob/master/docs/disclosure-log.md -[spec/connection-handshake]: https://github.com/informalsystems/ibc-rs/tree/master/docs/spec/connection-handshake -[ibc-relayer]: https://github.com/informalsystems/ibc-rs/tree/master/relayer - -## v0.0.3 -*September 1, 2020* - -This release focuses on the IBC message processor framework and initial -implementations in ICS02 and ICS07. It also introduces an initial specification for the relayer algorithm. - -Other highlights: -- The ibc crate is published as [ibc](https://crates.io/crates/ibc) in crates.io -- ADR-001 and ADR-003 are complete. 🎉 - -### BREAKING CHANGES: -- [ibc] Renamed `modules` crate to `ibc` crate. Version number for the new crate is not reset. ([#198]) -- [ibc/ics02] `ConnectionId`s are now decoded to `Vec` and validated instead of `Vec` ([#185]) -- [ibc/ics03] Removed `Connection` and `ConnectionCounterparty` traits ([#193]) -- [ibc/ics04] Removed `Channel` and `ChannelCounterparty` traits ([#192]) - -### FEATURES: -- [ibc/ics02] partial implementation of message handler ([#119], [#194]) -- [ibc/ics07] partial implementation of message handler ([#119], [#194]) -- [architecture/ADR-003] Proposal for IBC handler (message processor) architecture ([#119], [#194]) -- [ibc/relayer-spec] Detailed technical specification of the relayer algorithm with focus on client update ([#84]) -- [architecture/ADR-001] Documentation for the repository structure ([#1]) -- [architecture/FSM-1] Connection Handshake FSM English description ([#122]) - -### IMPROVEMENTS: -- [contributing] Updated CONTRIBUTING.md. Please read before opening PRs ([#195]) -- [ibc-relayer-cli] Refactor ConnectionId decoding in `query client` ([#185]) - -### BUG FIXES: -- [ibc/ics24] Identifiers limit update according to ICS specs ([#168]) - -[ibc/relayer-spec]: https://github.com/informalsystems/ibc-rs/blob/master/docs/spec/relayer/Relayer.md -[#84]: https://github.com/informalsystems/ibc-rs/issues/84 -[architecture/ADR-001]: https://github.com/informalsystems/ibc-rs/blob/master/docs/architecture/adr-001-repo.md -[#1]: https://github.com/informalsystems/ibc-rs/issues/1 -[contributing]: https://github.com/informalsystems/ibc-rs/blob/master/CONTRIBUTING.md -[#195]: https://github.com/informalsystems/ibc-rs/pull/195 -[ibc]: https://github.com/informalsystems/ibc-rs/tree/master/modules -[#198]: https://github.com/informalsystems/ibc-rs/issues/198 -[ibc/ics02]: https://github.com/informalsystems/ibc-rs/tree/master/modules/src/ics02_client -[#185]: https://github.com/informalsystems/ibc-rs/issues/185 -[ibc/ics03]: https://github.com/informalsystems/ibc-rs/tree/master/modules/src/ics03_connection -[#193]: https://github.com/informalsystems/ibc-rs/issues/193 -[ibc/ics04]: https://github.com/informalsystems/ibc-rs/tree/master/modules/src/ics04_channel -[#192]: https://github.com/informalsystems/ibc-rs/issues/192 -[ibc-relayer-cli]: https://github.com/informalsystems/ibc-rs/tree/master/relayer-cli -[architecture/FSM-1]: https://github.com/informalsystems/ibc-rs/blob/master/docs/architecture/fsm-async-connection.md -[#122]: https://github.com/informalsystems/ibc-rs/issues/122 -[architecture/ADR-003]: https://github.com/informalsystems/ibc-rs/blob/master/docs/architecture/adr-003-handler-implementation.md -[#119]: https://github.com/informalsystems/ibc-rs/issues/119 -[#194]: https://github.com/informalsystems/ibc-rs/issues/194 -[ibc/ics24]: https://github.com/informalsystems/ibc-rs/tree/master/modules/src/ics24_host -[#168]: https://github.com/informalsystems/ibc-rs/issues/168 -[ibc/ics07]: https://github.com/informalsystems/ibc-rs/tree/master/modules/src/ics07_tendermint - -## v0.0.2 - -*August 1, 2020* - -This release is focused on updating the query system from amino to protobuf, -implementing a few queries from the CLI, and establishing an initial testing framework -that will support multiple chain types. - -It does not target a stable release of Cosmos-SDK chains, but is tracking -the latest state of development towards the Cosmos-SDK Stargate release. - -### BREAKING CHANGES: - -- [ibc|ibc-relayer] Refactor queries, paths, and Chain trait to reduce code and use - protobuf instead of Amino. - [\#152](https://github.com/informalsystems/ibc-rs/pull/152), - [\#174](https://github.com/informalsystems/ibc-rs/pull/174), - [\#155](https://github.com/informalsystems/ibc-rs/pull/155) -- [repo] Moved relayer/cli to relayer-cli, relayer/relay to relayer. [\#183](https://github.com/informalsystems/ibc-rs/pull/183) - -### FEATURES: - -- [ibc-relayer] Query connections given client id. [\#169](https://github.com/informalsystems/ibc-rs/pull/169) -- [ibc-relayer] Query connection given connection id. [\#136](https://github.com/informalsystems/ibc-rs/pull/136) -- [ibc-relayer] Query channel given channel id and port [\#163](https://github.com/informalsystems/ibc-rs/pull/163) -- [spec] Channel closing datagrams in TLA+ [\#141](https://github.com/informalsystems/ibc-rs/pull/141) - -### IMPROVEMENTS: - -- [ci] Framework (scripts and Github Actions) for integration testing the relayer queries against - the Cosmos-SDK's `simd` binary with prepopulated IBC state in the genesis - [\#140](https://github.com/informalsystems/ibc-rs/pull/140), - [\#184](https://github.com/informalsystems/ibc-rs/pull/184) -- [ibc-relayer|ibc] Implemented better Raw type handling. [\#156](https://github.com/informalsystems/ibc-rs/pull/156) -- [repo] Add rust-toolchain file. [\#154](https://github.com/informalsystems/ibc-rs/pull/154) - -### BUG FIXES: - -- [ibc] Fixed the identifiers limits according to updated ics spec. [\#189](https://github.com/informalsystems/ibc-rs/pull/189) -- [ibc/relayer] Remove some warnings triggered during compilation due to dependency specification. [\#132](https://github.com/informalsystems/ibc-rs/pull/132) -- [ibc] Fix nightly runs. [\#161](https://github.com/informalsystems/ibc-rs/pull/161) -- [repo] Fix for incomplete licence terms. [\#153](https://github.com/informalsystems/ibc-rs/pull/153) - -## 0.0.1 - -*July 1st, 2020* - -This is the initial prototype release of an IBC relayer and TLA+ specifications. -There are no compatibility guarantees until v0.1.0. - -Includes: - -- Configuration file definition and validation -- Client state, consensus state, connection, channel queries. - - Note: deserialization is unimplemented as it has dependency on migration to protobuf for ABCI queries -- Per chain light clients threads are created and headers are periodically retrieved and verified. -- Per chain IBC event monitor threads are spawned and main event handler that receives them. - - Note: the event handler just displays the events. -- IBC Modules partial implementation for datastructures, messages and queries. -- Some English and TLA+ specifications for Connection & Channel Handshake as well as naive relayer algorithm. diff --git a/.changelog/unreleased/.gitkeep b/.changelog/unreleased/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/.changelog/unreleased/bug-fixes/1247-add-missing-protobuf-impl.md b/.changelog/unreleased/bug-fixes/1247-add-missing-protobuf-impl.md deleted file mode 100644 index b4722bd865..0000000000 --- a/.changelog/unreleased/bug-fixes/1247-add-missing-protobuf-impl.md +++ /dev/null @@ -1,3 +0,0 @@ -- Add missing `Protobuf` impl for `ics03_connection::connection::Counterparty` ([#1247]) - -[#1247]: https://github.com/informalsystems/ibc-rs/issues/1247 diff --git a/.changelog/unreleased/features/988-flex-error.md b/.changelog/unreleased/features/988-flex-error.md deleted file mode 100644 index d89a1b6525..0000000000 --- a/.changelog/unreleased/features/988-flex-error.md +++ /dev/null @@ -1,4 +0,0 @@ -- Use the [`flex-error`](https://docs.rs/flex-error/) crate to define and -handle errors ([#1158]) - -[#1158]: https://github.com/informalsystems/ibc-rs/issues/1158 diff --git a/.changelog/unreleased/improvements/1245-max-params-validation.md b/.changelog/unreleased/improvements/1245-max-params-validation.md deleted file mode 100644 index f0205f6b39..0000000000 --- a/.changelog/unreleased/improvements/1245-max-params-validation.md +++ /dev/null @@ -1,3 +0,0 @@ -- Add semantic validation of of `max_tx_size` and `max_num_msg` config options ([#1245]) - -[#1245]: https://github.com/informalsystems/ibc-rs/issues/1245 diff --git a/.github/ISSUE_TEMPLATE/release-template.md b/.github/ISSUE_TEMPLATE/release-template.md index 1e82f10254..8f5670b3a1 100644 --- a/.github/ISSUE_TEMPLATE/release-template.md +++ b/.github/ISSUE_TEMPLATE/release-template.md @@ -15,7 +15,7 @@ v without deliberation ⚡ -- [ ] Create a new release in the changelog, using [`unclog`](https://github.com/informalsystems/unclog). +- [ ] Create new release section in [CHANGELOG](./CHANGELOG.md) and move "unreleased" items into this section. - [ ] Bump all crate versions to the new version. - [ ] Reassign unfinished issues of previous milestone to the next milestone. - [ ] Update Cargo.lock file (if re-publishing `ibc-relayer-cli`) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e891310128..27130aec4a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -17,8 +17,8 @@ ______ For contributor use: -- [ ] Added a changelog entry, using [`unclog`](https://github.com/informalsystems/unclog). +- [ ] Updated the __Unreleased__ section of [CHANGELOG.md](https://github.com/informalsystems/ibc-rs/blob/master/CHANGELOG.md) with the issue. - [ ] If applicable: Unit tests written, added test to CI. - [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [ ] Updated relevant documentation (`docs/`) and code comments. -- [ ] Re-reviewed `Files changed` in the Github PR explorer. +- [ ] Re-reviewed `Files changed` in the Github PR explorer. \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 84ae3ce090..6dd3074b8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ -# CHANGELOG +# Changelog + +## Unreleased + +> Nothing yet ## v0.6.1 *July 22nd, 2021* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c090a5bb22..343deb9fb0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -85,27 +85,7 @@ To pull in updates from the origin repo, run ## Changelog -Every non-trivial PR must update the [CHANGELOG](CHANGELOG.md). This is -accomplished indirectly by adding entries to the `.changelog` folder in -[unclog](https://github.com/informalsystems/unclog) format. `CHANGELOG.md` will -be built by whomever is responsible for performing a release just prior to -release - this is to avoid changelog conflicts prior to releases. For example: - -```bash -# Add a .changelog entry for the `ibc` crate (in the `modules` directory) -# under the `IMPROVEMENTS` section in CHANGELOG.md. -unclog add -c ibc improvements 1234-some-issue - -# Add a .changelog entry for the `ibc-relayer-cli` crate (in the `relayer-cli` -# directory) under the `FEATURES` section in CHANGELOG.md. -unclog add -c ibc-relayer-cli features 1235-some-other-issue - -# Preview unreleased changes -unclog build -u - -# Build the new CHANGELOG.md from entries in ./.changelog/ -unclog build > CHANGELOG.md -``` +Every non-trivial PR must update the [CHANGELOG.md](CHANGELOG.MD). The Changelog is *not* a record of what Pull Requests were merged; the commit history already shows that. The Changelog is a notice to users diff --git a/Cargo.lock b/Cargo.lock index add8c920d0..40b9fbb47b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -792,16 +792,6 @@ 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" @@ -824,16 +814,6 @@ 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" @@ -892,9 +872,9 @@ checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" [[package]] name = "futures" -version = "0.3.16" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adc00f486adfc9ce99f77d717836f0c5aa84965eb0b4f051f4e83f7cab53f8b" +checksum = "0e7e43a803dae2fa37c1f6a8fe121e1f7bf9548b4dfc0522a42f34145dadfc27" dependencies = [ "futures-channel", "futures-core", @@ -907,9 +887,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.16" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74ed2411805f6e4e3d9bc904c95d5d423b89b3b25dc0250aa74729de20629ff9" +checksum = "e682a68b29a882df0545c143dc3646daefe80ba479bcdede94d5a703de2871e2" dependencies = [ "futures-core", "futures-sink", @@ -917,15 +897,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.16" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af51b1b4a7fdff033703db39de8802c673eb91855f2e0d47dcf3bf2c0ef01f99" +checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1" [[package]] name = "futures-executor" -version = "0.3.16" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d0d535a57b87e1ae31437b892713aee90cd2d7b0ee48727cd11fc72ef54761c" +checksum = "badaa6a909fac9e7236d0620a2f57f7664640c56575b71a7552fbd68deafab79" dependencies = [ "futures-core", "futures-task", @@ -934,15 +914,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.16" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b0e06c393068f3a6ef246c75cdca793d6a46347e75286933e5e75fd2fd11582" +checksum = "acc499defb3b348f8d8f3f66415835a9131856ff7714bf10dadfc4ec4bdb29a1" [[package]] name = "futures-macro" -version = "0.3.16" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54913bae956fb8df7f4dc6fc90362aa72e69148e3f39041fbe8742d21e0ac57" +checksum = "a4c40298486cdf52cc00cd6d6987892ba502c7656a16a4192a9992b1ccedd121" dependencies = [ "autocfg", "proc-macro-hack", @@ -953,21 +933,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.16" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f30aaa67363d119812743aa5f33c201a7a66329f97d1a887022971feea4b53" +checksum = "a57bead0ceff0d6dde8f465ecd96c9338121bb7717d3e7b108059531870c4282" [[package]] name = "futures-task" -version = "0.3.16" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe54a98670017f3be909561f6ad13e810d9a51f3f061b902062ca3da80799f2" +checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae" [[package]] name = "futures-util" -version = "0.3.16" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eb846bfd58e44a8481a00049e82c43e0ccb5d61f8dc071057cb19249dd4d78" +checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967" dependencies = [ "autocfg", "futures-channel", @@ -1030,10 +1010,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ "cfg-if 1.0.0", - "js-sys", "libc", "wasi 0.10.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -1338,11 +1316,11 @@ dependencies = [ name = "ibc" version = "0.6.1" dependencies = [ + "anomaly", "bytes", "chrono", "dyn-clonable", "env_logger", - "flex-error", "ibc-proto", "ics23", "modelator", @@ -1368,11 +1346,12 @@ dependencies = [ name = "ibc-proto" version = "0.9.0" dependencies = [ + "anomaly", "bytes", - "getrandom 0.2.3", "prost", "prost-types", "tendermint-proto", + "thiserror", "tonic", ] @@ -1380,7 +1359,7 @@ dependencies = [ name = "ibc-relayer" version = "0.6.1" dependencies = [ - "anyhow", + "anomaly", "async-stream", "async-trait", "bech32", @@ -1391,12 +1370,10 @@ dependencies = [ "dyn-clonable", "dyn-clone", "env_logger", - "flex-error", "fraction", "futures", "hdpath", "hex", - "http", "humantime-serde", "ibc", "ibc-proto", @@ -1414,7 +1391,6 @@ dependencies = [ "serde_json", "serial_test", "sha2", - "signature", "sled", "subtle-encoding", "tendermint", @@ -1437,10 +1413,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", @@ -1463,6 +1439,7 @@ dependencies = [ "tendermint-light-client", "tendermint-proto", "tendermint-rpc", + "thiserror", "tokio", "toml", "tracing", @@ -1514,12 +1491,6 @@ 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" @@ -1583,9 +1554,9 @@ dependencies = [ [[package]] name = "k256" -version = "0.9.6" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "903ae2481bcdfdb7b68e0a9baa4b7c9aff600b9ae2e8e5bb5833b8c91ab851ea" +checksum = "008b0281ca8032567c9711cd48631781c15228301860a39b32deb28d63125e46" dependencies = [ "cfg-if 1.0.0", "ecdsa", @@ -2007,12 +1978,6 @@ 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 0be137fcab..eb389c9585 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,4 +18,4 @@ exclude = [ # tendermint-rpc = { git = "https://github.com/informalsystems/tendermint-rs", branch = "master" } # tendermint-proto = { git = "https://github.com/informalsystems/tendermint-rs", branch = "master" } # tendermint-light-client = { git = "https://github.com/informalsystems/tendermint-rs", branch = "master" } -# tendermint-testgen = { git = "https://github.com/informalsystems/tendermint-rs", branch = "master" } +# tendermint-testgen = { git = "https://github.com/informalsystems/tendermint-rs", branch = "master" } \ No newline at end of file diff --git a/docs/architecture/adr-007-error.md b/docs/architecture/adr-007-error.md deleted file mode 100644 index 95d072fc6b..0000000000 --- a/docs/architecture/adr-007-error.md +++ /dev/null @@ -1,228 +0,0 @@ -# ADR 007: Error Management - -## Changelog - -* 2020-07-26: Initial Proposal - -## Context - -This document describes the reason behind the switch from using -[`anomaly`](https://docs.rs/anomaly) for error handling to -the [`flex-error`](https://docs.rs/flex-error/) crate that is developed in-house. - -## Decision - -### Problem Statement - -To keep things brief, we will look at the issue of error handling from a specific example -in `relayer/src/error.rs`: - -```rust -pub type Error = anomaly::Error; - -#[derive(thiserror::Error)] -pub enum Kind { - #[error("GRPC error")] - Grpc, - ... -} - -impl Kind { - pub fn context(self, source: impl Into>) -> anomaly::Context { - Context::new(self, Some(source.into())) - } -} -``` - -The design above is meant to separate between two concerns: - - - The metadata about an error, as captured in `Kind`. - - The trace of how the error occured, as captured in `anomaly::Context`. - - The type `Error` is defined to be `anomaly::Error`, which is a newtype wrapper to `Box>`. - -There are a few issues with the original design using `anomaly`: - - - The error source type is erased and turned into a `Box`, making it difficult to recover metadata - information about the original error. - - The `Kind::context` method allows any error type to be used as an error source, making it difficult to statically - analyze which sub-error has what kind of error source. - -We can demonstrate the design issue with a specific use case: - -```rust -pub fn unbonding_period(&self) -> Result { - let mut client = self - .block_on(QueryClient::connect(self.grpc_addr.clone())) - .map_err(|e| Kind::Grpc.context(e))?; - - let request = Request::new(QueryParamsRequest {}); - - let response = self - .block_on(client.params(request)) - .map_err(|e| Kind::Grpc.context(e))?; - ... -} -``` - -Without the help of an IDE, it would be challenging to figure out that -the first use of `Kind::Grpc.context` has `tonic::Status` as the error source -type, while the second use has the error source type -`tonic::TransportError`. - -The mixing up of `tonic::Status` and `tonic::TransportError` as error sources -are not too critical in this specific case. However this would not be the -case if we want to use the error source information to determine whether -an error is _recoverable_ or not. For instance, let's say if we want to -implement custom retry logic only when the error source is -`std::io::Error`, there is not easy way to distinguished if an error -variant `Kind::Grpc` is caused by `std::io::Error`. - -### Proposed Design - -A better design is to define error construction functions with _explicit_ -error sources. The proposed design is as follows: - -```rust -pub struct Error(pub ErrorDetail, pub eyre::Report); - -pub enum ErrorDetail { - GrpcStatus { - status: tonic::Status - }, - GrpcTransport, - ... -} - -impl Error { - pub fn grpc_status(status: tonic::Status) -> Error { - let detail = ErrorDetail::GrpcStatus { status }; - Error(detail, Eyre::msg(detail)) - } - - pub fn grpc_transport(source: tonic::TransportError) -> Error { - let detail = ErrorDetail::GrpcTransport; - let trace = Eyre::new(source).wrap_err(detail); - Error(detail, trace) - } -} -``` - -There are a few things addressed by the design above: - - We use the `eyre::Report` type as an _error tracer_ to trace - the error sources, together with additional information such as backtrace. - - Depending on the error source type, we want to have different strategies - to trace the error. - - For example, we may not care about the metadata - inside `tonic::TransportError`, so we just discard the data - after tracing it using `eyre`. - - We define _error constructor functions_ that handle the error source using - different strategies. The function constructs the `ErrorDetail` and - `eyre::Report` values, and then wrap them as the `Error` tuple. - -In general, when the error sources are defined by external libraries, -we have little control of how the types are defined, and need to have -different ways to handle them. -But when we have multiple error types that are defined in the same crate, -we want to have special way to handle the propagation of error. - -For example, consider the `LinkError` type, which has the error -we defined earlier as the error source: - -```rust -use crate::error::{Error as RelayerError, ErrorDetail as RelayerErrorDetail}; - -pub struct LinkError(LinkErrorDetail, eyre::Report); - -pub enum LinkErrorDetail { - Relayer { - source: RelayerErrorDetail - }, - ... -} - -impl LinkError { - pub fn relayer_error((source_detail, trace): RelayerError) -> LinkError { - let detail = LinkErrorDetail::Relayer(source_detail); - LinkError(detail, trace.wrap_err(detail)) - } -} -``` - -We propagate the error detail to LinkErrorDetail so that we can recover -additional detail later on. Furthermore, we extract the `eyre::Report` -from the error source and use it to add additional information -when we construct `LinkError`. - -### `flex-error` - -The proposed design has a lot of boilerplate required to properly define -the error types. To reduce boilerplate, we have developed -[`flex-error`](https://docs.rs/flex-error/) with the `define_error!` -macro which makes it straightforward to implement the error types -using a DSL syntax. With that, the error types can instead be defined as: - -```rust -use flex_error::{define_error, TraceError}; - -define_error! { - Error { - GrpcStatus - { status: GrpcStatus } - | e | { format!("GRPC call return error status {0}", e.status) }, - GrpcTransport - [ TraceError ] - | _ | { "error in underlying transport when making GRPC call" }, - ... - } -} -``` - -Aside from the syntactic sugar provided by the `define_error!` macro, `flex-error` -also allows error tracer implementation to be switched based on the Cargo feature -flags set on the `flex-error` crate. For example, we can switch from the -[`eyre`](https://docs.rs/eyre/) tracer to the [`anyhow`](https://docs.rs/anyhow/) -tracer by disabling `"flex-error/eyre_tracer"` and enabling `"flex-error/anyhow_tracer"` features. - -If all error tracer features and the `"flex-error/std"` feature are disabled, -a simple `flex_error::StringTracer` is used for tracing errors. The `StringTracer` -do not provide additional information such as back trace, but it is useful -for supporting `no_std`, where standard constructs such as `std::error::Error` and -error backtrace are not available. - -The full documentation for `flex-error` is available at [Docs.rs](https://docs.rs/flex-error/). - -## Status - -Accepted - The PR has been merged in [#988](https://github.com/informalsystems/ibc-rs/pull/988) - -## Consequences - -All error definitions in the `ibc-rs` project will be defined using the -`flex-error` crate. - -### Positive - -- Fine grained error handling. -- Flexible error tracing. -- `no_std` support. - -### Negative - -- It takes time to learn about the DSL and how to manage different error sources. -- Compile errors arise inside the macros may be difficult to debug. -- IDE provides limited integration for code inside macros. - -### Neutral - -- The error variants are defined in the `ErrorDetail::ErrorVariant{...}` convention, - but the error constructor functions are defined in the `Error::error_variant(...)` - convention. - -## References - -- [PR #988](https://github.com/informalsystems/ibc-rs/pull/988): - Use flex-error to define errors -- [Issue #712](https://github.com/informalsystems/ibc-rs/issues/712): - Relayer error handling specification -- [Issue #11588](https://github.com/informalsystems/ibc-rs/issues/1158): - Tracking issue for no-std support diff --git a/modules/Cargo.toml b/modules/Cargo.toml index 2e534eee97..94634b45ca 100644 --- a/modules/Cargo.toml +++ b/modules/Cargo.toml @@ -14,11 +14,6 @@ description = """ """ [features] -default = ["std", "eyre_tracer"] -std = [ - "flex-error/std" -] -eyre_tracer = ["flex-error/eyre_tracer"] # This feature grants access to development-time mocking libraries, such as `MockContext` or `MockHeader`. # Depends on the `testgen` suite for generating Tendermint light blocks. mocks = [ "tendermint-testgen", "sha2" ] @@ -27,6 +22,7 @@ mocks = [ "tendermint-testgen", "sha2" ] # 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" @@ -40,7 +36,6 @@ 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 b956236439..6751d16fd0 100644 --- a/modules/src/application/ics20_fungible_token_transfer/error.rs +++ b/modules/src/application/ics20_fungible_token_transfer/error.rs @@ -1,46 +1,44 @@ -use crate::ics04_channel::error as channel_error; -use crate::ics24_host::error::ValidationError; +use anomaly::{BoxError, Context}; +use thiserror::Error; + use crate::ics24_host::identifier::{ChannelId, PortId}; -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" }, + +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())) } } 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 75f6a2eb49..c6e2388eb0 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; +use crate::application::ics20_fungible_token_transfer::error::{Error, Kind}; 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 = Error; + type Error = Kind; fn try_from(raw_msg: RawMsgTransfer) -> Result { let timeout_timestamp = Timestamp::from_nanoseconds(raw_msg.timeout_timestamp) - .map_err(|_| Error::invalid_packet_timeout_timestamp(raw_msg.timeout_timestamp))?; + .map_err(|_| Kind::InvalidPacketTimeoutTimestamp(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| { - Error::invalid_packet_timeout_height(format!("invalid timeout height {}", e)) + Kind::InvalidPacketTimeoutHeight(format!("invalid timeout height {}", e)) })?, }; @@ -71,11 +71,11 @@ impl TryFrom for MsgTransfer { source_port: raw_msg .source_port .parse() - .map_err(|e| Error::invalid_port_id(raw_msg.source_port.clone(), e))?, + .map_err(|_| Kind::InvalidPortId(raw_msg.source_port.clone()))?, source_channel: raw_msg .source_channel .parse() - .map_err(|e| Error::invalid_channel_id(raw_msg.source_channel.clone(), e))?, + .map_err(|_| Kind::InvalidChannelId(raw_msg.source_channel.clone()))?, token: raw_msg.token, sender: raw_msg.sender.into(), receiver: raw_msg.receiver.into(), @@ -101,14 +101,17 @@ impl From for RawMsgTransfer { #[cfg(test)] pub mod test_util { + use std::ops::Add; + use std::time::Duration; + use crate::{ ics24_host::identifier::{ChannelId, PortId}, test_utils::get_dummy_account_id, + timestamp::Timestamp, Height, }; use super::MsgTransfer; - use crate::timestamp::Timestamp; // Returns a dummy `RawMsgTransfer`, for testing only! pub fn get_dummy_msg_transfer(height: u64) -> MsgTransfer { @@ -120,7 +123,7 @@ pub mod test_util { token: None, sender: id.clone(), receiver: id, - timeout_timestamp: Timestamp::from_nanoseconds(1).unwrap(), + timeout_timestamp: Timestamp::now().add(Duration::from_secs(10)).unwrap(), timeout_height: Height { revision_number: 0, revision_height: height, 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 c23aed9688..2bb200ad9b 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; +use crate::application::ics20_fungible_token_transfer::error::{Error, Kind}; 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(|| { - Error::channel_not_found(msg.source_port.clone(), msg.source_channel.clone()) + Kind::ChannelNotFound(msg.source_port.clone(), msg.source_channel.clone()) })?; let destination_port = source_channel_end.counterparty().port_id().clone(); @@ -24,17 +24,14 @@ where .counterparty() .channel_id() .ok_or_else(|| { - Error::destination_channel_not_found( - msg.source_port.clone(), - msg.source_channel.clone(), - ) + Kind::DestinationChannelNotFound(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(|| { - Error::sequence_send_not_found(msg.source_port.clone(), msg.source_channel.clone()) + Kind::SequenceSendNotFound(msg.source_port.clone(), msg.source_channel.clone()) })?; //TODO: Application LOGIC. @@ -50,7 +47,8 @@ where timeout_timestamp: msg.timeout_timestamp, }; - let handler_output = send_packet(ctx, packet).map_err(Error::ics04_channel)?; + let handler_output = + send_packet(ctx, packet).map_err(|e| Kind::HandlerRaisedError.context(e))?; //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 b25fccbb1b..1b9542c264 100644 --- a/modules/src/events.rs +++ b/modules/src/events.rs @@ -1,69 +1,19 @@ 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 { @@ -287,28 +237,14 @@ impl RawObject { pub fn extract_events( events: &HashMap, S>, action_string: &str, -) -> Result<(), Error> { +) -> Result<(), BoxError> { if let Some(message_action) = events.get("message.action") { if message_action.contains(&action_string.to_owned()) { return Ok(()); } - return Err(Error::missing_action_string()); + return Err("Missing action string".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()) + Err("Incorrect Event Type".into()) } #[macro_export] @@ -319,14 +255,32 @@ macro_rules! make_event { pub data: ::std::collections::HashMap>, } impl ::std::convert::TryFrom<$crate::events::RawObject> for $a { - type Error = $crate::event::Error; + type Error = ::anomaly::BoxError; fn try_from(result: $crate::events::RawObject) -> Result { - $crate::events::extract_events(&result.events, $b)?; - Ok($a { - data: result.events.clone(), - }) + match $crate::events::extract_events(&result.events, $b) { + Ok(()) => Ok($a { + data: result.events.clone(), + }), + Err(e) => Err(e), + } } } }; } + +#[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 da2d0bb12c..5a965fac43 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; +use std::convert::{TryFrom, TryInto}; 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; +use crate::ics02_client::error::{Error, Kind}; 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,9 +26,8 @@ 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; @@ -36,7 +35,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<(), Self::Error>; + fn validate_basic(&self) -> Result<(), Box>; /// Wrap into an `AnyConsensusState` fn wrap_any(self) -> AnyConsensusState; @@ -81,20 +80,20 @@ impl TryFrom for AnyConsensusState { fn try_from(value: Any) -> Result { match value.type_url.as_str() { - "" => Err(Error::empty_consensus_state_response()), + "" => Err(Kind::EmptyConsensusStateResponse.into()), TENDERMINT_CONSENSUS_STATE_TYPE_URL => Ok(AnyConsensusState::Tendermint( consensus_state::ConsensusState::decode_vec(&value.value) - .map_err(Error::decode_raw_client_state)?, + .map_err(|e| Kind::InvalidRawConsensusState.context(e))?, )), #[cfg(any(test, feature = "mocks"))] MOCK_CONSENSUS_STATE_TYPE_URL => Ok(AnyConsensusState::Mock( MockConsensusState::decode_vec(&value.value) - .map_err(Error::decode_raw_client_state)?, + .map_err(|e| Kind::InvalidRawConsensusState.context(e))?, )), - _ => Err(Error::unknown_consensus_state_type(value.type_url)), + _ => Err(Kind::UnknownConsensusStateType(value.type_url).into()), } } } @@ -128,17 +127,22 @@ pub struct AnyConsensusStateWithHeight { impl Protobuf for AnyConsensusStateWithHeight {} impl TryFrom for AnyConsensusStateWithHeight { - type Error = Error; + type Error = Kind; fn try_from(value: ConsensusStateWithHeight) -> Result { let state = value .consensus_state .map(AnyConsensusState::try_from) - .transpose()? - .ok_or_else(Error::empty_consensus_state_response)?; + .transpose() + .map_err(|_| Kind::InvalidRawConsensusState)? + .ok_or(Kind::EmptyConsensusStateResponse)?; Ok(AnyConsensusStateWithHeight { - height: value.height.ok_or_else(Error::missing_height)?.into(), + height: value + .height + .ok_or(Kind::MissingHeight)? + .try_into() + .unwrap_infallible(), consensus_state: state, }) } @@ -154,8 +158,6 @@ impl From for ConsensusStateWithHeight { } impl ConsensusState for AnyConsensusState { - type Error = Infallible; - fn client_type(&self) -> ClientType { self.client_type() } @@ -164,7 +166,7 @@ impl ConsensusState for AnyConsensusState { todo!() } - fn validate_basic(&self) -> Result<(), Infallible> { + fn validate_basic(&self) -> Result<(), Box> { todo!() } diff --git a/modules/src/ics02_client/client_def.rs b/modules/src/ics02_client/client_def.rs index afb12f5808..45ee085b1c 100644 --- a/modules/src/ics02_client/client_def.rs +++ b/modules/src/ics02_client/client_def.rs @@ -4,7 +4,8 @@ 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::Error; +use crate::ics02_client::context::ClientReader; +use crate::ics02_client::error::Kind; use crate::ics02_client::header::{AnyHeader, Header}; use crate::ics03_connection::connection::ConnectionEnd; use crate::ics04_channel::channel::ChannelEnd; @@ -25,9 +26,11 @@ pub trait ClientDef: Clone { /// TODO fn check_header_and_update_state( &self, + ctx: &dyn ClientReader, + client_id: ClientId, client_state: Self::ClientState, header: Self::Header, - ) -> Result<(Self::ClientState, Self::ConsensusState), Error>; + ) -> Result<(Self::ClientState, Self::ConsensusState), Box>; fn verify_upgrade_and_update_state( &self, @@ -35,7 +38,7 @@ pub trait ClientDef: Clone { consensus_state: &Self::ConsensusState, proof_upgrade_client: MerkleProof, proof_upgrade_consensus_state: MerkleProof, - ) -> Result<(Self::ClientState, Self::ConsensusState), Error>; + ) -> Result<(Self::ClientState, Self::ConsensusState), Box>; /// Verification functions as specified in: /// @@ -54,7 +57,7 @@ pub trait ClientDef: Clone { client_id: &ClientId, consensus_height: Height, expected_consensus_state: &AnyConsensusState, - ) -> Result<(), Error>; + ) -> Result<(), Box>; /// Verify a `proof` that a connection state matches that of the input `connection_end`. fn verify_connection_state( @@ -65,7 +68,7 @@ pub trait ClientDef: Clone { proof: &CommitmentProofBytes, connection_id: Option<&ConnectionId>, expected_connection_end: &ConnectionEnd, - ) -> Result<(), Error>; + ) -> Result<(), Box>; /// Verify a `proof` that a channel state matches that of the input `channel_end`. #[allow(clippy::too_many_arguments)] @@ -78,7 +81,7 @@ pub trait ClientDef: Clone { port_id: &PortId, channel_id: &ChannelId, expected_channel_end: &ChannelEnd, - ) -> Result<(), Error>; + ) -> Result<(), Box>; /// Verify the client state for this chain that it is stored on the counterparty chain. #[allow(clippy::too_many_arguments)] @@ -91,7 +94,7 @@ pub trait ClientDef: Clone { client_id: &ClientId, proof: &CommitmentProofBytes, client_state: &AnyClientState, - ) -> Result<(), Error>; + ) -> Result<(), Box>; /// Verify a `proof` that a packet has been commited. #[allow(clippy::too_many_arguments)] @@ -104,7 +107,7 @@ pub trait ClientDef: Clone { channel_id: &ChannelId, seq: &Sequence, commitment: String, - ) -> Result<(), Error>; + ) -> Result<(), Box>; /// Verify a `proof` that a packet has been commited. #[allow(clippy::too_many_arguments)] @@ -117,7 +120,7 @@ pub trait ClientDef: Clone { channel_id: &ChannelId, seq: &Sequence, ack: Vec, - ) -> Result<(), Error>; + ) -> Result<(), Box>; /// Verify a `proof` that of the next_seq_received. #[allow(clippy::too_many_arguments)] @@ -129,7 +132,7 @@ pub trait ClientDef: Clone { port_id: &PortId, channel_id: &ChannelId, seq: &Sequence, - ) -> Result<(), Error>; + ) -> Result<(), Box>; /// Verify a `proof` that a packet has not been received. #[allow(clippy::too_many_arguments)] @@ -141,7 +144,7 @@ pub trait ClientDef: Clone { port_id: &PortId, channel_id: &ChannelId, seq: &Sequence, - ) -> Result<(), Error>; + ) -> Result<(), Box>; } #[derive(Clone, Debug, PartialEq, Eq)] @@ -172,19 +175,21 @@ impl ClientDef for AnyClient { /// Validates an incoming `header` against the latest consensus state of this client. fn check_header_and_update_state( &self, + ctx: &dyn ClientReader, + client_id: ClientId, client_state: AnyClientState, header: AnyHeader, - ) -> Result<(AnyClientState, AnyConsensusState), Error> { + ) -> Result<(AnyClientState, AnyConsensusState), Box> { match self { Self::Tendermint(client) => { let (client_state, header) = downcast!( client_state => AnyClientState::Tendermint, header => AnyHeader::Tendermint, ) - .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Tendermint))?; + .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Tendermint))?; let (new_state, new_consensus) = - client.check_header_and_update_state(client_state, header)?; + client.check_header_and_update_state(ctx, client_id, client_state, header)?; Ok(( AnyClientState::Tendermint(new_state), @@ -198,10 +203,10 @@ impl ClientDef for AnyClient { client_state => AnyClientState::Mock, header => AnyHeader::Mock, ) - .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Mock))?; + .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Mock))?; let (new_state, new_consensus) = - client.check_header_and_update_state(client_state, header)?; + client.check_header_and_update_state(ctx, client_id, client_state, header)?; Ok(( AnyClientState::Mock(new_state), @@ -220,13 +225,13 @@ impl ClientDef for AnyClient { client_id: &ClientId, consensus_height: Height, expected_consensus_state: &AnyConsensusState, - ) -> Result<(), Error> { + ) -> Result<(), Box> { match self { Self::Tendermint(client) => { let client_state = downcast!( client_state => AnyClientState::Tendermint ) - .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Tendermint))?; + .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Tendermint))?; client.verify_client_consensus_state( client_state, @@ -244,7 +249,7 @@ impl ClientDef for AnyClient { let client_state = downcast!( client_state => AnyClientState::Mock ) - .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Mock))?; + .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Mock))?; client.verify_client_consensus_state( client_state, @@ -267,11 +272,11 @@ impl ClientDef for AnyClient { proof: &CommitmentProofBytes, connection_id: Option<&ConnectionId>, expected_connection_end: &ConnectionEnd, - ) -> Result<(), Error> { + ) -> Result<(), Box> { match self { Self::Tendermint(client) => { let client_state = downcast!(client_state => AnyClientState::Tendermint) - .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Tendermint))?; + .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Tendermint))?; client.verify_connection_state( client_state, @@ -286,7 +291,7 @@ impl ClientDef for AnyClient { #[cfg(any(test, feature = "mocks"))] Self::Mock(client) => { let client_state = downcast!(client_state => AnyClientState::Mock) - .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Mock))?; + .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Mock))?; client.verify_connection_state( client_state, @@ -309,11 +314,11 @@ impl ClientDef for AnyClient { port_id: &PortId, channel_id: &ChannelId, expected_channel_end: &ChannelEnd, - ) -> Result<(), Error> { + ) -> Result<(), Box> { match self { Self::Tendermint(client) => { let client_state = downcast!(client_state => AnyClientState::Tendermint) - .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Tendermint))?; + .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Tendermint))?; client.verify_channel_state( client_state, @@ -329,7 +334,7 @@ impl ClientDef for AnyClient { #[cfg(any(test, feature = "mocks"))] Self::Mock(client) => { let client_state = downcast!(client_state => AnyClientState::Mock) - .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Mock))?; + .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Mock))?; client.verify_channel_state( client_state, @@ -353,13 +358,13 @@ impl ClientDef for AnyClient { client_id: &ClientId, proof: &CommitmentProofBytes, client_state_on_counterparty: &AnyClientState, - ) -> Result<(), Error> { + ) -> Result<(), Box> { match self { Self::Tendermint(client) => { let client_state = downcast!( client_state => AnyClientState::Tendermint ) - .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Tendermint))?; + .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Tendermint))?; client.verify_client_full_state( client_state, @@ -377,7 +382,7 @@ impl ClientDef for AnyClient { let client_state = downcast!( client_state => AnyClientState::Mock ) - .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Mock))?; + .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Mock))?; client.verify_client_full_state( client_state, @@ -400,13 +405,13 @@ impl ClientDef for AnyClient { channel_id: &ChannelId, seq: &Sequence, commitment: String, - ) -> Result<(), Error> { + ) -> Result<(), Box> { match self { Self::Tendermint(client) => { let client_state = downcast!( client_state => AnyClientState::Tendermint ) - .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Tendermint))?; + .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Tendermint))?; client.verify_packet_data( client_state, @@ -424,7 +429,7 @@ impl ClientDef for AnyClient { let client_state = downcast!( client_state => AnyClientState::Mock ) - .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Mock))?; + .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Mock))?; client.verify_packet_data( client_state, @@ -448,13 +453,13 @@ impl ClientDef for AnyClient { channel_id: &ChannelId, seq: &Sequence, ack: Vec, - ) -> Result<(), Error> { + ) -> Result<(), Box> { match self { Self::Tendermint(client) => { let client_state = downcast!( client_state => AnyClientState::Tendermint ) - .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Tendermint))?; + .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Tendermint))?; client.verify_packet_acknowledgement( client_state, @@ -472,7 +477,7 @@ impl ClientDef for AnyClient { let client_state = downcast!( client_state => AnyClientState::Mock ) - .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Mock))?; + .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Mock))?; client.verify_packet_acknowledgement( client_state, @@ -495,13 +500,13 @@ impl ClientDef for AnyClient { port_id: &PortId, channel_id: &ChannelId, seq: &Sequence, - ) -> Result<(), Error> { + ) -> Result<(), Box> { match self { Self::Tendermint(client) => { let client_state = downcast!( client_state => AnyClientState::Tendermint ) - .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Tendermint))?; + .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Tendermint))?; client.verify_next_sequence_recv( client_state, @@ -518,7 +523,7 @@ impl ClientDef for AnyClient { let client_state = downcast!( client_state => AnyClientState::Mock ) - .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Mock))?; + .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Mock))?; client.verify_next_sequence_recv( client_state, @@ -539,13 +544,13 @@ impl ClientDef for AnyClient { port_id: &PortId, channel_id: &ChannelId, seq: &Sequence, - ) -> Result<(), Error> { + ) -> Result<(), Box> { match self { Self::Tendermint(client) => { let client_state = downcast!( client_state => AnyClientState::Tendermint ) - .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Tendermint))?; + .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Tendermint))?; client.verify_packet_receipt_absence( client_state, @@ -562,7 +567,7 @@ impl ClientDef for AnyClient { let client_state = downcast!( client_state => AnyClientState::Mock ) - .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Mock))?; + .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Mock))?; client.verify_packet_receipt_absence( client_state, @@ -582,14 +587,14 @@ impl ClientDef for AnyClient { consensus_state: &Self::ConsensusState, proof_upgrade_client: MerkleProof, proof_upgrade_consensus_state: MerkleProof, - ) -> Result<(Self::ClientState, Self::ConsensusState), Error> { + ) -> Result<(Self::ClientState, Self::ConsensusState), Box> { match self { Self::Tendermint(client) => { let (client_state, consensus_state) = downcast!( client_state => AnyClientState::Tendermint, consensus_state => AnyConsensusState::Tendermint, ) - .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Tendermint))?; + .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Tendermint))?; let (new_state, new_consensus) = client.verify_upgrade_and_update_state( client_state, @@ -610,7 +615,7 @@ impl ClientDef for AnyClient { client_state => AnyClientState::Mock, consensus_state => AnyConsensusState::Mock, ) - .ok_or_else(|| Error::client_args_type_mismatch(ClientType::Mock))?; + .ok_or_else(|| Kind::ClientArgsTypeMismatch(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 52a9be60d4..c23715939c 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; +use crate::ics02_client::error::{Error, Kind}; use crate::ics07_tendermint::client_state; use crate::ics24_host::error::ValidationError; use crate::ics24_host::identifier::{ChainId, ClientId}; @@ -103,19 +103,20 @@ impl TryFrom for AnyClientState { fn try_from(raw: Any) -> Result { match raw.type_url.as_str() { - "" => Err(Error::empty_client_state_response()), + "" => Err(Kind::EmptyClientStateResponse.into()), TENDERMINT_CLIENT_STATE_TYPE_URL => Ok(AnyClientState::Tendermint( client_state::ClientState::decode_vec(&raw.value) - .map_err(Error::decode_raw_client_state)?, + .map_err(|e| Kind::InvalidRawClientState.context(e))?, )), #[cfg(any(test, feature = "mocks"))] MOCK_CLIENT_STATE_TYPE_URL => Ok(AnyClientState::Mock( - MockClientState::decode_vec(&raw.value).map_err(Error::decode_raw_client_state)?, + MockClientState::decode_vec(&raw.value) + .map_err(|e| Kind::InvalidRawClientState.context(e))?, )), - _ => Err(Error::unknown_client_state_type(raw.type_url)), + _ => Err(Kind::UnknownClientStateType(raw.type_url).into()), } } } @@ -196,11 +197,11 @@ impl TryFrom for IdentifiedAnyClientState { fn try_from(raw: IdentifiedClientState) -> Result { Ok(IdentifiedAnyClientState { client_id: raw.client_id.parse().map_err(|e: ValidationError| { - Error::invalid_raw_client_id(raw.client_id.clone(), e) + Kind::InvalidRawClientId(raw.client_id.clone(), e.kind().clone()) })?, client_state: raw .client_state - .ok_or_else(Error::missing_raw_client_state)? + .ok_or_else(|| Kind::InvalidRawClientState.context("missing client state"))? .try_into()?, }) } diff --git a/modules/src/ics02_client/client_type.rs b/modules/src/ics02_client/client_type.rs index 7f363e2e57..033deffdc1 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::Error; +use super::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; + type Err = error::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::unknown_client_type(s.to_string())), + _ => Err(error::Kind::UnknownClientType(s.to_string()).into()), } } } diff --git a/modules/src/ics02_client/error.rs b/modules/src/ics02_client/error.rs index f1b783aaaa..a58f293201 100644 --- a/modules/src/ics02_client/error.rs +++ b/modules/src/ics02_client/error.rs @@ -1,201 +1,146 @@ +use std::num::ParseIntError; + +use anomaly::{BoxError, Context}; +use tendermint::hash::Hash; +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::ValidationError; +use crate::ics24_host::error::ValidationKind; use crate::ics24_host::identifier::ClientId; +use crate::timestamp::Timestamp; use crate::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) - }, + +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), + + /// Insufficient voting power in the commit + #[error("insufficient overlap {0}")] + InsufficientVotingPower(String), + + #[error("Timestamp none or {0} and now {1}")] + InvalidConsensusStateTimestamp(Timestamp, Timestamp), + + /// Not enough trust because insufficient validators overlap + #[error("not enough trust because insufficient validators overlap: {0}")] + NotEnoughTrustedValsSigned(String), + + /// Hash mismatch for the validator set + #[error("invalid validator set: header_validators_hash={0} validators_hash={1}")] + InvalidValidatorSet(Hash, Hash), + + #[error("not withing trusting period: expires_at={0} now={1}")] + ClientStateNotWithinTrustPeriod(Timestamp, Timestamp), + + #[error("header not withing trusting period: expires_at={0} now={1}")] + HeaderNotWithinTrustPeriod(Timestamp, Timestamp), + + #[error("Header revision {0} and client state revision {1} should coincide")] + MismatchedRevisions(u64, u64), +} + +impl Kind { + pub fn context(self, source: impl Into) -> Context { + Context::new(self, Some(source.into())) } } diff --git a/modules/src/ics02_client/events.rs b/modules/src/ics02_client/events.rs index 5082e55c2d..a7a6dd9daa 100644 --- a/modules/src/ics02_client/events.rs +++ b/modules/src/ics02_client/events.rs @@ -1,16 +1,17 @@ //! Types for the IBC events emitted from Tendermint Websocket by the client module. use std::convert::{TryFrom, TryInto}; -use prost::Message; +use anomaly::BoxError; use serde_derive::{Deserialize, Serialize}; use subtle_encoding::hex; use tendermint_proto::Protobuf; -use crate::events::{extract_attribute, Error, IbcEvent, RawObject}; +use crate::events::{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"; @@ -134,25 +135,6 @@ 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); @@ -176,9 +158,15 @@ impl From for CreateClient { } impl TryFrom for CreateClient { - type Error = Error; + type Error = BoxError; fn try_from(obj: RawObject) -> Result { - Ok(CreateClient(extract_attributes(&obj, "create_client")?)) + 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()?, + })) } } @@ -232,30 +220,24 @@ impl From for UpdateClient { } impl TryFrom for UpdateClient { - type Error = Error; - + type Error = BoxError; fn try_from(obj: RawObject) -> Result { - let header_str: Option = obj - .events - .get("update_client.header") - .and_then(|tags| tags[obj.idx].parse().ok()); - + let header_str: Option = some_attribute!(obj, "update_client.header"); let header: Option = match header_str { Some(str) => { - 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) + let header_bytes = hex::decode(str)?; + Some(Protobuf::decode(header_bytes.as_ref())?) } None => None, }; - + let consensus_height_str: String = attribute!(obj, "update_client.consensus_height"); Ok(UpdateClient { - common: extract_attributes(&obj, "update_client")?, + 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()?, + }, header, }) } @@ -291,12 +273,15 @@ impl ClientMisbehaviour { } impl TryFrom for ClientMisbehaviour { - type Error = Error; - fn try_from(obj: RawObject) -> Result { - Ok(ClientMisbehaviour(extract_attributes( - &obj, - "client_misbehaviour", - )?)) + 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()?, + })) } } diff --git a/modules/src/ics02_client/handler/create_client.rs b/modules/src/ics02_client/handler/create_client.rs index 558c77f296..5d2cd2fea4 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; +use crate::ics02_client::error::{Error, Kind}; 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| { - Error::client_identifier_constructor(msg.client_state().client_type(), id_counter, e) + Kind::ClientIdentifierConstructor(msg.client_state().client_type(), id_counter).context(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 c58eea832b..186548e2b6 100644 --- a/modules/src/ics02_client/handler/update_client.rs +++ b/modules/src/ics02_client/handler/update_client.rs @@ -1,16 +1,19 @@ //! Protocol logic specific to processing ICS2 messages of type `MsgUpdateAnyClient`. +use tracing::info; + 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::{AnyClientState, ClientState}; use crate::ics02_client::context::ClientReader; -use crate::ics02_client::error::Error; +use crate::ics02_client::error::{Error, Kind}; use crate::ics02_client::events::Attributes; use crate::ics02_client::handler::ClientResult; use crate::ics02_client::msgs::update_client::MsgUpdateAnyClient; use crate::ics24_host::identifier::ClientId; +use crate::timestamp::Timestamp; /// The result following the successful processing of a `MsgUpdateAnyClient` message. Preferably /// this data type should be used with a qualified name `update_client::Result` to avoid ambiguity. @@ -36,25 +39,51 @@ 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(|| Error::client_not_found(client_id.clone()))?; + .ok_or_else(|| Kind::ClientNotFound(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(|| Error::client_not_found(client_id.clone()))?; + .ok_or_else(|| Kind::ClientNotFound(client_id.clone()))?; + + if client_state.is_frozen() { + return Err(Kind::ClientFrozen(client_id).into()); + } + + // Read consensus state from the host chain store. + let latest_consensus_state = ctx + .consensus_state(&client_id, client_state.latest_height()) + .ok_or_else(|| { + Kind::ConsensusStateNotFound(client_id.clone(), client_state.latest_height()) + })?; - let latest_height = client_state.latest_height(); - ctx.consensus_state(&client_id, latest_height) - .ok_or_else(|| Error::consensus_state_not_found(client_id.clone(), latest_height))?; + info!("latest consensus state {:?}", latest_consensus_state); + + let duration = Timestamp::now() + .duration_since(&latest_consensus_state.timestamp()) + .ok_or_else(|| { + Kind::InvalidConsensusStateTimestamp( + latest_consensus_state.timestamp(), + Timestamp::now(), + ) + })?; + + if client_state.expired(duration) { + return Err(Kind::ClientStateNotWithinTrustPeriod( + latest_consensus_state.timestamp(), + Timestamp::now(), + ) + .into()); + } // 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| Error::header_verification_failure(e.to_string()))?; + .check_header_and_update_state(ctx, client_id.clone(), client_state, header) + .map_err(|e| Kind::HeaderVerificationFailure.context(e.to_string()))?; let result = ClientResult::Update(Result { client_id: client_id.clone(), @@ -74,22 +103,27 @@ pub fn process( #[cfg(test)] mod tests { use std::str::FromStr; + use test_env_log::test; use crate::events::IbcEvent; use crate::handler::HandlerOutput; - use crate::ics02_client::client_state::AnyClientState; - use crate::ics02_client::error::{Error, ErrorDetail}; + use crate::ics02_client::client_consensus::AnyConsensusState; + 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::handler::dispatch; use crate::ics02_client::handler::ClientResult::Update; - use crate::ics02_client::header::Header; + use crate::ics02_client::header::{AnyHeader, Header}; use crate::ics02_client::msgs::update_client::MsgUpdateAnyClient; use crate::ics02_client::msgs::ClientMsg; - use crate::ics24_host::identifier::ClientId; + use crate::ics24_host::identifier::{ChainId, ClientId}; use crate::mock::client_state::MockClientState; use crate::mock::context::MockContext; use crate::mock::header::MockHeader; + use crate::mock::host::{HostBlock, HostType}; use crate::test_utils::get_dummy_account_id; + use crate::timestamp::Timestamp; use crate::Height; #[test] @@ -97,10 +131,14 @@ mod tests { let client_id = ClientId::default(); let signer = get_dummy_account_id(); + let timestamp = Timestamp::now(); + let ctx = MockContext::default().with_client(&client_id, Height::new(0, 42)); let msg = MsgUpdateAnyClient { client_id: client_id.clone(), - header: MockHeader::new(Height::new(0, 46)).into(), + header: MockHeader::new(Height::new(0, 46)) + .with_timestamp(timestamp) + .into(), signer, }; @@ -124,9 +162,9 @@ mod tests { assert_eq!(upd_res.client_id, client_id); assert_eq!( upd_res.client_state, - AnyClientState::Mock(MockClientState(MockHeader::new( - msg.header.height() - ))) + AnyClientState::Mock(MockClientState( + MockHeader::new(msg.header.height()).with_timestamp(timestamp) + )) ) } _ => panic!("update handler result has incorrect type"), @@ -154,11 +192,11 @@ mod tests { let output = dispatch(&ctx, ClientMsg::UpdateClient(msg.clone())); match output { - Err(Error(ErrorDetail::ClientNotFound(e), _)) => { - assert_eq!(e.client_id, msg.client_id); + Ok(_) => { + panic!("unexpected success (expected error)"); } - _ => { - panic!("expected ClientNotFound error, instead got {:?}", output) + Err(err) => { + assert_eq!(err.kind(), &Kind::ClientNotFound(msg.client_id)); } } } @@ -208,4 +246,362 @@ mod tests { } } } + + #[test] + fn test_update_syntetic_tendermint_client_adjacent_ok() { + let client_id = ClientId::new(ClientType::Tendermint, 0).unwrap(); + let client_height = Height::new(1, 20); + let update_height = Height::new(1, 21); + + let ctx = MockContext::new( + ChainId::new("mockgaiaA".to_string(), 1), + HostType::Mock, + 5, + Height::new(1, 1), + ) + .with_client_parametrized( + &client_id, + client_height, + Some(ClientType::Tendermint), // The target host chain (B) is synthetic TM. + Some(client_height), + ); + + let ctx_b = MockContext::new( + ChainId::new("mockgaiaB".to_string(), 1), + HostType::SyntheticTendermint, + 5, + update_height, + ); + + let signer = get_dummy_account_id(); + + let block_ref = ctx_b.host_block(update_height); + let mut latest_header: AnyHeader = block_ref.cloned().map(Into::into).unwrap(); + + latest_header = match latest_header { + AnyHeader::Tendermint(mut theader) => { + theader.trusted_height = client_height; + AnyHeader::Tendermint(theader) + } + AnyHeader::Mock(m) => AnyHeader::Mock(m), + }; + + let msg = MsgUpdateAnyClient { + client_id: client_id.clone(), + header: latest_header, + signer, + }; + + let output = dispatch(&ctx, ClientMsg::UpdateClient(msg.clone())); + + match output { + Ok(HandlerOutput { + result, + mut events, + log, + }) => { + assert_eq!(events.len(), 1); + let event = events.pop().unwrap(); + assert!( + matches!(event, IbcEvent::UpdateClient(e) if e.client_id() == &msg.client_id) + ); + assert!(log.is_empty()); + // Check the result + match result { + Update(upd_res) => { + assert_eq!(upd_res.client_id, client_id); + assert!(!upd_res.client_state.is_frozen()); + assert_eq!(upd_res.client_state.latest_height(), msg.header.height(),) + } + _ => panic!("update handler result has incorrect type"), + } + } + Err(err) => { + panic!("unexpected error: {}", err); + } + } + } + + #[test] + fn test_update_syntetic_tendermint_client_non_adjacent_ok() { + let client_id = ClientId::new(ClientType::Tendermint, 0).unwrap(); + let client_height = Height::new(1, 20); + let update_height = Height::new(1, 21); + + let ctx = MockContext::new( + ChainId::new("mockgaiaA".to_string(), 1), + HostType::Mock, + 5, + Height::new(1, 1), + ) + .with_client_parametrized_history( + &client_id, + client_height, + Some(ClientType::Tendermint), // The target host chain (B) is synthetic TM. + Some(client_height), + ); + + let ctx_b = MockContext::new( + ChainId::new("mockgaiaB".to_string(), 1), + HostType::SyntheticTendermint, + 5, + update_height, + ); + + let signer = get_dummy_account_id(); + + let block_ref = ctx_b.host_block(update_height); + let mut latest_header: AnyHeader = block_ref.cloned().map(Into::into).unwrap(); + + let trusted_height = client_height.clone().sub(1).unwrap_or_default(); + + latest_header = match latest_header { + AnyHeader::Tendermint(mut theader) => { + theader.trusted_height = trusted_height; + AnyHeader::Tendermint(theader) + } + AnyHeader::Mock(m) => AnyHeader::Mock(m), + }; + + let msg = MsgUpdateAnyClient { + client_id: client_id.clone(), + header: latest_header, + signer, + }; + + let output = dispatch(&ctx, ClientMsg::UpdateClient(msg.clone())); + + match output { + Ok(HandlerOutput { + result, + mut events, + log, + }) => { + assert_eq!(events.len(), 1); + let event = events.pop().unwrap(); + assert!( + matches!(event, IbcEvent::UpdateClient(e) if e.client_id() == &msg.client_id) + ); + assert!(log.is_empty()); + // Check the result + match result { + Update(upd_res) => { + assert_eq!(upd_res.client_id, client_id); + assert!(!upd_res.client_state.is_frozen()); + assert_eq!(upd_res.client_state.latest_height(), msg.header.height(),) + } + _ => panic!("update handler result has incorrect type"), + } + } + Err(err) => { + panic!("unexpected error: {}", err); + } + } + } + + #[test] + fn test_update_syntetic_tendermint_client_duplicate_ok() { + let client_id = ClientId::new(ClientType::Tendermint, 0).unwrap(); + let client_height = Height::new(1, 20); + + let chain_start_height = Height::new(1, 11); + + let ctx = MockContext::new( + ChainId::new("mockgaiaA".to_string(), 1), + HostType::Mock, + 5, + chain_start_height, + ) + .with_client_parametrized( + &client_id, + client_height, + Some(ClientType::Tendermint), // The target host chain (B) is synthetic TM. + Some(client_height), + ); + + let ctx_b = MockContext::new( + ChainId::new("mockgaiaB".to_string(), 1), + HostType::SyntheticTendermint, + 5, + client_height, + ); + + let signer = get_dummy_account_id(); + + let block_ref = ctx_b.host_block(client_height); + let latest_header: AnyHeader = match block_ref.cloned().map(Into::into).unwrap() { + AnyHeader::Tendermint(mut theader) => { + let cons_state = ctx + .latest_consensus_states(&client_id, &client_height) + .clone(); + if let AnyConsensusState::Tendermint(tcs) = cons_state { + theader.signed_header.header.time = tcs.timestamp; + } + AnyHeader::Tendermint(theader) + } + AnyHeader::Mock(header) => AnyHeader::Mock(header), + }; + + let msg = MsgUpdateAnyClient { + client_id: client_id.clone(), + header: latest_header, + signer, + }; + + let output = dispatch(&ctx, ClientMsg::UpdateClient(msg.clone())); + + match output { + Ok(HandlerOutput { + result, + mut events, + log, + }) => { + assert_eq!(events.len(), 1); + let event = events.pop().unwrap(); + assert!( + matches!(event, IbcEvent::UpdateClient(e) if e.client_id() == &msg.client_id) + ); + assert!(log.is_empty()); + // Check the result + match result { + Update(upd_res) => { + assert_eq!(upd_res.client_id, client_id); + assert!(!upd_res.client_state.is_frozen()); + assert_eq!( + upd_res.client_state, + ctx.latest_client_states(&client_id).clone() + ); + assert_eq!(upd_res.client_state.latest_height(), msg.header.height(),) + } + _ => panic!("update handler result has incorrect type"), + } + } + Err(_err) => { + panic!("unexpected error"); + } + } + } + + #[test] + fn test_update_syntetic_tendermint_client_duplicate_height_frozen() { + let client_id = ClientId::new(ClientType::Tendermint, 0).unwrap(); + let client_height = Height::new(1, 20); + + let chain_start_height = Height::new(1, 11); + + let ctx = MockContext::new( + ChainId::new("mockgaiaA".to_string(), 1), + HostType::Mock, + 5, + chain_start_height, + ) + .with_client_parametrized( + &client_id, + client_height, + Some(ClientType::Tendermint), // The target host chain (B) is synthetic TM. + Some(client_height), + ); + + let signer = get_dummy_account_id(); + + let block_ref = HostBlock::generate_block( + ChainId::new("mockgaiaB".to_string(), 1), + HostType::SyntheticTendermint, + client_height.revision_height, + ); + + let latest_header: AnyHeader = Some(block_ref).map(Into::into).unwrap(); + + let msg = MsgUpdateAnyClient { + client_id: client_id.clone(), + header: latest_header, + signer, + }; + + let output = dispatch(&ctx, ClientMsg::UpdateClient(msg.clone())); + + match output { + Ok(HandlerOutput { + result, + mut events, + log, + }) => { + assert_eq!(events.len(), 1); + let event = events.pop().unwrap(); + assert!( + matches!(event, IbcEvent::UpdateClient(e) if e.client_id() == &msg.client_id) + ); + assert!(log.is_empty()); + // Check the result + match result { + Update(upd_res) => { + assert_eq!(upd_res.client_id, client_id); + assert!(upd_res.client_state.is_frozen()); + assert_ne!( + upd_res.client_state, + ctx.latest_client_states(&client_id).clone() + ); + assert_eq!(upd_res.client_state.latest_height(), msg.header.height(),) + } + _ => panic!("update handler result has incorrect type"), + } + } + Err(_err) => { + panic!("unexpected error"); + } + } + } + + #[test] + fn test_update_syntetic_tendermint_client_lower_height() { + let client_id = ClientId::new(ClientType::Tendermint, 0).unwrap(); + let client_height = Height::new(1, 20); + + let client_update_height = Height::new(1, 19); + + let chain_start_height = Height::new(1, 11); + + let ctx = MockContext::new( + ChainId::new("mockgaiaA".to_string(), 1), + HostType::Mock, + 5, + chain_start_height, + ) + .with_client_parametrized( + &client_id, + client_height, + Some(ClientType::Tendermint), // The target host chain (B) is synthetic TM. + Some(client_height), + ); + + let ctx_b = MockContext::new( + ChainId::new("mockgaiaB".to_string(), 1), + HostType::SyntheticTendermint, + 5, + client_height, + ); + + let signer = get_dummy_account_id(); + + let block_ref = ctx_b.host_block(client_update_height); + let latest_header: AnyHeader = block_ref.cloned().map(Into::into).unwrap(); + + let msg = MsgUpdateAnyClient { + client_id, + header: latest_header, + signer, + }; + + let output = dispatch(&ctx, ClientMsg::UpdateClient(msg)); + + match output { + Ok(_) => { + panic!("update handler result has incorrect type"); + } + Err(err) => { + assert_eq!(err.kind(), &Kind::HeaderVerificationFailure); + println!("err is {}", err.to_string()); + } + } + } } diff --git a/modules/src/ics02_client/handler/upgrade_client.rs b/modules/src/ics02_client/handler/upgrade_client.rs index 57e2e1829f..b0b910c871 100644 --- a/modules/src/ics02_client/handler/upgrade_client.rs +++ b/modules/src/ics02_client/handler/upgrade_client.rs @@ -4,9 +4,10 @@ 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, ClientState}; +use crate::ics02_client::client_state::AnyClientState; +use crate::ics02_client::client_state::ClientState; use crate::ics02_client::context::ClientReader; -use crate::ics02_client::error::Error; +use crate::ics02_client::error::{Error, Kind}; use crate::ics02_client::events::Attributes; use crate::ics02_client::handler::ClientResult; use crate::ics02_client::msgs::upgrade_client::MsgUpgradeAnyClient; @@ -31,33 +32,36 @@ pub fn process( // Read client state from the host chain store. let client_state = ctx .client_state(&client_id) - .ok_or_else(|| Error::client_not_found(client_id.clone()))?; + .ok_or_else(|| Kind::ClientNotFound(client_id.clone()))?; if client_state.is_frozen() { - return Err(Error::client_frozen(client_id)); + return Err(Kind::ClientFrozen(client_id).into()); } let upgrade_client_state = msg.client_state.clone(); if client_state.latest_height() >= upgrade_client_state.latest_height() { - return Err(Error::low_upgrade_height( + return Err(Kind::LowUpgradeHeight( client_state.latest_height(), upgrade_client_state.latest_height(), - )); + ) + .into()); } let client_type = ctx .client_type(&client_id) - .ok_or_else(|| Error::client_not_found(client_id.clone()))?; + .ok_or_else(|| Kind::ClientNotFound(client_id.clone()))?; let client_def = AnyClient::from_client_type(client_type); - let (new_client_state, new_consensus_state) = client_def.verify_upgrade_and_update_state( - &upgrade_client_state, - &msg.consensus_state, - msg.proof_upgrade_client.clone(), - msg.proof_upgrade_consensus_state, - )?; + let (new_client_state, new_consensus_state) = client_def + .verify_upgrade_and_update_state( + &upgrade_client_state, + &msg.consensus_state, + msg.proof_upgrade_client.clone(), + msg.proof_upgrade_consensus_state, + ) + .map_err(|e| Kind::UpgradeVerificationFailure.context(e.to_string()))?; // Not implemented yet: https://github.com/informalsystems/ibc-rs/issues/722 // todo!() @@ -82,7 +86,7 @@ mod tests { use crate::events::IbcEvent; use crate::handler::HandlerOutput; - use crate::ics02_client::error::{Error, ErrorDetail}; + use crate::ics02_client::error::Kind; use crate::ics02_client::handler::dispatch; use crate::ics02_client::handler::ClientResult::Upgrade; use crate::ics02_client::msgs::upgrade_client::MsgUpgradeAnyClient; @@ -172,11 +176,11 @@ mod tests { let output = dispatch(&ctx, ClientMsg::UpgradeClient(msg.clone())); match output { - Err(Error(ErrorDetail::ClientNotFound(e), _)) => { - assert_eq!(e.client_id, msg.client_id); + Ok(_) => { + panic!("unexpected success (expected error)"); } - _ => { - panic!("expected ClientNotFound error, instead got {:?}", output); + Err(err) => { + assert_eq!(err.kind(), &Kind::ClientNotFound(msg.client_id)); } } } @@ -206,12 +210,14 @@ mod tests { let output = dispatch(&ctx, ClientMsg::UpgradeClient(msg.clone())); match output { - Err(Error(ErrorDetail::LowUpgradeHeight(e), _)) => { - assert_eq!(e.upgraded_height, Height::new(0, 42)); - assert_eq!(e.client_height, msg.client_state.latest_height()); + Ok(_) => { + panic!("unexpected success (expected error)"); } - _ => { - panic!("expected LowUpgradeHeight error, instead got {:?}", output); + Err(err) => { + assert_eq!( + err.kind(), + &Kind::LowUpgradeHeight(Height::new(0, 42), msg.client_state.latest_height()) + ); } } } diff --git a/modules/src/ics02_client/header.rs b/modules/src/ics02_client/header.rs index 16d9628938..4bf8cd2986 100644 --- a/modules/src/ics02_client/header.rs +++ b/modules/src/ics02_client/header.rs @@ -1,15 +1,15 @@ use std::convert::TryFrom; -use std::ops::Deref; + +use prost_types::Any; +use serde_derive::{Deserialize, Serialize}; +use tendermint_proto::Protobuf; use crate::ics02_client::client_type::ClientType; -use crate::ics02_client::error::Error; -use crate::ics07_tendermint::header::{decode_header, Header as TendermintHeader}; +use crate::ics02_client::error::{Error, Kind}; +use crate::ics07_tendermint::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 => { - let val = decode_header(raw.value.deref()).map_err(Error::tendermint)?; - - Ok(AnyHeader::Tendermint(val)) - } + TENDERMINT_HEADER_TYPE_URL => Ok(AnyHeader::Tendermint( + TendermintHeader::decode_vec(&raw.value) + .map_err(|e| Kind::InvalidRawHeader.context(e))?, + )), #[cfg(any(test, feature = "mocks"))] MOCK_HEADER_TYPE_URL => Ok(AnyHeader::Mock( - MockHeader::decode_vec(&raw.value).map_err(Error::invalid_raw_header)?, + MockHeader::decode_vec(&raw.value) + .map_err(|e| Kind::InvalidRawHeader.context(e))?, )), - _ => Err(Error::unknown_header_type(raw.type_url)), + _ => Err(Kind::UnknownHeaderType(raw.type_url).into()), } } } diff --git a/modules/src/ics02_client/height.rs b/modules/src/ics02_client/height.rs index b75d1ccdc5..5f29755f90 100644 --- a/modules/src/ics02_client/height.rs +++ b/modules/src/ics02_client/height.rs @@ -1,15 +1,13 @@ use std::cmp::Ordering; -use std::convert::TryFrom; -use std::num::ParseIntError; +use std::convert::{Infallible, TryFrom}; 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; +use crate::ics02_client::error::{Error, Kind}; #[derive(Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Height { @@ -52,7 +50,9 @@ impl Height { pub fn sub(&self, delta: u64) -> Result { if self.revision_height <= delta { - return Err(Error::invalid_height_result()); + return Err(Kind::InvalidHeightResult + .context("height cannot end up zero or negative") + .into()); } Ok(Height { @@ -103,12 +103,14 @@ impl Ord for Height { impl Protobuf for Height {} -impl From for Height { - fn from(raw: RawHeight) -> Self { - Height { +impl TryFrom for Height { + type Error = Infallible; + + fn try_from(raw: RawHeight) -> Result { + Ok(Height { revision_number: raw.revision_number, revision_height: raw.revision_height, - } + }) } } @@ -137,30 +139,18 @@ 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 = HeightError; + type Error = Kind; fn try_from(value: &str) -> Result { let split: Vec<&str> = value.split('-').collect(); Ok(Height { revision_number: split[0] .parse::() - .map_err(|e| HeightError::height_conversion(value.to_owned(), e))?, + .map_err(|e| Kind::HeightConversion(value.to_owned(), e))?, revision_height: split[1] .parse::() - .map_err(|e| HeightError::height_conversion(value.to_owned(), e))?, + .map_err(|e| Kind::HeightConversion(value.to_owned(), e))?, }) } } @@ -172,7 +162,7 @@ impl From for String { } impl FromStr for Height { - type Err = HeightError; + type Err = Kind; 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 c36f985398..e1ad1eb304 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; +use crate::ics02_client::error::{Error, Kind}; use crate::ics07_tendermint::misbehaviour::Misbehaviour as TmMisbehaviour; #[cfg(any(test, feature = "mocks"))] @@ -68,17 +68,19 @@ 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(Error::decode_raw_misbehaviour)?, + TmMisbehaviour::decode_vec(&raw.value) + .map_err(|e| Kind::InvalidRawMisbehaviour.context(e))?, )), #[cfg(any(test, feature = "mocks"))] MOCK_MISBEHAVIOUR_TYPE_URL => Ok(AnyMisbehaviour::Mock( - MockMisbehaviour::decode_vec(&raw.value).map_err(Error::decode_raw_misbehaviour)?, + MockMisbehaviour::decode_vec(&raw.value) + .map_err(|e| Kind::InvalidRawMisbehaviour.context(e))?, )), - _ => Err(Error::unknown_misbehaviour_type(raw.type_url)), + _ => Err(Kind::UnknownMisbehaviourType(raw.type_url).into()), } } } diff --git a/modules/src/ics02_client/msgs/create_client.rs b/modules/src/ics02_client/msgs/create_client.rs index a01266d685..5309239167 100644 --- a/modules/src/ics02_client/msgs/create_client.rs +++ b/modules/src/ics02_client/msgs/create_client.rs @@ -8,7 +8,8 @@ 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::Error; +use crate::ics02_client::error; +use crate::ics02_client::error::{Error, Kind}; use crate::signer::Signer; use crate::tx_msg::Msg; @@ -29,10 +30,11 @@ impl MsgCreateAnyClient { signer: Signer, ) -> Result { if client_state.client_type() != consensus_state.client_type() { - return Err(Error::raw_client_and_consensus_state_types_mismatch( - 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()); } Ok(MsgCreateAnyClient { client_state, @@ -68,18 +70,20 @@ 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(Error::missing_raw_client_state)?; + .ok_or_else(|| Kind::InvalidRawClientState.context("missing client state"))?; let raw_consensus_state = raw .consensus_state - .ok_or_else(Error::missing_raw_client_state)?; + .ok_or_else(|| Kind::InvalidRawConsensusState.context("missing consensus state"))?; MsgCreateAnyClient::new( - AnyClientState::try_from(raw_client_state)?, - AnyConsensusState::try_from(raw_consensus_state)?, + AnyClientState::try_from(raw_client_state) + .map_err(|e| Kind::InvalidRawClientState.context(e))?, + AnyConsensusState::try_from(raw_consensus_state) + .map_err(|e| Kind::InvalidRawConsensusState.context(e))?, raw.signer.into(), ) } diff --git a/modules/src/ics02_client/msgs/misbehavior.rs b/modules/src/ics02_client/msgs/misbehavior.rs index 7d9c01c355..b5666b7b18 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; +use crate::ics02_client::error::{Error, Kind}; use crate::ics02_client::misbehaviour::AnyMisbehaviour; use crate::ics24_host::identifier::ClientId; use crate::signer::Signer; @@ -42,15 +42,13 @@ impl TryFrom for MsgSubmitAnyMisbehaviour { type Error = Error; fn try_from(raw: RawMsgSubmitMisbehaviour) -> Result { - let raw_misbehaviour = raw - .misbehaviour - .ok_or_else(Error::missing_raw_misbehaviour)?; + let raw_misbehaviour = raw.misbehaviour.ok_or(Kind::InvalidRawMisbehaviour)?; Ok(MsgSubmitAnyMisbehaviour { client_id: raw .client_id .parse() - .map_err(Error::invalid_raw_misbehaviour)?, + .map_err(|e| Kind::InvalidRawMisbehaviour.context(e))?, 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 9062f203a1..1214f66db7 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; +use crate::ics02_client::error::{Error, Kind}; 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_else(Error::missing_raw_header)?; + let raw_header = raw.header.ok_or(Kind::InvalidRawHeader)?; Ok(MsgUpdateAnyClient { client_id: raw .client_id .parse() - .map_err(Error::invalid_msg_update_client_id)?, + .map_err(|e| Kind::InvalidMsgUpdateClientId.context(e))?, 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 624ec819b8..70a6e1cbb5 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::Error; +use crate::ics02_client::error::Kind; use crate::ics23_commitment::commitment::CommitmentProofBytes; use crate::ics24_host::identifier::ClientId; use crate::signer::Signer; @@ -80,29 +80,28 @@ impl From for RawMsgUpgradeClient { } impl TryFrom for MsgUpgradeAnyClient { - type Error = Error; + type Error = Kind; fn try_from(proto_msg: RawMsgUpgradeClient) -> Result { - let raw_client_state = proto_msg - .client_state - .ok_or_else(Error::missing_raw_client_state)?; - + let raw_client_state = proto_msg.client_state.ok_or(Kind::InvalidRawClientState)?; let raw_consensus_state = proto_msg .consensus_state - .ok_or_else(Error::missing_raw_client_state)?; + .ok_or(Kind::InvalidRawConsensusState)?; 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(Error::invalid_client_identifier)?, - client_state: AnyClientState::try_from(raw_client_state)?, - consensus_state: AnyConsensusState::try_from(raw_consensus_state)?, + .map_err(|e| Kind::InvalidClientIdentifier(e.kind().clone()))?, + client_state: AnyClientState::try_from(raw_client_state) + .map_err(|_| Kind::InvalidRawClientState)?, + consensus_state: AnyConsensusState::try_from(raw_consensus_state) + .map_err(|_| Kind::InvalidRawConsensusState)?, proof_upgrade_client: RawMerkleProof::try_from(c_bytes) - .map_err(Error::invalid_upgrade_client_proof)?, + .map_err(Kind::InvalidUpgradeClientProof)?, proof_upgrade_consensus_state: RawMerkleProof::try_from(cs_bytes) - .map_err(Error::invalid_upgrade_consensus_state_proof)?, + .map_err(Kind::InvalidUpgradeConsensusStateProof)?, signer: proto_msg.signer.into(), }) } diff --git a/modules/src/ics03_connection/connection.rs b/modules/src/ics03_connection/connection.rs index 510ffc9b75..3d2ca44401 100644 --- a/modules/src/ics03_connection/connection.rs +++ b/modules/src/ics03_connection/connection.rs @@ -3,6 +3,7 @@ use std::str::FromStr; use std::time::Duration; use std::u64; +use anomaly::fail; use serde::{Deserialize, Serialize}; use tendermint_proto::Protobuf; @@ -11,7 +12,7 @@ use ibc_proto::ibc::core::connection::v1::{ IdentifiedConnection as RawIdentifiedConnection, }; -use crate::ics03_connection::error::Error; +use crate::ics03_connection::error::{self, Error, Kind}; use crate::ics03_connection::version::Version; use crate::ics23_commitment::commitment::CommitmentPrefix; use crate::ics24_host::error::ValidationError; @@ -44,7 +45,7 @@ impl IdentifiedConnectionEnd { impl Protobuf for IdentifiedConnectionEnd {} impl TryFrom for IdentifiedConnectionEnd { - type Error = Error; + type Error = anomaly::Error; fn try_from(value: RawIdentifiedConnection) -> Result { let raw_connection_end = RawConnectionEnd { @@ -56,7 +57,7 @@ impl TryFrom for IdentifiedConnectionEnd { }; Ok(IdentifiedConnectionEnd { - connection_id: value.id.parse().map_err(Error::invalid_identifier)?, + connection_id: value.id.parse().map_err(|_| Kind::IdentifierError)?, connection_end: raw_connection_end.try_into()?, }) } @@ -104,28 +105,32 @@ impl Default for ConnectionEnd { impl Protobuf for ConnectionEnd {} impl TryFrom for ConnectionEnd { - type Error = Error; + type Error = anomaly::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(Error::empty_proto_connection_end()); + return Err(Kind::EmptyProtoConnectionEnd.into()); } Ok(Self::new( state, - value.client_id.parse().map_err(Error::invalid_identifier)?, + value + .client_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, value .counterparty - .ok_or_else(Error::missing_counterparty)? + .ok_or(Kind::MissingCounterparty)? .try_into()?, value .versions .into_iter() .map(Version::try_from) - .collect::, _>>()?, + .collect::, _>>() + .map_err(|e| Kind::InvalidVersion.context(e))?, Duration::from_nanos(value.delay_period), )) } @@ -251,25 +256,26 @@ impl Default for Counterparty { } } -impl Protobuf 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 = Error; + type Error = anomaly::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(Error::invalid_identifier)?; + .map_err(|e| Kind::IdentifierError.context(e))?; Ok(Counterparty::new( - value.client_id.parse().map_err(Error::invalid_identifier)?, + value + .client_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, connection_id, value .prefix - .ok_or_else(Error::missing_counterparty)? + .ok_or(Kind::MissingCounterparty)? .key_prefix .into(), )) @@ -340,14 +346,15 @@ 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), - _ => Err(Error::invalid_state(s)), + _ => fail!(error::Kind::InvalidState(s), s), } } @@ -371,14 +378,14 @@ impl State { } impl TryFrom for State { - type Error = Error; + type Error = anomaly::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(Error::invalid_state(value)), + _ => Err(Kind::InvalidState(value).into()), } } } diff --git a/modules/src/ics03_connection/error.rs b/modules/src/ics03_connection/error.rs index 1fb2edb280..2249e21b49 100644 --- a/modules/src/ics03_connection/error.rs +++ b/modules/src/ics03_connection/error.rs @@ -1,175 +1,103 @@ -use crate::ics02_client::error as client_error; -use crate::ics24_host::error::ValidationError; +use anomaly::{BoxError, Context}; +use thiserror::Error; + use crate::ics24_host::identifier::{ClientId, ConnectionId}; -use crate::proofs::ProofError; use crate::Height; -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) - }, + +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())) } } diff --git a/modules/src/ics03_connection/events.rs b/modules/src/ics03_connection/events.rs index 2380ce2155..b90822eba3 100644 --- a/modules/src/ics03_connection/events.rs +++ b/modules/src/ics03_connection/events.rs @@ -1,7 +1,9 @@ //! Types for the IBC events emitted from Tendermint Websocket by the connection module. -use crate::events::{extract_attribute, maybe_extract_attribute, Error, IbcEvent, RawObject}; +use crate::events::{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; @@ -130,9 +132,18 @@ impl From for OpenInit { } impl TryFrom for OpenInit { - type Error = Error; + type Error = BoxError; fn try_from(obj: RawObject) -> Result { - Ok(OpenInit(extract_attributes(&obj, "connection_open_init")?)) + 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"), + })) } } @@ -167,9 +178,18 @@ impl From for OpenTry { } impl TryFrom for OpenTry { - type Error = Error; + type Error = BoxError; fn try_from(obj: RawObject) -> Result { - Ok(OpenTry(extract_attributes(&obj, "connection_open_try")?)) + 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"), + })) } } @@ -204,9 +224,18 @@ impl From for OpenAck { } impl TryFrom for OpenAck { - type Error = Error; + type Error = BoxError; fn try_from(obj: RawObject) -> Result { - Ok(OpenAck(extract_attributes(&obj, "connection_open_ack")?)) + 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"), + })) } } @@ -241,12 +270,21 @@ impl From for OpenConfirm { } impl TryFrom for OpenConfirm { - type Error = Error; + type Error = BoxError; fn try_from(obj: RawObject) -> Result { - Ok(OpenConfirm(extract_attributes( - &obj, - "connection_open_confirm", - )?)) + 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" + ), + })) } } diff --git a/modules/src/ics03_connection/handler/conn_open_ack.rs b/modules/src/ics03_connection/handler/conn_open_ack.rs index 306980cbe6..d30aa338d7 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; +use crate::ics03_connection::error::{Error, Kind}; 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,12 +44,16 @@ pub(crate) fn process( Ok(old_conn_end) } else { // Old connection end is in incorrect state, propagate the error. - Err(Error::connection_mismatch(msg.connection_id().clone())) + Err(Into::::into(Kind::ConnectionMismatch( + msg.connection_id().clone(), + ))) } } None => { // No connection end exists for this conn. identifier. Impossible to continue handshake. - Err(Error::uninitialized_connection(msg.connection_id().clone())) + Err(Into::::into(Kind::UninitializedConnection( + msg.connection_id().clone(), + ))) } }?; @@ -104,7 +108,7 @@ mod tests { use crate::events::IbcEvent; use crate::ics03_connection::connection::{ConnectionEnd, Counterparty, State}; - use crate::ics03_connection::error; + use crate::ics03_connection::error::Kind; 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; @@ -122,7 +126,7 @@ mod tests { ctx: MockContext, msg: ConnectionMsg, want_pass: bool, - match_error: Box, + error_kind: Option, } let msg_ack = @@ -180,28 +184,14 @@ mod tests { .with_connection(conn_id.clone(), default_conn_end), msg: ConnectionMsg::ConnectionOpenAck(Box::new(msg_ack.clone())), want_pass: true, - match_error: Box::new(|_| { - panic!("should not have error") - }), + error_kind: None, }, 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, - 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"); - } - } - }) - }, + error_kind: Some(Kind::UninitializedConnection(conn_id.clone())), }, Test { name: "Processing fails due to connections mismatch (incorrect 'open' state)".to_string(), @@ -211,19 +201,7 @@ mod tests { .with_connection(conn_id.clone(), conn_end_open), msg: ConnectionMsg::ConnectionOpenAck(Box::new(msg_ack.clone())), want_pass: false, - 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"); - } - } - }) - }, + error_kind: Some(Kind::ConnectionMismatch(conn_id.clone())) }, Test { name: "Processing fails: ConsensusStateVerificationFailure due to empty counterparty prefix".to_string(), @@ -232,17 +210,7 @@ mod tests { .with_connection(conn_id, conn_end_prefix), msg: ConnectionMsg::ConnectionOpenAck(Box::new(msg_ack)), want_pass: false, - match_error: - Box::new(move |e| { - match e.detail() { - error::ErrorDetail::ConsensusStateVerificationFailure(e) => { - assert_eq!(e.height, proof_height) - } - _ => { - panic!("Expected ConsensusStateVerificationFailure error"); - } - } - }), + error_kind: Some(Kind::ConsensusStateVerificationFailure(proof_height)) }, /* Test { @@ -291,7 +259,16 @@ mod tests { ); // Verify that the error kind matches - (test.match_error)(e); + 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() + ) + } } } } diff --git a/modules/src/ics03_connection/handler/conn_open_confirm.rs b/modules/src/ics03_connection/handler/conn_open_confirm.rs index 5d3ba0c1fe..664b826ef4 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; +use crate::ics03_connection::error::{Error, Kind}; use crate::ics03_connection::events::Attributes; use crate::ics03_connection::handler::verify::verify_proofs; use crate::ics03_connection::handler::{ConnectionIdState, ConnectionResult}; @@ -22,14 +22,18 @@ 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(Error::connection_mismatch(msg.connection_id().clone())) + Err(Into::::into(Kind::ConnectionMismatch( + msg.connection_id().clone(), + ))) } else { Ok(old_conn_end) } } None => { // No connection end exists for this conn. identifier. Impossible to continue handshake. - Err(Error::uninitialized_connection(msg.connection_id().clone())) + Err(Into::::into(Kind::UninitializedConnection( + 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 ceb583a072..ee9fc12b7a 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; +use crate::ics03_connection::error::{Error, Kind}; 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(Error::missing_client(msg.client_id().clone())); + return Err(Kind::MissingClient(msg.client_id().clone()).into()); } 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 ed59f58427..fe05fd6b3a 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; +use crate::ics03_connection::error::{Error, Kind}; 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(|| Error::connection_not_found(prev_id.clone()))?; + .ok_or_else(|| Kind::ConnectionNotFound(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,7 +42,9 @@ pub(crate) fn process( Ok((old_connection_end, prev_id.clone())) } else { // A ConnectionEnd already exists and validation failed. - Err(Error::connection_mismatch(prev_id.clone())) + Err(Into::::into(Kind::ConnectionMismatch( + prev_id.clone(), + ))) } } // No prev. connection id was supplied, create a new connection end and conn id. @@ -91,7 +93,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_else(Error::no_common_version)?, + .ok_or(Kind::NoCommonVersion)?, ); 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 2e287e6d2d..217f6239c0 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; +use crate::ics03_connection::error::{Error, Kind}; 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_else(Error::null_client_proof)?, + .ok_or(Kind::NullClientProof)?, )?; } @@ -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(|| Error::missing_client(connection_end.client_id().clone()))?; + .ok_or_else(|| Kind::MissingClient(connection_end.client_id().clone()))?; // The client must not be frozen. if client_state.is_frozen() { - return Err(Error::frozen_client(connection_end.client_id().clone())); + return Err(Kind::FrozenClient(connection_end.client_id().clone()).into()); } // The client must have the consensus state for the height where this proof was created. @@ -78,10 +78,11 @@ pub fn verify_connection_proof( .client_consensus_state(connection_end.client_id(), proof_height) .is_none() { - return Err(Error::missing_client_consensus_state( + return Err(Kind::MissingClientConsensusState( proof_height, connection_end.client_id().clone(), - )); + ) + .into()); } let client_def = AnyClient::from_client_type(client_state.client_type()); @@ -89,7 +90,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. - client_def + Ok(client_def .verify_connection_state( &client_state, proof_height, @@ -98,7 +99,7 @@ pub fn verify_connection_proof( connection_end.counterparty().connection_id(), expected_conn, ) - .map_err(Error::verify_connection_state) + .map_err(|_| Kind::InvalidProof)?) } /// Verifies the client `proof` from a connection handshake message, typically from a @@ -118,21 +119,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(|| Error::missing_client(connection_end.client_id().clone()))?; + .ok_or_else(|| Kind::MissingClient(connection_end.client_id().clone()))?; if client_state.is_frozen() { - return Err(Error::frozen_client(connection_end.client_id().clone())); + return Err(Kind::FrozenClient(connection_end.client_id().clone()).into()); } let consensus_state = ctx .client_consensus_state(connection_end.client_id(), proof_height) .ok_or_else(|| { - Error::missing_client_consensus_state(proof_height, connection_end.client_id().clone()) + Kind::MissingClientConsensusState(proof_height, connection_end.client_id().clone()) })?; let client_def = AnyClient::from_client_type(client_state.client_type()); - client_def + Ok(client_def .verify_client_full_state( &client_state, proof_height, @@ -143,8 +144,9 @@ pub fn verify_client_proof( &expected_client_state, ) .map_err(|e| { - Error::client_state_verification_failure(connection_end.client_id().clone(), e) - }) + Kind::ClientStateVerificationFailure(connection_end.client_id().clone()) + .context(e.to_string()) + })?) } pub fn verify_consensus_proof( @@ -156,20 +158,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(|| Error::missing_client(connection_end.client_id().clone()))?; + .ok_or_else(|| Kind::MissingClient(connection_end.client_id().clone()))?; if client_state.is_frozen() { - return Err(Error::frozen_client(connection_end.client_id().clone())); + return Err(Kind::FrozenClient(connection_end.client_id().clone()).into()); } // Fetch the expected consensus state from the historical (local) header data. let expected_consensus = ctx .host_consensus_state(proof.height()) - .ok_or_else(|| Error::missing_local_consensus_state(proof.height()))?; + .ok_or_else(|| Kind::MissingLocalConsensusState.context(proof.height().to_string()))?; let client = AnyClient::from_client_type(client_state.client_type()); - client + Ok(client .verify_client_consensus_state( &client_state, proof_height, @@ -179,7 +181,9 @@ pub fn verify_consensus_proof( proof.height(), &expected_consensus, ) - .map_err(|e| Error::consensus_state_verification_failure(proof.height(), e)) + .map_err(|e| { + Kind::ConsensusStateVerificationFailure(proof.height()).context(e.to_string()) + })?) } /// Checks that `claimed_height` is within normal bounds, i.e., fresh enough so that the chain has @@ -190,18 +194,12 @@ 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(Error::invalid_consensus_height( - claimed_height, - ctx.host_current_height(), - )); + return Err(Kind::InvalidConsensusHeight(claimed_height, ctx.host_current_height()).into()); } if claimed_height < ctx.host_oldest_height() { // Fail if the consensus height is too old (has been pruned). - return Err(Error::stale_consensus_height( - claimed_height, - ctx.host_oldest_height(), - )); + return Err(Kind::StaleConsensusHeight(claimed_height, ctx.host_oldest_height()).into()); } // 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 e48f5be1c0..41868d9916 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; +use crate::ics03_connection::error::{Error, Kind}; use crate::ics03_connection::version::Version; use crate::ics23_commitment::commitment::CommitmentProofBytes; use crate::ics24_host::identifier::ConnectionId; @@ -79,20 +79,22 @@ impl Msg for MsgConnectionOpenAck { impl Protobuf for MsgConnectionOpenAck {} impl TryFrom for MsgConnectionOpenAck { - type Error = Error; + type Error = anomaly::Error; fn try_from(msg: RawMsgConnectionOpenAck) -> Result { let consensus_height = msg .consensus_height - .ok_or_else(Error::missing_consensus_height)? - .into(); + .ok_or(Kind::MissingConsensusHeight)? + .try_into() // Cast from the raw height type into the domain type. + .map_err(|e| Kind::InvalidProof.context(e))?; let consensus_proof_obj = ConsensusProof::new(msg.proof_consensus.into(), consensus_height) - .map_err(Error::invalid_proof)?; + .map_err(|e| Kind::InvalidProof.context(e))?; let proof_height = msg .proof_height - .ok_or_else(Error::missing_proof_height)? - .into(); + .ok_or(Kind::MissingProofHeight)? + .try_into() + .map_err(|e| Kind::InvalidProof.context(e))?; let client_proof = Some(msg.proof_client) .filter(|x| !x.is_empty()) @@ -102,17 +104,21 @@ impl TryFrom for MsgConnectionOpenAck { connection_id: msg .connection_id .parse() - .map_err(Error::invalid_identifier)?, + .map_err(|e| Kind::IdentifierError.context(e))?, counterparty_connection_id: msg .counterparty_connection_id .parse() - .map_err(Error::invalid_identifier)?, + .map_err(|e| Kind::IdentifierError.context(e))?, client_state: msg .client_state .map(AnyClientState::try_from) .transpose() - .map_err(Error::ics02_client)?, - version: msg.version.ok_or_else(Error::empty_versions)?.try_into()?, + .map_err(|e| Kind::InvalidProof.context(e))?, + version: msg + .version + .ok_or(Kind::InvalidVersion)? + .try_into() + .map_err(|e| Kind::InvalidVersion.context(e))?, proofs: Proofs::new( msg.proof_try.into(), client_proof, @@ -120,7 +126,7 @@ impl TryFrom for MsgConnectionOpenAck { None, proof_height, ) - .map_err(Error::invalid_proof)?, + .map_err(|e| Kind::InvalidProof.context(e))?, 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 bdf2ae0279..0e8bfd6a04 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; +use std::convert::{TryFrom, TryInto}; use tendermint_proto::Protobuf; use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenConfirm as RawMsgConnectionOpenConfirm; -use crate::ics03_connection::error::Error; +use crate::ics03_connection::error::{Error, Kind}; 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 = Error; + type Error = anomaly::Error; fn try_from(msg: RawMsgConnectionOpenConfirm) -> Result { let proof_height = msg .proof_height - .ok_or_else(Error::missing_proof_height)? - .into(); - + .ok_or(Kind::MissingProofHeight)? + .try_into() // Cast from the raw height type into the domain type. + .map_err(|e| Kind::InvalidProof.context(e))?; Ok(Self { connection_id: msg .connection_id .parse() - .map_err(Error::invalid_identifier)?, + .map_err(|e| Kind::IdentifierError.context(e))?, proofs: Proofs::new(msg.proof_ack.into(), None, None, None, proof_height) - .map_err(Error::invalid_proof)?, + .map_err(|e| Kind::InvalidProof.context(e))?, 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 06a93e2c81..ae8eb8d515 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; +use crate::ics03_connection::error::{Error, Kind}; use crate::ics03_connection::version::Version; use crate::ics24_host::identifier::ClientId; use crate::signer::Signer; @@ -53,16 +53,23 @@ impl Msg for MsgConnectionOpenInit { impl Protobuf for MsgConnectionOpenInit {} impl TryFrom for MsgConnectionOpenInit { - type Error = Error; + type Error = anomaly::Error; fn try_from(msg: RawMsgConnectionOpenInit) -> Result { Ok(Self { - client_id: msg.client_id.parse().map_err(Error::invalid_identifier)?, + client_id: msg + .client_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, counterparty: msg .counterparty - .ok_or_else(Error::missing_counterparty)? + .ok_or(Kind::MissingCounterparty)? .try_into()?, - version: msg.version.ok_or_else(Error::empty_versions)?.try_into()?, + version: msg + .version + .ok_or(Kind::InvalidVersion)? + .try_into() + .map_err(|e| Kind::InvalidVersion.context(e))?, 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 6432948eb0..7bc9ee6b82 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; +use crate::ics03_connection::error::{Error, Kind}; use crate::ics03_connection::version::Version; use crate::ics23_commitment::commitment::CommitmentProofBytes; use crate::ics24_host::identifier::{ClientId, ConnectionId}; @@ -100,20 +100,22 @@ impl TryFrom for MsgConnectionOpenTry { .filter(|x| !x.is_empty()) .map(|v| FromStr::from_str(v.as_str())) .transpose() - .map_err(Error::invalid_identifier)?; + .map_err(|e| Kind::IdentifierError.context(e))?; let consensus_height = msg .consensus_height - .ok_or_else(Error::missing_consensus_height)? - .into(); + .ok_or(Kind::MissingConsensusHeight)? + .try_into() // Cast from the raw height type into the domain type. + .map_err(|e| Kind::InvalidProof.context(e))?; let consensus_proof_obj = ConsensusProof::new(msg.proof_consensus.into(), consensus_height) - .map_err(Error::invalid_proof)?; + .map_err(|e| Kind::InvalidProof.context(e))?; let proof_height = msg .proof_height - .ok_or_else(Error::missing_proof_height)? - .into(); + .ok_or(Kind::MissingProofHeight)? + .try_into() + .map_err(|e| Kind::InvalidProof.context(e))?; let client_proof = Some(msg.proof_client) .filter(|x| !x.is_empty()) @@ -123,23 +125,29 @@ impl TryFrom for MsgConnectionOpenTry { .counterparty_versions .into_iter() .map(Version::try_from) - .collect::, _>>()?; + .collect::, _>>() + .map_err(|e| Kind::InvalidVersion.context(e))?; if counterparty_versions.is_empty() { - return Err(Error::empty_versions()); + return Err(Kind::EmptyVersions + .context("empty counterparty versions in try message".to_string()) + .into()); } Ok(Self { previous_connection_id, - client_id: msg.client_id.parse().map_err(Error::invalid_identifier)?, + client_id: msg + .client_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, client_state: msg .client_state .map(AnyClientState::try_from) .transpose() - .map_err(Error::ics02_client)?, + .map_err(|e| Kind::InvalidProof.context(e))?, counterparty: msg .counterparty - .ok_or_else(Error::missing_counterparty)? + .ok_or(Kind::MissingCounterparty)? .try_into()?, counterparty_versions, proofs: Proofs::new( @@ -149,7 +157,7 @@ impl TryFrom for MsgConnectionOpenTry { None, proof_height, ) - .map_err(Error::invalid_proof)?, + .map_err(|e| Kind::InvalidProof.context(e))?, 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 8fa897ca9c..d24e8d2f32 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::Error; +use crate::ics03_connection::error::Kind; /// Stores the identifier and the features supported by a version #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -26,14 +26,18 @@ impl Version { impl Protobuf for Version {} impl TryFrom for Version { - type Error = Error; + type Error = anomaly::Error; fn try_from(value: RawVersion) -> Result { if value.identifier.trim().is_empty() { - return Err(Error::empty_versions()); + return Err(Kind::InvalidVersion + .context("empty version string".to_string()) + .into()); } for feature in value.features.iter() { if feature.trim().is_empty() { - return Err(Error::empty_features()); + return Err(Kind::InvalidVersion + .context("empty feature string".to_string()) + .into()); } } Ok(Version { diff --git a/modules/src/ics04_channel/channel.rs b/modules/src/ics04_channel/channel.rs index 1391c45d01..f3370e5db5 100644 --- a/modules/src/ics04_channel/channel.rs +++ b/modules/src/ics04_channel/channel.rs @@ -2,6 +2,7 @@ use std::convert::{TryFrom, TryInto}; use std::fmt; use std::str::FromStr; +use anomaly::fail; use serde::{Deserialize, Serialize}; use tendermint_proto::Protobuf; @@ -12,7 +13,10 @@ use ibc_proto::ibc::core::channel::v1::{ use crate::events::IbcEventType; use crate::ics02_client::height::Height; -use crate::ics04_channel::{error::Error, packet::Sequence}; +use crate::ics04_channel::{ + error::{self, Error, Kind}, + packet::Sequence, +}; use crate::ics24_host::identifier::{ChannelId, ConnectionId, PortId}; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -35,7 +39,7 @@ impl IdentifiedChannelEnd { impl Protobuf for IdentifiedChannelEnd {} impl TryFrom for IdentifiedChannelEnd { - type Error = Error; + type Error = anomaly::Error; fn try_from(value: RawIdentifiedChannel) -> Result { let raw_channel_end = RawChannel { @@ -47,8 +51,11 @@ impl TryFrom for IdentifiedChannelEnd { }; Ok(IdentifiedChannelEnd { - port_id: value.port_id.parse().map_err(Error::identifier)?, - channel_id: value.channel_id.parse().map_err(Error::identifier)?, + port_id: value.port_id.parse().map_err(|_| Kind::IdentifierError)?, + channel_id: value + .channel_id + .parse() + .map_err(|_| Kind::IdentifierError)?, channel_end: raw_channel_end.try_into()?, }) } @@ -97,7 +104,7 @@ impl Default for ChannelEnd { impl Protobuf for ChannelEnd {} impl TryFrom for ChannelEnd { - type Error = Error; + type Error = anomaly::Error; fn try_from(value: RawChannel) -> Result { let chan_state: State = State::from_i32(value.state)?; @@ -111,7 +118,7 @@ impl TryFrom for ChannelEnd { // Assemble the 'remote' attribute of the Channel, which represents the Counterparty. let remote = value .counterparty - .ok_or_else(Error::missing_counterparty)? + .ok_or(Kind::MissingCounterparty)? .try_into()?; // Parse each item in connection_hops into a ConnectionId. @@ -120,7 +127,7 @@ impl TryFrom for ChannelEnd { .into_iter() .map(|conn_id| ConnectionId::from_str(conn_id.as_str())) .collect::, _>>() - .map_err(Error::identifier)?; + .map_err(|e| Kind::IdentifierError.context(e))?; let version = validate_version(value.version)?; @@ -208,13 +215,14 @@ impl ChannelEnd { pub fn validate_basic(&self) -> Result<(), Error> { if self.connection_hops.len() != 1 { - return Err(Error::invalid_connection_hops_length( - 1, - self.connection_hops.len(), - )); + return Err( + Kind::InvalidConnectionHopsLength(1, self.connection_hops.len()) + .context("validate channel") + .into(), + ); } if self.version().trim() == "" { - return Err(Error::empty_version()); + return Err(Kind::InvalidVersion.context("empty version string").into()); } self.counterparty().validate_basic() } @@ -282,16 +290,19 @@ impl Counterparty { impl Protobuf for Counterparty {} impl TryFrom for Counterparty { - type Error = Error; + type Error = anomaly::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(Error::identifier)?; + .map_err(|e| Kind::IdentifierError.context(e))?; Ok(Counterparty::new( - value.port_id.parse().map_err(Error::identifier)?, + value + .port_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, channel_id, )) } @@ -343,7 +354,7 @@ impl Order { 0 => Ok(Self::None), 1 => Ok(Self::Unordered), 2 => Ok(Self::Ordered), - _ => Err(Error::unknown_order_type(nr.to_string())), + _ => fail!(error::Kind::UnknownOrderType, nr), } } } @@ -356,7 +367,7 @@ impl FromStr for Order { "uninitialized" => Ok(Self::None), "unordered" => Ok(Self::Unordered), "ordered" => Ok(Self::Ordered), - _ => Err(Error::unknown_order_type(s.to_string())), + _ => fail!(error::Kind::UnknownOrderType, s), } } } @@ -382,7 +393,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), @@ -390,7 +401,7 @@ impl State { 2 => Ok(Self::TryOpen), 3 => Ok(Self::Open), 4 => Ok(Self::Closed), - _ => Err(Error::unknown_state(s)), + _ => fail!(error::Kind::UnknownState, s), } } diff --git a/modules/src/ics04_channel/error.rs b/modules/src/ics04_channel/error.rs index cfe6ab3f2b..c7c48cca20 100644 --- a/modules/src/ics04_channel/error.rs +++ b/modules/src/ics04_channel/error.rs @@ -1,321 +1,182 @@ +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::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" }, +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), + + #[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 Error { - pub fn chan_open_confirm_proof_verification(e: Error) -> Error { - e.add_trace(&"Handshake proof verification fails at ChannelOpenConfirm") +impl Kind { + pub fn context(self, source: impl Into) -> Context { + Context::new(self, Some(source.into())) } } diff --git a/modules/src/ics04_channel/events.rs b/modules/src/ics04_channel/events.rs index f5f9e9aa45..22d09fecab 100644 --- a/modules/src/ics04_channel/events.rs +++ b/modules/src/ics04_channel/events.rs @@ -1,10 +1,12 @@ //! Types for the IBC events emitted from Tendermint Websocket by the channels module. -use crate::events::{extract_attribute, maybe_extract_attribute, Error, IbcEvent, RawObject}; +use crate::events::{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; +use std::convert::{TryFrom, TryInto}; /// Channel event types const OPEN_INIT_EVENT_TYPE: &str = "channel_open_init"; @@ -235,9 +237,19 @@ impl From for OpenInit { } impl TryFrom for OpenInit { - type Error = Error; + type Error = BoxError; fn try_from(obj: RawObject) -> Result { - Ok(OpenInit(extract_attributes(&obj, "channel_open_init")?)) + 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" + ), + })) } } @@ -275,9 +287,19 @@ impl From for OpenTry { } impl TryFrom for OpenTry { - type Error = Error; + type Error = BoxError; fn try_from(obj: RawObject) -> Result { - Ok(OpenTry(extract_attributes(&obj, "channel_open_try")?)) + 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" + ), + })) } } @@ -319,9 +341,19 @@ impl From for OpenAck { } impl TryFrom for OpenAck { - type Error = Error; + type Error = BoxError; fn try_from(obj: RawObject) -> Result { - Ok(OpenAck(extract_attributes(&obj, "channel_open_ack")?)) + 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" + ), + })) } } @@ -359,12 +391,19 @@ impl From for OpenConfirm { } impl TryFrom for OpenConfirm { - type Error = Error; + type Error = BoxError; fn try_from(obj: RawObject) -> Result { - Ok(OpenConfirm(extract_attributes( - &obj, - "channel_open_confirm", - )?)) + 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" + ), + })) } } @@ -414,9 +453,19 @@ impl From for CloseInit { } impl TryFrom for CloseInit { - type Error = Error; + type Error = BoxError; fn try_from(obj: RawObject) -> Result { - Ok(CloseInit(extract_attributes(&obj, "channel_close_init")?)) + 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" + ), + })) } } @@ -460,12 +509,19 @@ impl From for CloseConfirm { } impl TryFrom for CloseConfirm { - type Error = Error; + type Error = BoxError; fn try_from(obj: RawObject) -> Result { - Ok(CloseConfirm(extract_attributes( - &obj, - "channel_close_confirm", - )?)) + 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" + ), + })) } } @@ -475,41 +531,28 @@ 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 = Error; + type Error = BoxError; 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: 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)?, + 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"), data: vec![], - 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)?, + timeout_height: height_str.as_str().try_into()?, + timeout_timestamp: p_attribute!(obj, "packet_timeout_timestamp"), }) } } @@ -542,14 +585,12 @@ impl SendPacket { } impl TryFrom for SendPacket { - type Error = Error; + type Error = BoxError; fn try_from(obj: RawObject) -> Result { let height = obj.height; - let data_str: String = extract_attribute(&obj, &format!("{}.packet_data", obj.action))?; - + let data_str: String = p_attribute!(obj, "packet_data"); let mut packet = Packet::try_from(obj)?; packet.data = Vec::from(data_str.as_str().as_bytes()); - Ok(SendPacket { height, packet }) } } @@ -594,14 +635,12 @@ impl ReceivePacket { } impl TryFrom for ReceivePacket { - type Error = Error; + type Error = BoxError; fn try_from(obj: RawObject) -> Result { let height = obj.height; - let data_str: String = extract_attribute(&obj, &format!("{}.packet_data", obj.action))?; - + let data_str: String = p_attribute!(obj, "packet_data"); let mut packet = Packet::try_from(obj)?; packet.data = Vec::from(data_str.as_str().as_bytes()); - Ok(ReceivePacket { height, packet }) } } @@ -648,17 +687,13 @@ impl WriteAcknowledgement { } impl TryFrom for WriteAcknowledgement { - type Error = Error; + type Error = BoxError; fn try_from(obj: RawObject) -> Result { let height = obj.height; - - 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 data_str: String = p_attribute!(obj, "packet_data"); + let ack_str: String = p_attribute!(obj, "packet_ack"); let mut packet = Packet::try_from(obj)?; packet.data = Vec::from(data_str.as_str().as_bytes()); - Ok(WriteAcknowledgement { height, packet, @@ -705,7 +740,7 @@ impl AcknowledgePacket { } impl TryFrom for AcknowledgePacket { - type Error = Error; + type Error = BoxError; fn try_from(obj: RawObject) -> Result { let height = obj.height; let packet = Packet::try_from(obj)?; @@ -753,7 +788,7 @@ impl TimeoutPacket { } impl TryFrom for TimeoutPacket { - type Error = Error; + type Error = BoxError; fn try_from(obj: RawObject) -> Result { Ok(TimeoutPacket { height: obj.height, @@ -802,7 +837,7 @@ impl TimeoutOnClosePacket { } impl TryFrom for TimeoutOnClosePacket { - type Error = Error; + type Error = BoxError; 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 5c3d7f9f26..024b5c1334 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}; +use crate::ics04_channel::{context::ChannelReader, error::Error, error::Kind}; use crate::ics24_host::identifier::{ChannelId, PortId}; #[derive(Clone, Debug)] @@ -30,11 +30,12 @@ pub fn process( let source_channel_end = ctx .channel_end(&(packet.source_port.clone(), packet.source_channel.clone())) .ok_or_else(|| { - Error::channel_not_found(packet.source_port.clone(), packet.source_channel.clone()) + Kind::ChannelNotFound(packet.source_port.clone(), packet.source_channel.clone()) + .context(packet.source_channel.to_string()) })?; if !source_channel_end.state_matches(&State::Open) { - return Err(Error::channel_closed(packet.source_channel.clone())); + return Err(Kind::ChannelClosed(packet.source_channel.clone()).into()); } let _channel_cap = ctx.authenticated_capability(&packet.source_port)?; @@ -45,22 +46,21 @@ pub fn process( ); if !source_channel_end.counterparty_matches(&counterparty) { - return Err(Error::invalid_packet_counterparty( + return Err(Kind::InvalidPacketCounterparty( packet.destination_port.clone(), packet.destination_channel.clone(), - )); + ) + .into()); } let connection_end = ctx .connection_end(&source_channel_end.connection_hops()[0]) - .ok_or_else(|| { - Error::missing_connection(source_channel_end.connection_hops()[0].clone()) - })?; + .ok_or_else(|| Kind::MissingConnection(source_channel_end.connection_hops()[0].clone()))?; if !connection_end.state_matches(&ConnectionState::Open) { - return Err(Error::connection_not_open( - source_channel_end.connection_hops()[0].clone(), - )); + return Err( + Kind::ConnectionNotOpen(source_channel_end.connection_hops()[0].clone()).into(), + ); } let client_id = connection_end.client_id().clone(); @@ -72,7 +72,7 @@ pub fn process( packet.source_channel.clone(), packet.sequence, )) - .ok_or_else(|| Error::packet_commitment_not_found(packet.sequence))?; + .ok_or(Kind::PacketCommitmentNotFound(packet.sequence))?; let input = format!( "{:?},{:?},{:?}", @@ -80,7 +80,7 @@ pub fn process( ); if packet_commitment != ctx.hash(input) { - return Err(Error::incorrect_packet_commitment(packet.sequence)); + return Err(Kind::IncorrectPacketCommitment(packet.sequence).into()); } // Verify the acknowledgement proof @@ -95,13 +95,10 @@ 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_else(Error::missing_next_ack_seq)?; + .ok_or(Kind::MissingNextAckSeq)?; if packet.sequence != next_seq_ack { - return Err(Error::invalid_packet_sequence( - packet.sequence, - next_seq_ack, - )); + return Err(Kind::InvalidPacketSequence(packet.sequence, next_seq_ack).into()); } 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 c1ce1c3a69..ba4800fd5a 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; +use crate::ics04_channel::error::{Error, Kind}; 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(|| Error::channel_not_found(msg.port_id.clone(), msg.channel_id().clone()))?; + .ok_or_else(|| Kind::ChannelNotFound(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(Error::channel_closed(msg.channel_id().clone())); + return Err(Kind::ChannelClosed(msg.channel_id().clone()).into()); } // Channel capabilities @@ -31,20 +31,15 @@ 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(Error::invalid_connection_hops_length( - 1, - channel_end.connection_hops().len(), - )); + return Err( + Kind::InvalidConnectionHopsLength(1, channel_end.connection_hops().len()).into(), + ); } - let conn = ctx .connection_end(&channel_end.connection_hops()[0]) - .ok_or_else(|| Error::missing_connection(channel_end.connection_hops()[0].clone()))?; - + .ok_or_else(|| Kind::MissingConnection(channel_end.connection_hops()[0].clone()))?; if !conn.state_matches(&ConnectionState::Open) { - return Err(Error::connection_not_open( - channel_end.connection_hops()[0].clone(), - )); + return Err(Kind::ConnectionNotOpen(channel_end.connection_hops()[0].clone()).into()); } // Proof verification in two steps: @@ -55,7 +50,7 @@ pub(crate) fn process( let counterparty = conn.counterparty(); let ccid = counterparty.connection_id().ok_or_else(|| { - Error::undefined_connection_counterparty(channel_end.connection_hops()[0].clone()) + Kind::UndefinedConnectionCounterparty(channel_end.connection_hops()[0].clone()) })?; let expected_connection_hops = vec![ccid.clone()]; @@ -74,7 +69,8 @@ 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 877ad88094..ee1c7a2cdd 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; +use crate::ics04_channel::error::{Error, Kind}; 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(|| Error::channel_not_found(msg.port_id.clone(), msg.channel_id().clone()))?; + .ok_or_else(|| Kind::ChannelNotFound(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(Error::invalid_channel_state( + return Err(Into::::into(Kind::InvalidChannelState( msg.channel_id().clone(), channel_end.state, - )); + ))); } // Channel capabilities @@ -33,20 +33,17 @@ 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(Error::invalid_connection_hops_length( - 1, - channel_end.connection_hops().len(), - )); + return Err( + Kind::InvalidConnectionHopsLength(1, channel_end.connection_hops().len()).into(), + ); } let conn = ctx .connection_end(&channel_end.connection_hops()[0]) - .ok_or_else(|| Error::missing_connection(channel_end.connection_hops()[0].clone()))?; + .ok_or_else(|| Kind::MissingConnection(channel_end.connection_hops()[0].clone()))?; if !conn.state_matches(&ConnectionState::Open) { - return Err(Error::connection_not_open( - channel_end.connection_hops()[0].clone(), - )); + return Err(Kind::ConnectionNotOpen(channel_end.connection_hops()[0].clone()).into()); } 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 1c5dd27ebf..58f08958cf 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; +use crate::ics04_channel::error::{Error, Kind}; use crate::ics04_channel::events::Attributes; use crate::ics04_channel::handler::verify::verify_channel_proofs; use crate::ics04_channel::handler::{ChannelIdState, ChannelResult}; @@ -19,14 +19,11 @@ 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(|| Error::channel_not_found(msg.port_id.clone(), msg.channel_id().clone()))?; + .ok_or_else(|| Kind::ChannelNotFound(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(Error::invalid_channel_state( - msg.channel_id().clone(), - channel_end.state, - )); + return Err(Kind::InvalidChannelState(msg.channel_id().clone(), channel_end.state).into()); } // Channel capabilities @@ -35,20 +32,17 @@ 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(Error::invalid_connection_hops_length( - 1, - channel_end.connection_hops().len(), - )); + return Err( + Kind::InvalidConnectionHopsLength(1, channel_end.connection_hops().len()).into(), + ); } let conn = ctx .connection_end(&channel_end.connection_hops()[0]) - .ok_or_else(|| Error::missing_connection(channel_end.connection_hops()[0].clone()))?; + .ok_or_else(|| Kind::MissingConnection(channel_end.connection_hops()[0].clone()))?; if !conn.state_matches(&ConnectionState::Open) { - return Err(Error::connection_not_open( - channel_end.connection_hops()[0].clone(), - )); + return Err(Kind::ConnectionNotOpen(channel_end.connection_hops()[0].clone()).into()); } // Proof verification in two steps: @@ -59,7 +53,7 @@ pub(crate) fn process( let counterparty = conn.counterparty(); let ccid = counterparty.connection_id().ok_or_else(|| { - Error::undefined_connection_counterparty(channel_end.connection_hops()[0].clone()) + Kind::UndefinedConnectionCounterparty(channel_end.connection_hops()[0].clone()) })?; let expected_connection_hops = vec![ccid.clone()]; @@ -78,7 +72,8 @@ 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 61b19c5f24..d574fc7cd7 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; +use crate::ics04_channel::error::{Error, Kind}; use crate::ics04_channel::events::Attributes; use crate::ics04_channel::handler::verify::verify_channel_proofs; use crate::ics04_channel::handler::{ChannelIdState, ChannelResult}; @@ -19,14 +19,11 @@ 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(|| Error::channel_not_found(msg.port_id.clone(), msg.channel_id().clone()))?; + .ok_or_else(|| Kind::ChannelNotFound(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(Error::invalid_channel_state( - msg.channel_id().clone(), - channel_end.state, - )); + return Err(Kind::InvalidChannelState(msg.channel_id().clone(), channel_end.state).into()); } // Channel capabilities @@ -34,20 +31,17 @@ 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(Error::invalid_connection_hops_length( - 1, - channel_end.connection_hops().len(), - )); + return Err( + Kind::InvalidConnectionHopsLength(1, channel_end.connection_hops().len()).into(), + ); } let conn = ctx .connection_end(&channel_end.connection_hops()[0]) - .ok_or_else(|| Error::missing_connection(channel_end.connection_hops()[0].clone()))?; + .ok_or_else(|| Kind::MissingConnection(channel_end.connection_hops()[0].clone()))?; if !conn.state_matches(&ConnectionState::Open) { - return Err(Error::connection_not_open( - channel_end.connection_hops()[0].clone(), - )); + return Err(Kind::ConnectionNotOpen(channel_end.connection_hops()[0].clone()).into()); } // Proof verification in two steps: @@ -58,7 +52,7 @@ pub(crate) fn process( let connection_counterparty = conn.counterparty(); let ccid = connection_counterparty.connection_id().ok_or_else(|| { - Error::undefined_connection_counterparty(channel_end.connection_hops()[0].clone()) + Kind::UndefinedConnectionCounterparty(channel_end.connection_hops()[0].clone()) })?; let expected_connection_hops = vec![ccid.clone()]; @@ -78,7 +72,7 @@ pub(crate) fn process( &expected_channel_end, msg.proofs(), ) - .map_err(Error::chan_open_confirm_proof_verification)?; + .map_err(|e| Kind::ChanOpenConfirmProofVerification.context(e))?; 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 c4507d6b84..64544f244d 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; +use crate::ics04_channel::error::{Error, Kind}; use crate::ics04_channel::events::Attributes; use crate::ics04_channel::handler::{ChannelIdState, ChannelResult}; use crate::ics04_channel::msgs::chan_open_init::MsgChannelOpenInit; @@ -20,32 +20,31 @@ pub(crate) fn process( let channel_cap = ctx.authenticated_capability(&msg.port_id().clone())?; if msg.channel().connection_hops().len() != 1 { - return Err(Error::invalid_connection_hops_length( - 1, - msg.channel().connection_hops().len(), - )); + return Err( + Kind::InvalidConnectionHopsLength(1, msg.channel().connection_hops().len()).into(), + ); } // 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(|| Error::missing_connection(msg.channel().connection_hops()[0].clone()))?; + .ok_or_else(|| Kind::MissingConnection(msg.channel().connection_hops()[0].clone()))?; let get_versions = conn.versions(); let version = match get_versions.as_slice() { [version] => version, - _ => return Err(Error::invalid_version_length_connection()), + _ => return Err(Kind::InvalidVersionLengthConnection.into()), }; let channel_feature = msg.channel().ordering().to_string(); if !version.is_supported_feature(channel_feature) { - return Err(Error::channel_feature_not_suported_by_connection()); + return Err(Kind::ChannelFeatureNotSuportedByConnection.into()); } // TODO: Check that `version` is non empty but not necessary coherent if msg.channel().version().is_empty() { - return Err(Error::empty_version()); + return Err(Kind::InvalidVersion.into()); } // 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 76579f3f96..3417233498 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; +use crate::ics04_channel::error::{Error, Kind}; 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(|| Error::channel_not_found(msg.port_id.clone(), prev_id.clone()))?; + .ok_or_else(|| Kind::ChannelNotFound(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(Error::channel_mismatch(prev_id.clone())) + Err(Into::::into(Kind::ChannelMismatch(prev_id.clone()))) } } // No previous channel id was supplied. Create a new channel end & an identifier. @@ -64,39 +64,36 @@ pub(crate) fn process( // An IBC connection running on the local (host) chain should exist. if msg.channel.connection_hops().len() != 1 { - return Err(Error::invalid_connection_hops_length( - 1, - msg.channel.connection_hops().len(), - )); + return Err( + Kind::InvalidConnectionHopsLength(1, msg.channel.connection_hops().len()).into(), + ); } let connection_end_opt = ctx.connection_end(&msg.channel().connection_hops()[0]); let conn = connection_end_opt - .ok_or_else(|| Error::missing_connection(msg.channel().connection_hops()[0].clone()))?; + .ok_or_else(|| Kind::MissingConnection(msg.channel().connection_hops()[0].clone()))?; if !conn.state_matches(&ConnectionState::Open) { - return Err(Error::connection_not_open( - msg.channel.connection_hops()[0].clone(), - )); + return Err(Kind::ConnectionNotOpen(msg.channel.connection_hops()[0].clone()).into()); } let get_versions = conn.versions(); let version = match get_versions.as_slice() { [version] => version, - _ => return Err(Error::invalid_version_length_connection()), + _ => return Err(Kind::InvalidVersionLengthConnection.into()), }; let channel_feature = msg.channel().ordering().to_string(); if !version.is_supported_feature(channel_feature) { - return Err(Error::channel_feature_not_suported_by_connection()); + return Err(Kind::ChannelFeatureNotSuportedByConnection.into()); } // Channel capabilities let channel_cap = ctx.authenticated_capability(&msg.port_id().clone())?; if msg.channel().version().is_empty() { - return Err(Error::empty_version()); + return Err(Kind::InvalidVersion.into()); } // Proof verification in two steps: @@ -106,7 +103,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(|| { - Error::undefined_connection_counterparty(msg.channel().connection_hops()[0].clone()) + Kind::UndefinedConnectionCounterparty(msg.channel().connection_hops()[0].clone()) })?; let expected_connection_hops = vec![ccid.clone()]; @@ -126,7 +123,8 @@ pub(crate) fn process( &conn, &expected_channel_end, msg.proofs(), - )?; + ) + .map_err(|e| Kind::FailedChanneOpenTryVerification.context(e))?; output.log("success: channel open try "); @@ -167,7 +165,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; + use crate::ics04_channel::error::Kind; 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; @@ -184,7 +182,7 @@ mod tests { ctx: MockContext, msg: ChannelMsg, want_pass: bool, - match_error: Box, + expect_error_kind: Option, } // Some general-purpose variable to parametrize the messages and the context. @@ -257,36 +255,19 @@ mod tests { ctx: context.clone(), msg: ChannelMsg::ChannelOpenTry(msg.clone()), want_pass: false, - 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) - } - }) - }, + expect_error_kind: Some(Kind::ChannelNotFound( + msg.port_id.clone(), + chan_id.clone(), + )), }, 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, - 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) - } - }) - }, + expect_error_kind: Some(Kind::MissingConnection( + msg.channel().connection_hops()[0].clone(), + )), }, Test { name: "Processing fails because the port does not have a capability associated" @@ -296,17 +277,7 @@ mod tests { .with_connection(conn_id.clone(), conn_end.clone()), msg: ChannelMsg::ChannelOpenTry(msg_vanilla.clone()), want_pass: false, - 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) - } - }) - }, + expect_error_kind: Some(Kind::NoPortCapability(msg.port_id.clone())), }, Test { name: "Processing fails because of inconsistent version with preexisting channel" @@ -318,17 +289,7 @@ mod tests { .with_channel(msg.port_id.clone(), chan_id.clone(), incorrect_chan_end_ver), msg: ChannelMsg::ChannelOpenTry(msg.clone()), want_pass: false, - 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) - } - }) - }, + expect_error_kind: Some(Kind::ChannelMismatch(chan_id.clone())), }, Test { name: "Processing fails because of inconsistent connection hops".to_string(), @@ -343,17 +304,7 @@ mod tests { ), msg: ChannelMsg::ChannelOpenTry(msg.clone()), want_pass: false, - 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) - } - }) - }, + expect_error_kind: Some(Kind::ChannelMismatch(chan_id.clone())), }, Test { name: "Processing fails b/c the context has no client state".to_string(), @@ -368,12 +319,7 @@ mod tests { ), msg: ChannelMsg::ChannelOpenTry(msg.clone()), want_pass: false, - match_error: Box::new(|e| match e { - error::ErrorDetail::MissingClientState(_) => {} - _ => { - panic!("Expected MissingClientState, instead got {}", e) - } - }), + expect_error_kind: Some(Kind::FailedChanneOpenTryVerification), }, Test { name: "Processing is successful".to_string(), @@ -385,7 +331,7 @@ mod tests { .with_channel(msg.port_id.clone(), chan_id, correct_chan_end), msg: ChannelMsg::ChannelOpenTry(msg.clone()), want_pass: true, - match_error: Box::new(|_| {}), + expect_error_kind: None, }, Test { name: "Processing is successful against an empty context (no preexisting channel)" @@ -393,10 +339,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), + .with_port_capability(msg.port_id.clone()), msg: ChannelMsg::ChannelOpenTry(msg_vanilla), want_pass: true, - match_error: Box::new(|_| {}), + expect_error_kind: None, }, ] .into_iter() @@ -435,7 +381,17 @@ mod tests { e, ); - (test.match_error)(e.0); + // 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 + ); } } } diff --git a/modules/src/ics04_channel/handler/recv_packet.rs b/modules/src/ics04_channel/handler/recv_packet.rs index 5a407a075b..435d30570f 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; +use crate::ics04_channel::error::{Error, Kind}; 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,17 +32,18 @@ pub fn process(ctx: &dyn ChannelReader, msg: MsgRecvPacket) -> HandlerResult HandlerResult HandlerResult HandlerResult HandlerResult return Err(Error::packet_already_received(packet.sequence)), + Some(_receipt) => return Err(Kind::PacketAlreadyReceived(packet.sequence).into()), 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 3cfd5f39f5..5df43cc01c 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, packet::Packet}; +use crate::ics04_channel::{context::ChannelReader, error::Error, error::Kind, packet::Packet}; use crate::ics24_host::identifier::{ChannelId, PortId}; use crate::timestamp::{Expiry, Timestamp}; use crate::Height; @@ -27,11 +27,12 @@ pub fn send_packet(ctx: &dyn ChannelReader, packet: Packet) -> HandlerResult HandlerResult HandlerResult HandlerResult HandlerResult HandlerResult proof_height { - return Err(Error::packet_timeout_height_not_reached( - packet.timeout_height, - proof_height, - )); + return Err( + Kind::PacketTimeoutHeightNotReached(packet.timeout_height, proof_height).into(), + ); } let consensus_state = ctx .client_consensus_state(&client_id, proof_height) - .ok_or_else(|| Error::missing_client_consensus_state(client_id.clone(), proof_height))?; + .ok_or_else(|| Kind::MissingClientConsensusState(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(Error::packet_timeout_timestamp_not_reached( - packet_timestamp, - proof_timestamp, - )); + return Err( + Kind::PacketTimeoutTimestampNotReached(packet_timestamp, proof_timestamp).into(), + ); } //verify packet commitment @@ -89,7 +87,7 @@ pub fn process(ctx: &dyn ChannelReader, msg: MsgTimeout) -> HandlerResult HandlerResult Result<(), Error> { let client_state = ctx .client_state(&client_id) - .ok_or_else(|| Error::missing_client_state(client_id.clone()))?; + .ok_or_else(|| Kind::MissingClientState(client_id.clone()))?; // The client must not be frozen. if client_state.is_frozen() { - return Err(Error::frozen_client(client_id)); + return Err(Kind::FrozenClient(client_id).into()); } if ctx .client_consensus_state(&client_id, proofs.height()) .is_none() { - return Err(Error::missing_client_consensus_state( - client_id, - proofs.height(), - )); + return Err(Kind::MissingClientConsensusState(client_id, proofs.height()).into()); } let client_def = AnyClient::from_client_type(client_state.client_type()); @@ -90,7 +84,7 @@ pub fn verify_packet_recv_proofs( let commitment = ctx.hash(input); // Verify the proof for the packet against the chain store. - client_def + Ok(client_def .verify_packet_data( &client_state, proofs.height(), @@ -100,9 +94,7 @@ pub fn verify_packet_recv_proofs( &packet.sequence, commitment, ) - .map_err(|e| Error::packet_verification_failed(packet.sequence, e))?; - - Ok(()) + .map_err(|_| Kind::PacketVerificationFailed(packet.sequence))?) } /// Entry point for verifying all proofs bundled in an ICS4 packet ack message. @@ -115,17 +107,17 @@ pub fn verify_packet_acknowledgement_proofs( ) -> Result<(), Error> { let client_state = ctx .client_state(&client_id) - .ok_or_else(|| Error::missing_client_state(client_id.clone()))?; + .ok_or_else(|| Kind::MissingClientState(client_id.clone()))?; // The client must not be frozen. if client_state.is_frozen() { - return Err(Error::frozen_client(client_id)); + return Err(Kind::FrozenClient(client_id).into()); } let client_def = AnyClient::from_client_type(client_state.client_type()); // Verify the proof for the packet against the chain store. - client_def + Ok(client_def .verify_packet_acknowledgement( &client_state, proofs.height(), @@ -135,9 +127,7 @@ pub fn verify_packet_acknowledgement_proofs( &packet.sequence, acknowledgement, ) - .map_err(|e| Error::packet_verification_failed(packet.sequence, e))?; - - Ok(()) + .map_err(|_| Kind::PacketVerificationFailed(packet.sequence))?) } /// Entry point for verifying all timeout proofs. @@ -150,17 +140,17 @@ pub fn verify_next_sequence_recv( ) -> Result<(), Error> { let client_state = ctx .client_state(&client_id) - .ok_or_else(|| Error::missing_client_state(client_id.clone()))?; + .ok_or_else(|| Kind::MissingClientState(client_id.clone()))?; // The client must not be frozen. if client_state.is_frozen() { - return Err(Error::frozen_client(client_id)); + return Err(Kind::FrozenClient(client_id).into()); } let client_def = AnyClient::from_client_type(client_state.client_type()); // Verify the proof for the packet against the chain store. - client_def + Ok(client_def .verify_next_sequence_recv( &client_state, proofs.height(), @@ -169,9 +159,7 @@ pub fn verify_next_sequence_recv( &packet.destination_channel, &seq, ) - .map_err(|e| Error::packet_verification_failed(seq, e))?; - - Ok(()) + .map_err(|_| Kind::PacketVerificationFailed(seq))?) } pub fn verify_packet_receipt_absence( @@ -182,17 +170,17 @@ pub fn verify_packet_receipt_absence( ) -> Result<(), Error> { let client_state = ctx .client_state(&client_id) - .ok_or_else(|| Error::missing_client_state(client_id.clone()))?; + .ok_or_else(|| Kind::MissingClientState(client_id.clone()))?; // The client must not be frozen. if client_state.is_frozen() { - return Err(Error::frozen_client(client_id)); + return Err(Kind::FrozenClient(client_id).into()); } let client_def = AnyClient::from_client_type(client_state.client_type()); // Verify the proof for the packet against the chain store. - client_def + Ok(client_def .verify_packet_receipt_absence( &client_state, proofs.height(), @@ -201,7 +189,5 @@ pub fn verify_packet_receipt_absence( &packet.destination_channel, &packet.sequence, ) - .map_err(|e| Error::packet_verification_failed(packet.sequence, e))?; - - Ok(()) + .map_err(|_| Kind::PacketVerificationFailed(packet.sequence))?) } diff --git a/modules/src/ics04_channel/handler/write_acknowledgement.rs b/modules/src/ics04_channel/handler/write_acknowledgement.rs index 84095308d9..423c174c06 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}; +use crate::ics04_channel::{context::ChannelReader, error::Error, error::Kind}; use crate::ics24_host::identifier::{ChannelId, PortId}; use crate::{ events::IbcEvent, @@ -29,17 +29,16 @@ pub fn process( packet.destination_channel.clone(), )) .ok_or_else(|| { - Error::channel_not_found( + Kind::ChannelNotFound( packet.destination_port.clone(), packet.destination_channel.clone(), ) })?; if !dest_channel_end.state_matches(&State::Open) { - return Err(Error::invalid_channel_state( - packet.source_channel, - dest_channel_end.state, - )); + return Err( + Kind::InvalidChannelState(packet.source_channel, dest_channel_end.state).into(), + ); } let _channel_cap = ctx.authenticated_capability(&packet.destination_port)?; @@ -55,11 +54,11 @@ pub fn process( )) .is_some() { - return Err(Error::acknowledgement_exists(packet.sequence)); + return Err(Kind::AcknowledgementExists(packet.sequence).into()); } if ack.is_empty() { - return Err(Error::invalid_acknowledgement()); + return Err(Kind::InvalidAcknowledgement.into()); } let result = PacketResult::WriteAck(WriteAckPacketResult { diff --git a/modules/src/ics04_channel/msgs/acknowledgement.rs b/modules/src/ics04_channel/msgs/acknowledgement.rs index 3ed12d793b..668d899eb4 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; +use crate::ics04_channel::error::{Error, Kind}; 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 = Error; + type Error = anomaly::Error; fn try_from(raw_msg: RawMsgAcknowledgement) -> Result { let proofs = Proofs::new( @@ -73,16 +73,18 @@ impl TryFrom for MsgAcknowledgement { None, raw_msg .proof_height - .ok_or_else(Error::missing_height)? - .into(), + .ok_or(Kind::MissingHeight)? + .try_into() + .map_err(|e| Kind::InvalidProof.context(e))?, ) - .map_err(Error::invalid_proof)?; + .map_err(|e| Kind::InvalidProof.context(e))?; Ok(MsgAcknowledgement { packet: raw_msg .packet - .ok_or_else(Error::missing_packet)? - .try_into()?, + .ok_or(Kind::MissingPacket)? + .try_into() + .map_err(|e| Kind::InvalidPacket.context(e))?, 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 9aabe2c273..e4c83a61be 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; +use std::convert::{TryFrom, TryInto}; use tendermint_proto::Protobuf; use ibc_proto::ibc::core::channel::v1::MsgChannelCloseConfirm as RawMsgChannelCloseConfirm; -use crate::ics04_channel::error::Error; +use crate::ics04_channel::error::{Error, Kind}; 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 = Error; + type Error = anomaly::Error; fn try_from(raw_msg: RawMsgChannelCloseConfirm) -> Result { let proofs = Proofs::new( @@ -72,14 +72,21 @@ impl TryFrom for MsgChannelCloseConfirm { None, raw_msg .proof_height - .ok_or_else(Error::missing_height)? - .into(), + .ok_or(Kind::MissingHeight)? + .try_into() + .map_err(|e| Kind::InvalidProof.context(e))?, ) - .map_err(Error::invalid_proof)?; + .map_err(|e| Kind::InvalidProof.context(e))?; Ok(MsgChannelCloseConfirm { - port_id: raw_msg.port_id.parse().map_err(Error::identifier)?, - channel_id: raw_msg.channel_id.parse().map_err(Error::identifier)?, + 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))?, 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 6d718ea2be..2af36cfdaa 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; +use crate::ics04_channel::error::{Error, Kind}; use crate::ics24_host::identifier::{ChannelId, PortId}; use crate::signer::Signer; use crate::tx_msg::Msg; @@ -55,12 +55,18 @@ impl Msg for MsgChannelCloseInit { impl Protobuf for MsgChannelCloseInit {} impl TryFrom for MsgChannelCloseInit { - type Error = Error; + type Error = anomaly::Error; fn try_from(raw_msg: RawMsgChannelCloseInit) -> Result { Ok(MsgChannelCloseInit { - port_id: raw_msg.port_id.parse().map_err(Error::identifier)?, - channel_id: raw_msg.channel_id.parse().map_err(Error::identifier)?, + 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))?, 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 8822abad3d..436fd546cc 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; +use crate::ics04_channel::error::{Error, Kind}; 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; +use std::convert::{TryFrom, TryInto}; 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 = Error; + type Error = anomaly::Error; fn try_from(raw_msg: RawMsgChannelOpenAck) -> Result { let proofs = Proofs::new( @@ -91,18 +91,25 @@ impl TryFrom for MsgChannelOpenAck { None, raw_msg .proof_height - .ok_or_else(Error::missing_height)? - .into(), + .ok_or(Kind::MissingHeight)? + .try_into() + .map_err(|e| Kind::InvalidProof.context(e))?, ) - .map_err(Error::invalid_proof)?; + .map_err(|e| Kind::InvalidProof.context(e))?; Ok(MsgChannelOpenAck { - port_id: raw_msg.port_id.parse().map_err(Error::identifier)?, - channel_id: raw_msg.channel_id.parse().map_err(Error::identifier)?, + 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))?, counterparty_channel_id: raw_msg .counterparty_channel_id .parse() - .map_err(Error::identifier)?, + .map_err(|e| Kind::IdentifierError.context(e))?, 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 4c9fe464dc..95024446d0 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; +use crate::ics04_channel::error::{Error, Kind}; 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; +use std::convert::{TryFrom, TryInto}; 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 = Error; + type Error = anomaly::Error; fn try_from(raw_msg: RawMsgChannelOpenConfirm) -> Result { let proofs = Proofs::new( @@ -73,14 +73,21 @@ impl TryFrom for MsgChannelOpenConfirm { None, raw_msg .proof_height - .ok_or_else(Error::missing_height)? - .into(), + .ok_or(Kind::MissingHeight)? + .try_into() + .map_err(|e| Kind::InvalidProof.context(e))?, ) - .map_err(Error::invalid_proof)?; + .map_err(|e| Kind::InvalidProof.context(e))?; Ok(MsgChannelOpenConfirm { - port_id: raw_msg.port_id.parse().map_err(Error::identifier)?, - channel_id: raw_msg.channel_id.parse().map_err(Error::identifier)?, + 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))?, 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 7c9765837c..68afb9c4db 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; +use crate::ics04_channel::error::{Error, Kind}; 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 = Error; + type Error = anomaly::Error; fn try_from(raw_msg: RawMsgChannelOpenInit) -> Result { Ok(MsgChannelOpenInit { - port_id: raw_msg.port_id.parse().map_err(Error::identifier)?, - channel: raw_msg - .channel - .ok_or_else(Error::missing_channel)? - .try_into()?, + port_id: raw_msg + .port_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, + channel: raw_msg.channel.ok_or(Kind::MissingChannel)?.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 9046e35d46..816bc68725 100644 --- a/modules/src/ics04_channel/msgs/chan_open_try.rs +++ b/modules/src/ics04_channel/msgs/chan_open_try.rs @@ -1,6 +1,7 @@ use crate::ics04_channel::channel::{validate_version, ChannelEnd}; -use crate::ics04_channel::error::Error as ChannelError; +use crate::ics04_channel::error::{Error, Kind}; 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; @@ -64,7 +65,7 @@ impl MsgChannelOpenTry { } } impl Msg for MsgChannelOpenTry { - type ValidationError = ChannelError; + type ValidationError = Error; type Raw = RawMsgChannelOpenTry; fn route(&self) -> String { @@ -77,7 +78,7 @@ impl Msg for MsgChannelOpenTry { fn validate_basic(&self) -> Result<(), ValidationError> { match self.channel().counterparty().channel_id() { - None => Err(ValidationError::invalid_counterparty_channel_id()), + None => Err(ValidationKind::InvalidCounterpartyChannelId.into()), Some(_c) => Ok(()), } } @@ -86,7 +87,7 @@ impl Msg for MsgChannelOpenTry { impl Protobuf for MsgChannelOpenTry {} impl TryFrom for MsgChannelOpenTry { - type Error = ChannelError; + type Error = anomaly::Error; fn try_from(raw_msg: RawMsgChannelOpenTry) -> Result { let proofs = Proofs::new( @@ -96,33 +97,34 @@ impl TryFrom for MsgChannelOpenTry { None, raw_msg .proof_height - .ok_or_else(ChannelError::missing_height)? - .into(), + .ok_or(Kind::MissingHeight)? + .try_into() + .map_err(|e| Kind::InvalidProof.context(e))?, ) - .map_err(ChannelError::invalid_proof)?; + .map_err(|e| Kind::InvalidProof.context(e))?; 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(ChannelError::identifier)?; + .map_err(|e| Kind::IdentifierError.context(e))?; let msg = MsgChannelOpenTry { - port_id: raw_msg.port_id.parse().map_err(ChannelError::identifier)?, + port_id: raw_msg + .port_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, previous_channel_id, - channel: raw_msg - .channel - .ok_or_else(ChannelError::missing_channel)? - .try_into()?, + channel: raw_msg.channel.ok_or(Kind::MissingChannel)?.try_into()?, counterparty_version: validate_version(raw_msg.counterparty_version)?, proofs, signer: raw_msg.signer.into(), }; - msg.validate_basic() - .map_err(ChannelError::invalid_counterparty_channel_id)?; - - Ok(msg) + match msg.validate_basic() { + Err(_e) => Err(Kind::InvalidCounterpartyChannelId.into()), + Ok(()) => Ok(msg), + } } } diff --git a/modules/src/ics04_channel/msgs/recv_packet.rs b/modules/src/ics04_channel/msgs/recv_packet.rs index f5f952f2e1..b8adefd46f 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; +use crate::ics04_channel::error::{Error, Kind}; 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 = Error; + type Error = anomaly::Error; fn try_from(raw_msg: RawMsgRecvPacket) -> Result { let proofs = Proofs::new( @@ -58,16 +58,18 @@ impl TryFrom for MsgRecvPacket { None, raw_msg .proof_height - .ok_or_else(Error::missing_height)? - .into(), + .ok_or(Kind::MissingHeight)? + .try_into() + .map_err(|e| Kind::InvalidProof.context(e))?, ) - .map_err(Error::invalid_proof)?; + .map_err(|e| Kind::InvalidProof.context(e))?; Ok(MsgRecvPacket { packet: raw_msg .packet - .ok_or_else(Error::missing_packet)? - .try_into()?, + .ok_or(Kind::MissingPacket)? + .try_into() + .map_err(|e| Kind::InvalidPacket.context(e))?, 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 088bbc4445..ba43692aee 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; +use crate::ics04_channel::error::{Error, Kind}; 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 = Error; + type Error = anomaly::Error; fn try_from(raw_msg: RawMsgTimeout) -> Result { let proofs = Proofs::new( @@ -65,18 +65,20 @@ impl TryFrom for MsgTimeout { None, raw_msg .proof_height - .ok_or_else(Error::missing_height)? - .into(), + .ok_or(Kind::MissingHeight)? + .try_into() + .map_err(|e| Kind::InvalidProof.context(e))?, ) - .map_err(Error::invalid_proof)?; + .map_err(|e| Kind::InvalidProof.context(e))?; // TODO: Domain type verification for the next sequence: this should probably be > 0. Ok(MsgTimeout { packet: raw_msg .packet - .ok_or_else(Error::missing_packet)? - .try_into()?, + .ok_or(Kind::MissingPacket)? + .try_into() + .map_err(|e| Kind::InvalidPacket.context(e))?, 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 c181ab0772..ace86cb977 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; +use crate::ics04_channel::error::{Error, Kind}; 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 = Error; + type Error = anomaly::Error; fn try_from(raw_msg: RawMsgTimeoutOnClose) -> Result { let proofs = Proofs::new( @@ -65,18 +65,20 @@ impl TryFrom for MsgTimeoutOnClose { None, raw_msg .proof_height - .ok_or_else(Error::missing_height)? - .into(), + .ok_or(Kind::MissingHeight)? + .try_into() + .map_err(|e| Kind::InvalidProof.context(e))?, ) - .map_err(Error::invalid_proof)?; + .map_err(|e| Kind::InvalidProof.context(e))?; // TODO: Domain type verification for the next sequence: this should probably be > 0. Ok(MsgTimeoutOnClose { packet: raw_msg .packet - .ok_or_else(Error::missing_packet)? - .try_into()?, + .ok_or(Kind::MissingPacket)? + .try_into() + .map_err(|e| Kind::InvalidPacket.context(e))?, 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 9f32c5863e..672d777449 100644 --- a/modules/src/ics04_channel/packet.rs +++ b/modules/src/ics04_channel/packet.rs @@ -1,11 +1,11 @@ -use std::convert::TryFrom; +use std::convert::{TryFrom, TryInto}; use std::str::FromStr; use serde_derive::{Deserialize, Serialize}; use ibc_proto::ibc::core::channel::v1::Packet as RawPacket; -use crate::ics04_channel::error::Error; +use crate::ics04_channel::error::Kind; 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 = Error; + type Err = anomaly::Error; fn from_str(s: &str) -> Result { - Ok(Self::from(s.parse::().map_err(|e| { - Error::invalid_string_as_sequence(s.to_string(), e) + Ok(Self::from(s.parse::().map_err(|_e| { + Kind::InvalidStringAsSequence(s.to_string()) })?)) } } @@ -163,39 +163,46 @@ impl Default for Packet { } impl TryFrom for Packet { - type Error = Error; + type Error = anomaly::Error; fn try_from(raw_pkt: RawPacket) -> Result { if Sequence::from(raw_pkt.sequence).is_zero() { - return Err(Error::zero_packet_sequence()); + return Err(Kind::ZeroPacketSequence.into()); } let packet_timeout_height: Height = raw_pkt .timeout_height - .ok_or_else(Error::missing_height)? - .into(); + .ok_or(Kind::MissingHeight)? + .try_into() + .map_err(|e| Kind::InvalidTimeoutHeight.context(e))?; if packet_timeout_height.is_zero() && raw_pkt.timeout_timestamp == 0 { - return Err(Error::zero_packet_timeout()); + return Err(Kind::ZeroPacketTimeout.into()); } if raw_pkt.data.is_empty() { - return Err(Error::zero_packet_data()); + return Err(Kind::ZeroPacketData.into()); } let timeout_timestamp = Timestamp::from_nanoseconds(raw_pkt.timeout_timestamp) - .map_err(Error::invalid_packet_timestamp)?; + .map_err(|_| Kind::InvalidPacketTimestamp)?; Ok(Packet { sequence: Sequence::from(raw_pkt.sequence), - source_port: raw_pkt.source_port.parse().map_err(Error::identifier)?, - source_channel: raw_pkt.source_channel.parse().map_err(Error::identifier)?, + 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))?, destination_port: raw_pkt .destination_port .parse() - .map_err(Error::identifier)?, + .map_err(|e| Kind::IdentifierError.context(e))?, destination_channel: raw_pkt .destination_channel .parse() - .map_err(Error::identifier)?, + .map_err(|e| Kind::IdentifierError.context(e))?, 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 2356194a9c..29a1ff6238 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; +use crate::ics04_channel::error::{Error, Kind}; 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 = Error; + type Error = anomaly::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 { - Version::decode(s.as_bytes()).map_err(Error::invalid_version) + Ok(Version::decode(s.as_bytes()).map_err(|e| Kind::InvalidVersion.context(e))?) } } @@ -76,7 +76,8 @@ 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(Error::invalid_version)?; + let supported_version = + Version::decode(s.as_bytes()).map_err(|e| Kind::InvalidVersion.context(e))?; for c in counterparty_versions.iter() { let counterparty_version = Version::from_str(c.as_str())?; if supported_version.identifier != counterparty_version.identifier { @@ -88,14 +89,16 @@ pub fn pick_version( } intersection.sort_by(|a, b| a.identifier.cmp(&b.identifier)); if intersection.is_empty() { - return Err(Error::no_common_version()); + return Err(Kind::NoCommonVersion.into()); } Ok(intersection[0].to_string()) } pub fn validate_versions(versions: Vec) -> Result, Error> { if versions.is_empty() { - return Err(Error::empty_version()); + return Err(Kind::InvalidVersion + .context("no versions".to_string()) + .into()); } for version_str in versions.iter() { validate_version(version_str.clone())?; @@ -104,14 +107,19 @@ 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())?; + let version = + Version::from_str(raw_version.as_ref()).map_err(|e| Kind::InvalidVersion.context(e))?; if version.identifier.trim().is_empty() { - return Err(Error::empty_version()); + return Err(Kind::InvalidVersion + .context("empty version string".to_string()) + .into()); } for feature in version.features { if feature.trim().is_empty() { - return Err(Error::empty_version()); + return Err(Kind::InvalidVersion + .context("empty feature string".to_string()) + .into()); } } Ok(raw_version) diff --git a/modules/src/ics05_port/error.rs b/modules/src/ics05_port/error.rs index 9c94b0d6c5..51ae2bd614 100644 --- a/modules/src/ics05_port/error.rs +++ b/modules/src/ics05_port/error.rs @@ -1,8 +1,16 @@ -use flex_error::define_error; +use anomaly::{BoxError, Context}; +use thiserror::Error; -define_error! { - Error { - UnknownPort - | _ | { format_args!("port unknown") } +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())) } } diff --git a/modules/src/ics07_tendermint/client_def.rs b/modules/src/ics07_tendermint/client_def.rs index 8646574a91..5cea1b502e 100644 --- a/modules/src/ics07_tendermint/client_def.rs +++ b/modules/src/ics07_tendermint/client_def.rs @@ -3,17 +3,25 @@ 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::ics02_client::client_type::ClientType; +use crate::ics02_client::context::ClientReader; +use crate::ics02_client::error::Kind; use crate::ics03_connection::connection::ConnectionEnd; use crate::ics04_channel::channel::ChannelEnd; use crate::ics04_channel::packet::Sequence; use crate::ics07_tendermint::client_state::ClientState; use crate::ics07_tendermint::consensus_state::ConsensusState; use crate::ics07_tendermint::header::Header; +use crate::ics07_tendermint::header::{monotonicity_checks, voting_power_in}; + use crate::ics23_commitment::commitment::{CommitmentPrefix, CommitmentProofBytes, CommitmentRoot}; use crate::ics24_host::identifier::ConnectionId; use crate::ics24_host::identifier::{ChannelId, ClientId, PortId}; use crate::Height; +use tendermint::trust_threshold::TrustThresholdFraction; +use tendermint::validator::Set; + +use crate::downcast; #[derive(Clone, Debug, PartialEq, Eq)] pub struct TendermintClient; @@ -25,17 +33,114 @@ impl ClientDef for TendermintClient { fn check_header_and_update_state( &self, + ctx: &dyn ClientReader, + client_id: ClientId, client_state: Self::ClientState, header: Self::Header, - ) -> Result<(Self::ClientState, Self::ConsensusState), Error> { - if client_state.latest_height() >= header.height() { - return Err(Error::low_header_height( - header.height(), - client_state.latest_height(), - )); - } + ) -> Result<(Self::ClientState, Self::ConsensusState), Box> { + // check if a consensus state is already installed; if so it should + // match the untrusted header. + + if let Some(cs) = ctx.consensus_state(&client_id, header.height()) { + //could the header height be zero ? + let consensus_state = downcast!( + cs => AnyConsensusState::Tendermint + ) + .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Tendermint))?; + + if consensus_state != ConsensusState::from(header.clone()) { + //freeze the client and return the installed consensus state + return Ok(( + client_state.with_set_frozen(header.height()), + consensus_state, + )); + } else { + return Ok((client_state, consensus_state)); + } + }; + + let latest_consensus_state = + match ctx.consensus_state(&client_id, client_state.latest_height) { + //could the header height be zero ? + Some(cs) => downcast!( + cs => AnyConsensusState::Tendermint + ) + .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Tendermint))?, + None => { + return Err(Kind::ConsensusStateNotFound( + client_id.clone(), + client_state.latest_height, + ) + .into()); + } + }; + + monotonicity_checks(latest_consensus_state, header.clone(), client_state.clone())?; - // TODO: Additional verifications should be implemented here. + // check that the versions of the client state and the header match + if client_state.latest_height.revision_number != header.height().revision_number { + return Err(Kind::MismatchedRevisions( + client_state.latest_height.revision_number, + header.height().revision_number, + ) + .into()); + }; + + let trusted_consensus_state = match ctx.consensus_state(&client_id, header.trusted_height) { + Some(ts) => downcast!( + ts => AnyConsensusState::Tendermint + ) + .ok_or_else(|| Kind::ClientArgsTypeMismatch(ClientType::Tendermint))?, + None => { + return Err(Kind::ConsensusStateNotFound(client_id, header.trusted_height).into()) + } + }; + + if header.height() == header.trusted_height.increment() { + //adjacent + + // check that the header's trusted validator set is + // the next_validator_set of the trusted consensus state + if Set::hash(&header.validator_set) != trusted_consensus_state.next_validators_hash { + return Err(Kind::InvalidValidatorSet( + trusted_consensus_state.next_validators_hash, + Set::hash(&header.validator_set), + ) + .into()); + } + + // check that the validators that sign the commit of the untrusted header + // have 2/3 of the voting power of the current validator set. + if let Err(e) = voting_power_in( + &header.signed_header, + &header.validator_set, + TrustThresholdFraction::TWO_THIRDS, + ) { + return Err(Kind::InsufficientVotingPower(e.to_string()).into()); + } + } else { + //Non-adjacent + + //check that a subset of the trusted validator set, having 1/3 of the voting power + //signes the commit of the untrusted header + if let Err(e) = voting_power_in( + &header.signed_header, + &header.trusted_validator_set, + TrustThresholdFraction::default(), + ) { + return Err(Kind::NotEnoughTrustedValsSigned(e.to_string()).into()); + }; + + // check that the validators that sign the commit of the untrusted header + // have 2/3 of the voting power of the current validator set. + if let Err(e) = voting_power_in( + &header.signed_header, + &header.validator_set, + TrustThresholdFraction::TWO_THIRDS, + ) { + return Err(Kind::InsufficientVotingPower(e.to_string()).into()); + }; + } Ok(( client_state.with_header(header.clone()), @@ -52,7 +157,7 @@ impl ClientDef for TendermintClient { _client_id: &ClientId, _consensus_height: Height, _expected_consensus_state: &AnyConsensusState, - ) -> Result<(), Error> { + ) -> Result<(), Box> { todo!() } @@ -64,7 +169,7 @@ impl ClientDef for TendermintClient { _proof: &CommitmentProofBytes, _connection_id: Option<&ConnectionId>, _expected_connection_end: &ConnectionEnd, - ) -> Result<(), Error> { + ) -> Result<(), Box> { todo!() } @@ -77,7 +182,7 @@ impl ClientDef for TendermintClient { _port_id: &PortId, _channel_id: &ChannelId, _expected_channel_end: &ChannelEnd, - ) -> Result<(), Error> { + ) -> Result<(), Box> { todo!() } @@ -90,7 +195,7 @@ impl ClientDef for TendermintClient { _client_id: &ClientId, _proof: &CommitmentProofBytes, _expected_client_state: &AnyClientState, - ) -> Result<(), Error> { + ) -> Result<(), Box> { unimplemented!() } @@ -103,7 +208,7 @@ impl ClientDef for TendermintClient { _channel_id: &ChannelId, _seq: &Sequence, _data: String, - ) -> Result<(), Error> { + ) -> Result<(), Box> { todo!() } @@ -116,7 +221,7 @@ impl ClientDef for TendermintClient { _channel_id: &ChannelId, _seq: &Sequence, _data: Vec, - ) -> Result<(), Error> { + ) -> Result<(), Box> { todo!() } @@ -128,7 +233,7 @@ impl ClientDef for TendermintClient { _port_id: &PortId, _channel_id: &ChannelId, _seq: &Sequence, - ) -> Result<(), Error> { + ) -> Result<(), Box> { todo!() } @@ -140,7 +245,7 @@ impl ClientDef for TendermintClient { _port_id: &PortId, _channel_id: &ChannelId, _seq: &Sequence, - ) -> Result<(), Error> { + ) -> Result<(), Box> { todo!() } @@ -150,7 +255,7 @@ impl ClientDef for TendermintClient { _consensus_state: &Self::ConsensusState, _proof_upgrade_client: MerkleProof, _proof_upgrade_consensus_state: MerkleProof, - ) -> Result<(Self::ClientState, Self::ConsensusState), Error> { + ) -> Result<(Self::ClientState, Self::ConsensusState), Box> { todo!() } } diff --git a/modules/src/ics07_tendermint/client_state.rs b/modules/src/ics07_tendermint/client_state.rs index f2da51299f..c2e150f921 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; +use crate::ics07_tendermint::error::{Error, Kind}; 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(Error::invalid_trusting_period( - "ClientState trusting period must be greater than zero".to_string(), - )); + return Err(Kind::InvalidTrustingPeriod + .context("ClientState trusting period must be greater than zero") + .into()); } if unbonding_period <= Duration::new(0, 0) { - return Err(Error::invalid_unbounding_period( - "ClientState unbonding period must be greater than zero".to_string(), - )); + return Err(Kind::InvalidUnboundingPeriod + .context("ClientState unbonding period must be greater than zero") + .into()); } if trusting_period >= unbonding_period { - return Err(Error::invalid_unbounding_period( - "ClientState trusting period must be smaller than unbonding period".to_string(), - )); + return Err(Kind::InvalidUnboundingPeriod + .context("ClientState trusting period must be smaller than unbonding period") + .into()); } // Basic validation for the frozen_height parameter. if !frozen_height.is_zero() { - return Err(Error::validation( - "ClientState cannot be frozen at creation time".to_string(), - )); + return Err(Kind::ValidationError + .context("ClientState cannot be frozen at creation time") + .into()); } // Basic validation for the latest_height parameter. if latest_height <= Height::zero() { - return Err(Error::validation( - "ClientState latest height cannot be smaller or equal than zero".to_string(), - )); + return Err(Kind::ValidationError + .context("ClientState latest height cannot be smaller or equal than zero") + .into()); } Ok(Self { @@ -111,6 +111,13 @@ impl ClientState { } } + pub fn with_set_frozen(self, h: Height) -> Self { + ClientState { + frozen_height: h, + ..self + } + } + /// Helper function to verify the upgrade client procedure. /// Resets all fields except the blockchain-specific ones. pub fn zero_custom_fields(mut client_state: Self) -> Self { @@ -165,36 +172,38 @@ impl TryFrom for ClientState { let trust_level = raw .trust_level .clone() - .ok_or_else(Error::missing_trusting_period)?; + .ok_or_else(|| Kind::InvalidRawClientState.context("missing trusting period"))?; Ok(Self { chain_id: ChainId::from_str(raw.chain_id.as_str()) - .map_err(Error::invalid_chain_identifier)?, + .map_err(|_| Kind::InvalidRawClientState.context("Invalid chain identifier"))?, trust_level: TrustThreshold::new(trust_level.numerator, trust_level.denominator) - .map_err(|e| Error::invalid_trust_threshold(format!("{}", e)))?, + .map_err(|e| Kind::InvalidTrustThreshold.context(e))?, trusting_period: raw .trusting_period - .ok_or_else(Error::missing_trusting_period)? + .ok_or_else(|| Kind::InvalidRawClientState.context("missing trusting period"))? .try_into() - .map_err(|_| Error::negative_trusting_period())?, + .map_err(|_| Kind::InvalidRawClientState.context("negative trusting period"))?, unbonding_period: raw .unbonding_period - .ok_or_else(Error::missing_unbonding_period)? + .ok_or_else(|| Kind::InvalidRawClientState.context("missing unbonding period"))? .try_into() - .map_err(|_| Error::negative_unbonding_period())?, + .map_err(|_| Kind::InvalidRawClientState.context("negative unbonding period"))?, max_clock_drift: raw .max_clock_drift - .ok_or_else(Error::missing_max_clock_drift)? + .ok_or_else(|| Kind::InvalidRawClientState.context("missing max clock drift"))? .try_into() - .map_err(|_| Error::negative_max_clock_drift())?, + .map_err(|_| Kind::InvalidRawClientState.context("negative max clock drift"))?, latest_height: raw .latest_height - .ok_or_else(Error::missing_latest_height)? - .into(), + .ok_or_else(|| Kind::InvalidRawClientState.context("missing latest height"))? + .try_into() + .map_err(|_| Kind::InvalidRawHeight)?, frozen_height: raw .frozen_height - .ok_or_else(Error::missing_frozen_height)? - .into(), + .ok_or_else(|| Kind::InvalidRawClientState.context("missing frozen height"))? + .try_into() + .map_err(|_| Kind::InvalidRawHeight)?, 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 521636fe9f..b7510dd14e 100644 --- a/modules/src/ics07_tendermint/consensus_state.rs +++ b/modules/src/ics07_tendermint/consensus_state.rs @@ -1,4 +1,3 @@ -use std::convert::Infallible; use std::convert::TryFrom; use std::time::SystemTime; @@ -12,7 +11,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; +use crate::ics07_tendermint::error::{Error, Kind}; use crate::ics07_tendermint::header::Header; use crate::ics23_commitment::commitment::CommitmentRoot; @@ -34,8 +33,6 @@ impl ConsensusState { } impl crate::ics02_client::client_consensus::ConsensusState for ConsensusState { - type Error = Infallible; - fn client_type(&self) -> ClientType { ClientType::Tendermint } @@ -44,7 +41,7 @@ impl crate::ics02_client::client_consensus::ConsensusState for ConsensusState { &self.root } - fn validate_basic(&self) -> Result<(), Infallible> { + fn validate_basic(&self) -> Result<(), Box> { unimplemented!() } @@ -61,21 +58,19 @@ impl TryFrom for ConsensusState { fn try_from(raw: RawConsensusState) -> Result { let proto_timestamp = raw .timestamp - .ok_or_else(|| Error::invalid_raw_consensus_state("missing timestamp".into()))?; + .ok_or_else(|| Kind::InvalidRawConsensusState.context("missing timestamp"))?; Ok(Self { root: raw .root - .ok_or_else(|| { - Error::invalid_raw_consensus_state("missing commitment root".into()) - })? + .ok_or_else(|| Kind::InvalidRawConsensusState.context("missing commitment root"))? .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| Error::invalid_raw_consensus_state(e.to_string()))?, + .map_err(|e| Kind::InvalidRawConsensusState.context(e.to_string()))?, }) } } diff --git a/modules/src/ics07_tendermint/error.rs b/modules/src/ics07_tendermint/error.rs index 949238cca5..1e30a941dc 100644 --- a/modules/src/ics07_tendermint/error.rs +++ b/modules/src/ics07_tendermint/error.rs @@ -1,102 +1,88 @@ -use crate::ics24_host::error::ValidationError; -use flex_error::{define_error, DisplayOnly, TraceError}; +use crate::Height; +use anomaly::{BoxError, Context}; +use thiserror::Error; -define_error! { - Error { - InvalidTrustingPeriod - { reason: String } - | _ | { "invalid trusting period" }, +use crate::ics24_host::error::ValidationKind; +use tendermint::{account::Id, validator::Info}; - InvalidUnboundingPeriod - { reason: String } - | _ | { "invalid unbonding period" }, +pub type Error = anomaly::Error; - InvalidAddress - | _ | { "invalid address" }, +#[derive(Clone, Debug, Error)] +pub enum Kind { + #[error("invalid trusting period")] + InvalidTrustingPeriod, - InvalidHeader - { reason: String } - [ DisplayOnly> ] - | _ | { "invalid header, failed basic validation" }, + #[error("invalid client state trust threshold")] + InvalidTrustThreshold, - InvalidTrustThreshold - { reason: String } - | e | { - format_args!("invalid client state trust threshold: {}", - e.reason) - }, + #[error("invalid unbonding period")] + InvalidUnboundingPeriod, - MissingSignedHeader - | _ | { "missing signed header" }, + #[error("invalid address")] + InvalidAddress, - Validation - { reason: String } - | _ | { "invalid header, failed basic validation" }, + #[error("invalid header, failed basic validation")] + InvalidHeader, - InvalidRawClientState - { reason: String } - | _ | { "invalid raw client state" }, + #[error("validation error")] + ValidationError, - MissingValidatorSet - | _ | { "missing validator set" }, + #[error("invalid raw client state")] + InvalidRawClientState, - MissingTrustedValidatorSet - | _ | { "missing trusted validator set" }, + #[error("invalid chain identifier: raw value {0} with underlying validation error: {1}")] + InvalidChainId(String, ValidationKind), - MissingTrustedHeight - | _ | { "missing trusted height" }, + #[error("invalid raw height")] + InvalidRawHeight, - MissingTrustingPeriod - | _ | { "missing trusting period" }, + #[error("invalid raw client consensus state")] + InvalidRawConsensusState, - MissingUnbondingPeriod - | _ | { "missing unbonding period" }, + #[error("invalid raw header")] + InvalidRawHeader, - InvalidChainIdentifier - [ ValidationError ] - | _ | { "Invalid chain identifier" }, + #[error("invalid raw misbehaviour")] + InvalidRawMisbehaviour, - NegativeTrustingPeriod - | _ | { "negative trusting period" }, + #[error(" hearder timestamp {0} must be at greater than current client consensus state timestamp {1}")] + LowUpdateTimestamp(String, String), - NegativeUnbondingPeriod - | _ | { "negative unbonding period" }, + #[error( + "Header timestamp {0} is outside the trusting period w.r.t. consenus state timestamp{1}" + )] + HeaderTimestampOutsideTrustingTime(String, String), - MissingMaxClockDrift - | _ | { "missing max clock drift" }, + #[error(" hearder height = {0} is invalid")] + InvalidHeaderHeight(Height), - 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" }, + #[error(" hearder height {0} must be at greater than current client height {1}")] + LowUpdateHeight(Height, Height), +} +impl Kind { + pub fn context(self, source: impl Into) -> Context { + Context::new(self, Some(source.into())) } } + +#[derive(Clone, Debug, Error)] +pub enum VerificationError { + #[error("Couldn't verify signature `{signature:?}` with validator `{validator:?}` on sign_bytes `{sign_bytes:?}`")] + InvalidSignature { + /// Signature as a byte array + signature: Vec, + /// Validator which provided the signature + validator: Box, + /// Bytes which were signed + sign_bytes: Vec, + }, + + /// Duplicate validator in commit signatures + #[error("duplicate validator with address {0}")] + DuplicateValidator(Id), + + /// Insufficient signers overlap + #[error("insufficient signers overlap {0} {1}")] + InsufficientOverlap(u64, u64), +} diff --git a/modules/src/ics07_tendermint/header.rs b/modules/src/ics07_tendermint/header.rs index 152ca4607f..87ba3204bf 100644 --- a/modules/src/ics07_tendermint/header.rs +++ b/modules/src/ics07_tendermint/header.rs @@ -1,21 +1,29 @@ 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; use tendermint::Time; use tendermint_proto::Protobuf; +use crate::ics07_tendermint::error::VerificationError; +use tendermint::block::{Commit, CommitSig}; +use tendermint::trust_threshold::TrustThreshold; +use tendermint::trust_threshold::TrustThresholdFraction; +use tendermint::vote::{SignedVote, ValidatorIndex, Vote}; + 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; +use crate::ics07_tendermint::client_state::ClientState; +use crate::ics07_tendermint::consensus_state::ConsensusState; +use crate::ics07_tendermint::error::{Error, Kind}; use crate::ics24_host::identifier::ChainId; use crate::Height; use std::cmp::Ordering; +use std::collections::HashSet; +use std::ops::Sub; /// Tendermint consensus header #[derive(Clone, PartialEq, Deserialize, Serialize)] // TODO: Add Eq bound once present in tendermint-rs @@ -69,6 +77,177 @@ pub fn headers_compatible(header: &SignedHeader, other: &SignedHeader) -> bool { } } +pub fn monotonicity_checks( + latest_consensus_state: ConsensusState, + header: Header, + client_state: ClientState, +) -> Result<(), Box> { + if client_state.latest_height() >= header.height() { + return Err(Kind::LowUpdateHeight(header.height(), client_state.latest_height).into()); + } + + if header.height().is_zero() { + return Err(Kind::InvalidHeaderHeight(header.height()).into()); + } + + //check header timestamp is increasing + if latest_consensus_state.timestamp >= header.signed_header.header().time { + return Err(Kind::HeaderTimestampOutsideTrustingTime( + header.signed_header.header().time.as_rfc3339(), + latest_consensus_state.timestamp.as_rfc3339(), + ) + .into()); + }; + + // check that the header is not outside the trusting period + if header + .signed_header + .header() + .time + .sub(client_state.trusting_period) + >= latest_consensus_state.timestamp + { + return Err(Kind::LowUpdateTimestamp( + header.signed_header.header().time.as_rfc3339(), + latest_consensus_state.timestamp.as_rfc3339(), + ) + .into()); + }; + + // check monotonicity of header height vs trusted height. + // unclear needed + if header.trusted_height >= header.height() { + return Err(format!( + "non monotonic height update w.r.t trusted header {}, {:?}", + header.trusted_height, + header.height() + ) + .into()); + }; + + Ok(()) +} + +/// Compute the voting power in a header and its commit against a validator set. +/// +/// The `trust_threshold` is currently not used, but might be in the future +/// for optimization purposes. +pub fn voting_power_in( + signed_header: &SignedHeader, + validator_set: &ValidatorSet, + trust_threshold: TrustThresholdFraction, +) -> Result<(), Box> { + let signatures = &signed_header.commit.signatures; + + let mut tallied_voting_power = 0_u64; + let mut seen_validators = HashSet::new(); + + // Get non-absent votes from the signatures + let non_absent_votes = signatures.iter().enumerate().flat_map(|(idx, signature)| { + non_absent_vote( + signature, + ValidatorIndex::try_from(idx).unwrap(), + &signed_header.commit, + ) + .map(|vote| (signature, vote)) + }); + + let total_voting_power = total_power_of(validator_set); + + for (signature, vote) in non_absent_votes { + // Ensure we only count a validator's power once + if seen_validators.contains(&vote.validator_address) { + return Err(VerificationError::DuplicateValidator(vote.validator_address).into()); + } else { + seen_validators.insert(vote.validator_address); + } + + let validator = match validator_set.validator(vote.validator_address) { + Some(validator) => validator, + None => continue, // Cannot find matching validator, so we skip the vote + }; + + let signed_vote = SignedVote::new( + vote.clone(), + signed_header.header.chain_id.clone(), + vote.validator_address, + vote.signature, + ); + + let sign_bytes = signed_vote.sign_bytes(); + // Check vote is valid + if validator + .verify_signature(&sign_bytes, signed_vote.signature()) + .is_err() + { + //continue; + return Err((VerificationError::InvalidSignature { + signature: signed_vote.signature().to_bytes(), + validator: Box::new(validator), + sign_bytes, + }) + .into()); + } + + // If the vote is neither absent nor nil, tally its power + if signature.is_commit() { + tallied_voting_power += validator.power(); + if trust_threshold.is_enough_power(tallied_voting_power, total_voting_power) { + return Ok(()); + } + } else { + // It's OK. We include stray signatures (~votes for nil) + // to measure validator availability. + } + } + + Err(VerificationError::InsufficientOverlap(tallied_voting_power, total_voting_power).into()) +} + +/// Compute the total voting power in a validator set +fn total_power_of(validator_set: &ValidatorSet) -> u64 { + validator_set + .validators() + .iter() + .fold(0u64, |total, val_info| total + val_info.power.value()) +} + +fn non_absent_vote( + commit_sig: &CommitSig, + validator_index: ValidatorIndex, + commit: &Commit, +) -> Option { + let (validator_address, timestamp, signature, block_id) = match commit_sig { + CommitSig::BlockIdFlagAbsent { .. } => return None, + CommitSig::BlockIdFlagCommit { + validator_address, + timestamp, + signature, + } => ( + *validator_address, + *timestamp, + signature, + Some(commit.block_id), + ), + CommitSig::BlockIdFlagNil { + validator_address, + timestamp, + signature, + } => (*validator_address, *timestamp, signature, None), + }; + + Some(Vote { + vote_type: tendermint::vote::Type::Precommit, + height: commit.height, + round: commit.round, + block_id, + timestamp: Some(timestamp), + validator_address, + validator_index, + signature: *signature, + }) +} + impl crate::ics02_client::header::Header for Header { fn client_type(&self) -> ClientType { ClientType::Tendermint @@ -92,31 +271,28 @@ impl TryFrom for Header { Ok(Self { signed_header: raw .signed_header - .ok_or_else(Error::missing_signed_header)? + .ok_or_else(|| Kind::InvalidRawHeader.context("missing signed header"))? .try_into() - .map_err(|e| Error::invalid_header("signed header conversion".to_string(), e))?, + .map_err(|_| Kind::InvalidHeader.context("signed header conversion"))?, validator_set: raw .validator_set - .ok_or_else(Error::missing_validator_set)? + .ok_or_else(|| Kind::InvalidRawHeader.context("missing validator set"))? .try_into() - .map_err(Error::invalid_raw_header)?, + .map_err(|e| Kind::InvalidRawHeader.context(e))?, trusted_height: raw .trusted_height - .ok_or_else(Error::missing_trusted_height)? - .into(), + .ok_or_else(|| Kind::InvalidRawHeader.context("missing height"))? + .try_into() + .map_err(|e| Kind::InvalidRawHeight.context(e))?, trusted_validator_set: raw .trusted_validators - .ok_or_else(Error::missing_trusted_validator_set)? + .ok_or_else(|| Kind::InvalidRawHeader.context("missing trusted validator set"))? .try_into() - .map_err(Error::invalid_raw_header)?, + .map_err(|e| Kind::InvalidRawHeader.context(e))?, }) } } -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 10bd7c1084..8061c082df 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; +use crate::ics07_tendermint::error::{Error, Kind}; 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(|| Error::invalid_raw_misbehaviour("missing header1".into()))? + .ok_or_else(|| Kind::InvalidRawMisbehaviour.context("missing header1"))? .try_into()?, header2: raw .header_2 - .ok_or_else(|| Error::invalid_raw_misbehaviour("missing header2".into()))? + .ok_or_else(|| Kind::InvalidRawMisbehaviour.context("missing header2"))? .try_into()?, }) } diff --git a/modules/src/ics07_tendermint/mod.rs b/modules/src/ics07_tendermint/mod.rs index 0c4263997f..4b981d7450 100644 --- a/modules/src/ics07_tendermint/mod.rs +++ b/modules/src/ics07_tendermint/mod.rs @@ -6,3 +6,4 @@ pub mod consensus_state; pub mod error; pub mod header; pub mod misbehaviour; +//pub mod predicates; diff --git a/modules/src/ics07_tendermint/predicates.rs b/modules/src/ics07_tendermint/predicates.rs new file mode 100644 index 0000000000..bc46b567dd --- /dev/null +++ b/modules/src/ics07_tendermint/predicates.rs @@ -0,0 +1,192 @@ +use crate::ics02_client::error::Kind; +use crate::ics07_tendermint::client_state::ClientState; +use crate::ics07_tendermint::consensus_state::ConsensusState; +use crate::ics07_tendermint::error::VerificationError; +use crate::ics07_tendermint::header::Header; +use std::collections::HashSet; +use std::convert::TryFrom; +use std::ops::Sub; +use tendermint::block::signed_header::SignedHeader; +use tendermint::block::{Commit, CommitSig}; +use tendermint::trust_threshold::TrustThreshold; +use tendermint::trust_threshold::TrustThresholdFraction; +use tendermint::validator::Set as ValidatorSet; +use tendermint::vote::{SignedVote, ValidatorIndex, Vote}; + +#[derive(Copy, Clone, Debug, Default)] +pub struct Predicates; + +impl Predicates { + pub fn monotonicity_checks( + &self, + latest_consensus_state: ConsensusState, + header: Header, + client_state: ClientState, + ) -> Result<(), Box> { + if client_state.latest_height() >= header.height() { + return Err(Kind::LowUpdateHeight(header.height(), client_state.latest_height).into()); + } + + if header.height().is_zero() { + return Err(Kind::InvalidHeaderHeight(header.height()).into()); + } + + //check header timestamp is increasing + if latest_consensus_state.timestamp >= header.signed_header.header().time { + return Err(Kind::HeaderTimestampOutsideTrustingTime( + header.signed_header.header().time.as_rfc3339(), + latest_consensus_state.timestamp.as_rfc3339(), + ) + .into()); + }; + + // check that the header is not outside the trusting period + if header + .signed_header + .header() + .time + .sub(client_state.trusting_period) + >= latest_consensus_state.timestamp + { + return Err(Kind::LowUpdateTimestamp( + header.signed_header.header().time.as_rfc3339(), + latest_consensus_state.timestamp.as_rfc3339(), + ) + .into()); + }; + + // check monotonicity of header height vs trusted height. + // unclear needed + if header.trusted_height >= header.height() { + return Err(format!( + "non monotonic height update w.r.t trusted header {}, {:?}", + header.trusted_height, + header.height() + ) + .into()); + }; + + Ok(()) + } + + /// Compute the voting power in a header and its commit against a validator set. + /// + /// The `trust_threshold` is currently not used, but might be in the future + /// for optimization purposes. + pub fn voting_power_in( + &self, + signed_header: &SignedHeader, + validator_set: &ValidatorSet, + trust_threshold: TrustThresholdFraction, + ) -> Result<(), Box> { + let signatures = &signed_header.commit.signatures; + + let mut tallied_voting_power = 0_u64; + let mut seen_validators = HashSet::new(); + + // Get non-absent votes from the signatures + let non_absent_votes = signatures.iter().enumerate().flat_map(|(idx, signature)| { + non_absent_vote( + signature, + ValidatorIndex::try_from(idx).unwrap(), + &signed_header.commit, + ) + .map(|vote| (signature, vote)) + }); + + let total_voting_power = self.total_power_of(validator_set); + + for (signature, vote) in non_absent_votes { + // Ensure we only count a validator's power once + if seen_validators.contains(&vote.validator_address) { + return Err(VerificationError::DuplicateValidator(vote.validator_address).into()); + } else { + seen_validators.insert(vote.validator_address); + } + + let validator = match validator_set.validator(vote.validator_address) { + Some(validator) => validator, + None => continue, // Cannot find matching validator, so we skip the vote + }; + + let signed_vote = SignedVote::new( + vote.clone(), + signed_header.header.chain_id.clone(), + vote.validator_address, + vote.signature, + ); + + let sign_bytes = signed_vote.sign_bytes(); + // Check vote is valid + if validator + .verify_signature(&sign_bytes, signed_vote.signature()) + .is_err() + { + //continue; + return Err((VerificationError::InvalidSignature { + signature: signed_vote.signature().to_bytes(), + validator: Box::new(validator), + sign_bytes, + }) + .into()); + } + + // If the vote is neither absent nor nil, tally its power + if signature.is_commit() { + tallied_voting_power += validator.power(); + if trust_threshold.is_enough_power(tallied_voting_power, total_voting_power) { + return Ok(()); + } + } else { + // It's OK. We include stray signatures (~votes for nil) + // to measure validator availability. + } + } + + Err(VerificationError::InsufficientOverlap(tallied_voting_power, total_voting_power).into()) + } + + /// Compute the total voting power in a validator set + fn total_power_of(&self, validator_set: &ValidatorSet) -> u64 { + validator_set + .validators() + .iter() + .fold(0u64, |total, val_info| total + val_info.power.value()) + } +} + +fn non_absent_vote( + commit_sig: &CommitSig, + validator_index: ValidatorIndex, + commit: &Commit, +) -> Option { + let (validator_address, timestamp, signature, block_id) = match commit_sig { + CommitSig::BlockIdFlagAbsent { .. } => return None, + CommitSig::BlockIdFlagCommit { + validator_address, + timestamp, + signature, + } => ( + *validator_address, + *timestamp, + signature, + Some(commit.block_id), + ), + CommitSig::BlockIdFlagNil { + validator_address, + timestamp, + signature, + } => (*validator_address, *timestamp, signature, None), + }; + + Some(Vote { + vote_type: tendermint::vote::Type::Precommit, + height: commit.height, + round: commit.round, + block_id, + timestamp: Some(timestamp), + validator_address, + validator_index, + signature: *signature, + }) +} diff --git a/modules/src/ics18_relayer/error.rs b/modules/src/ics18_relayer/error.rs index eb75c7aa20..d1cbefbfce 100644 --- a/modules/src/ics18_relayer/error.rs +++ b/modules/src/ics18_relayer/error.rs @@ -1,38 +1,27 @@ use crate::ics24_host::identifier::ClientId; -use crate::ics26_routing::error::Error as RoutingError; use crate::Height; -use flex_error::define_error; +use anomaly::{BoxError, Context}; +use thiserror::Error; -define_error! { - Error { - ClientStateNotFound - { client_id: ClientId } - | e | { format_args!("client state on destination chain not found, (client id: {0})", e.client_id) }, +pub type Error = anomaly::Error; - 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) - }, +#[derive(Clone, Debug, Error, PartialEq, Eq)] +pub enum Kind { + #[error("client state on destination chain not found, (client id: {0})")] + ClientStateNotFound(ClientId), - 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 already up-to-date (client id: {0}, source height: {1}, dest height: {2})")] + ClientAlreadyUpToDate(ClientId, Height, Height), - TransactionFailed - [ RoutingError ] - | _ | { "transaction processing by modules failed" }, + #[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())) } } diff --git a/modules/src/ics18_relayer/utils.rs b/modules/src/ics18_relayer/utils.rs index fd4bca4ba0..7d83f6cd49 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; +use crate::ics18_relayer::error::{Error, Kind}; use crate::ics24_host::identifier::ClientId; /// Builds a `ClientMsg::UpdateClient` for a client with id `client_id` running on the `dest` @@ -19,24 +19,26 @@ where // - query client state on destination chain let dest_client_state = dest .query_client_full_state(client_id) - .ok_or_else(|| Error::client_state_not_found(client_id.clone()))?; + .ok_or_else(|| Kind::ClientStateNotFound(client_id.clone()))?; let dest_client_latest_height = dest_client_state.latest_height(); if src_header.height() == dest_client_latest_height { - return Err(Error::client_already_up_to_date( + return Err(Kind::ClientAlreadyUpToDate( client_id.clone(), src_header.height(), dest_client_latest_height, - )); + ) + .into()); }; if dest_client_latest_height > src_header.height() { - return Err(Error::client_at_higher_height( + return Err(Kind::ClientAtHigherHeight( client_id.clone(), src_header.height(), dest_client_latest_height, - )); + ) + .into()); }; // Client on destination chain can be updated. @@ -50,7 +52,7 @@ where #[cfg(test)] mod tests { use crate::ics02_client::client_type::ClientType; - use crate::ics02_client::header::Header; + use crate::ics02_client::header::{AnyHeader, Header}; use crate::ics18_relayer::context::Ics18Context; use crate::ics18_relayer::utils::build_client_update_datagram; use crate::ics24_host::identifier::{ChainId, ClientId}; @@ -59,6 +61,7 @@ mod tests { use crate::mock::host::HostType; use crate::Height; use test_env_log::test; + use tracing::debug; #[test] /// Serves to test both ICS 26 `dispatch` & `build_client_update_datagram` functions. @@ -149,7 +152,19 @@ mod tests { // Update client on chain B to latest height of B. // - create the client update message with the latest header from B - let b_latest_header = ctx_b.query_latest_header().unwrap(); + let mut b_latest_header = ctx_b.query_latest_header().unwrap(); + // The test uses LightClientBlock that does not store the trusted height + + b_latest_header = match b_latest_header { + AnyHeader::Tendermint(header) => { + let th = header.height(); + let mut hheader = header.clone(); + hheader.trusted_height = th.decrement().unwrap(); + hheader.wrap_any() + } + AnyHeader::Mock(header) => header.wrap_any(), + }; + assert_eq!( b_latest_header.client_type(), ClientType::Tendermint, @@ -170,6 +185,8 @@ mod tests { let client_msg_a = client_msg_a_res.unwrap(); + debug!("client_msg_a = {:?}", client_msg_a); + // - send the message to A let dispatch_res_a = ctx_a.deliver(Ics26Envelope::Ics2Msg(client_msg_a)); let validation_res = ctx_a.validate(); diff --git a/modules/src/ics23_commitment/commitment.rs b/modules/src/ics23_commitment/commitment.rs index 05dd099914..4b0faac1e0 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::invalid_raw_merkle_proof)?; + prost::Message::decode(value.as_ref()).map_err(Error::InvalidRawMerkleProof)?; Ok(res) } } diff --git a/modules/src/ics23_commitment/error.rs b/modules/src/ics23_commitment/error.rs index bdcb8cd575..d5c6a68c6c 100644 --- a/modules/src/ics23_commitment/error.rs +++ b/modules/src/ics23_commitment/error.rs @@ -1,15 +1,11 @@ -use flex_error::{define_error, TraceError}; use prost::DecodeError; +use thiserror::Error; -define_error! { - #[derive(Debug, Clone)] - Error { - InvalidRawMerkleProof - [ TraceError ] - |_| { "invalid raw merkle proof" }, +#[derive(Clone, Debug, Error, PartialEq, Eq)] +pub enum Error { + #[error("invalid raw merkle proof")] + InvalidRawMerkleProof(DecodeError), - CommitmentProofDecodingFailed - [ TraceError ] - |_| { "failed to decode commitment proof" }, - } + #[error("failed to decode commitment proof")] + CommitmentProofDecodingFailed(DecodeError), } diff --git a/modules/src/ics23_commitment/merkle.rs b/modules/src/ics23_commitment/merkle.rs index 9103da5455..68fd7f183d 100644 --- a/modules/src/ics23_commitment/merkle.rs +++ b/modules/src/ics23_commitment/merkle.rs @@ -6,15 +6,12 @@ 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(EmptyPrefixError); + return Err("empty prefix".into()); } let mut result: Vec = vec![format!("{:?}", prefix)]; @@ -86,7 +83,7 @@ pub fn convert_tm_to_ics_merkle_proof(tm_proof: &Proof) -> Result`", 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" } +use anomaly::{BoxError, Context}; +use thiserror::Error; + +pub type ValidationError = anomaly::Error; + +#[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())) } } diff --git a/modules/src/ics24_host/identifier.rs b/modules/src/ics24_host/identifier.rs index a89ba20b30..53aa3ca2eb 100644 --- a/modules/src/ics24_host/identifier.rs +++ b/modules/src/ics24_host/identifier.rs @@ -4,15 +4,16 @@ use std::str::FromStr; use serde::{Deserialize, Serialize}; use crate::ics02_client::client_type::ClientType; -use crate::ics24_host::error::ValidationError; +use crate::ics24_host::error::ValidationKind; +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: . +/// See: https://github.com/informalsystems/ibc-rs/pull/304#discussion_r503917283. /// /// Also, contrast with tendermint-rs `ChainId` type. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] @@ -129,10 +130,10 @@ impl Default for ChainId { } impl TryFrom for ChainId { - type Error = ValidationError; + type Error = ValidationKind; fn try_from(value: String) -> Result { - Self::from_str(value.as_str()) + Self::from_str(value.as_str()).map_err(|e| e.kind().clone()) } } diff --git a/modules/src/ics24_host/validate.rs b/modules/src/ics24_host/validate.rs index c335203740..2650f732c9 100644 --- a/modules/src/ics24_host/validate.rs +++ b/modules/src/ics24_host/validate.rs @@ -1,4 +1,11 @@ -use super::error::ValidationError as Error; +use super::error::{ValidationError, ValidationKind}; + +/// Bails from the current function with the given error kind. +macro_rules! bail { + ($kind:expr) => { + return Err($kind.into()); + }; +} /// Path separator (ie. forward slash '/') const PATH_SEPARATOR: char = '/'; @@ -8,22 +15,27 @@ 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<(), Error> { +pub fn validate_identifier(id: &str, min: usize, max: usize) -> Result<(), ValidationError> { assert!(max >= min); // Check identifier is not empty if id.is_empty() { - return Err(Error::empty()); + bail!(ValidationKind::empty()); } // Check identifier does not contain path separators if id.contains(PATH_SEPARATOR) { - return Err(Error::contain_separator(id.to_string())); + bail!(ValidationKind::contains_separator(id.to_string())); } // Check identifier length is between given min/max if id.len() < min || id.len() > max { - return Err(Error::invalid_length(id.to_string(), id.len(), min, max)); + bail!(ValidationKind::invalid_length( + id.to_string(), + id.len(), + min, + max + )); } // Check that the identifier comprises only valid characters: @@ -34,7 +46,7 @@ pub fn validate_identifier(id: &str, min: usize, max: usize) -> Result<(), Error .chars() .all(|c| c.is_alphanumeric() || VALID_SPECIAL_CHARS.contains(c)) { - return Err(Error::invalid_character(id.to_string())); + bail!(ValidationKind::invalid_character(id.to_string())); } // All good! @@ -45,7 +57,7 @@ pub fn validate_identifier(id: &str, min: usize, max: usize) -> Result<(), Error /// /// A valid identifier must be between 9-64 characters and only contain lowercase /// alphabetic characters, -pub fn validate_client_identifier(id: &str) -> Result<(), Error> { +pub fn validate_client_identifier(id: &str) -> Result<(), ValidationError> { validate_identifier(id, 9, 64) } @@ -53,7 +65,7 @@ pub fn validate_client_identifier(id: &str) -> Result<(), Error> { /// /// A valid Identifier must be between 10-64 characters and only contain lowercase /// alphabetic characters, -pub fn validate_connection_identifier(id: &str) -> Result<(), Error> { +pub fn validate_connection_identifier(id: &str) -> Result<(), ValidationError> { validate_identifier(id, 10, 64) } @@ -61,7 +73,7 @@ pub fn validate_connection_identifier(id: &str) -> Result<(), Error> { /// /// A valid Identifier must be between 2-64 characters and only contain lowercase /// alphabetic characters, -pub fn validate_port_identifier(id: &str) -> Result<(), Error> { +pub fn validate_port_identifier(id: &str) -> Result<(), ValidationError> { validate_identifier(id, 2, 64) } @@ -69,7 +81,7 @@ pub fn validate_port_identifier(id: &str) -> Result<(), Error> { /// /// A valid Identifier must be between 10-64 characters and only contain lowercase /// alphabetic characters, -pub fn validate_channel_identifier(id: &str) -> Result<(), Error> { +pub fn validate_channel_identifier(id: &str) -> Result<(), ValidationError> { validate_identifier(id, 8, 64) } diff --git a/modules/src/ics26_routing/error.rs b/modules/src/ics26_routing/error.rs index af64a047d2..868d9a1f9e 100644 --- a/modules/src/ics26_routing/error.rs +++ b/modules/src/ics26_routing/error.rs @@ -1,33 +1,25 @@ -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}; +use anomaly::{BoxError, Context}; +use thiserror::Error; -define_error! { - Error { - Ics02Client - [ ics02_client::error::Error ] - | _ | { "ICS02 client error" }, +pub type Error = anomaly::Error; - Ics03Connection - [ ics03_connection::error::Error ] - | _ | { "ICS03 connection error" }, +#[derive(Clone, Debug, Error, PartialEq, Eq)] +pub enum Kind { + #[error("error raised by message handler")] + HandlerRaisedError, - Ics04Channel - [ ics04_channel::error::Error ] - | _ | { "ICS04 channel error" }, + #[error("error raised by the keeper functionality in message handler")] + KeeperRaisedError, - Ics20FungibleTokenTransfer - [ ics20_fungible_token_transfer::error::Error ] - | _ | { "ICS20 fungible token transfer error" }, + #[error("unknown type URL {0}")] + UnknownMessageTypeUrl(String), - UnknownMessageTypeUrl - { url: String } - | e | { format_args!("unknown type URL {0}", e.url) }, + #[error("the message is malformed and cannot be decoded")] + MalformedMessageBytes, +} - MalformedMessageBytes - [ TraceError ] - | _ | { "the message is malformed and cannot be decoded" }, +impl Kind { + pub fn context(self, source: impl Into) -> Context { + Context::new(self, Some(source.into())) } } diff --git a/modules/src/ics26_routing/handler.rs b/modules/src/ics26_routing/handler.rs index 65e9673a68..3c481b538b 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; +use crate::ics26_routing::error::{Error, Kind}; use crate::ics26_routing::msgs::Ics26Envelope::{ self, Ics20Msg, Ics2Msg, Ics3Msg, Ics4ChannelMsg, Ics4PacketMsg, }; @@ -16,6 +16,8 @@ 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, @@ -55,11 +57,12 @@ where { let output = match msg { Ics2Msg(msg) => { - let handler_output = ics2_msg_dispatcher(ctx, msg).map_err(Error::ics02_client)?; + let handler_output = + ics2_msg_dispatcher(ctx, msg).map_err(|e| Kind::HandlerRaisedError.context(e))?; // Apply the result to the context (host chain store). ctx.store_client_result(handler_output.result) - .map_err(Error::ics02_client)?; + .map_err(|e| Kind::KeeperRaisedError.context(e))?; HandlerOutput::builder() .with_log(handler_output.log) @@ -68,11 +71,12 @@ where } Ics3Msg(msg) => { - let handler_output = ics3_msg_dispatcher(ctx, msg).map_err(Error::ics03_connection)?; + let handler_output = + ics3_msg_dispatcher(ctx, msg).map_err(|e| Kind::HandlerRaisedError.context(e))?; // Apply any results to the host chain store. ctx.store_connection_result(handler_output.result) - .map_err(Error::ics03_connection)?; + .map_err(|e| Kind::KeeperRaisedError.context(e))?; HandlerOutput::builder() .with_log(handler_output.log) @@ -81,11 +85,12 @@ where } Ics4ChannelMsg(msg) => { - let handler_output = ics4_msg_dispatcher(ctx, msg).map_err(Error::ics04_channel)?; + let handler_output = + ics4_msg_dispatcher(ctx, msg).map_err(|e| Kind::HandlerRaisedError.context(e))?; // Apply any results to the host chain store. ctx.store_channel_result(handler_output.result) - .map_err(Error::ics04_channel)?; + .map_err(|e| Kind::KeeperRaisedError.context(e))?; HandlerOutput::builder() .with_log(handler_output.log) @@ -95,11 +100,11 @@ where Ics20Msg(msg) => { let handler_output = - ics20_msg_dispatcher(ctx, msg).map_err(Error::ics20_fungible_token_transfer)?; + ics20_msg_dispatcher(ctx, msg).map_err(|e| Kind::HandlerRaisedError.context(e))?; // Apply any results to the host chain store. ctx.store_packet_result(handler_output.result) - .map_err(Error::ics04_channel)?; + .map_err(|e| Kind::KeeperRaisedError.context(e))?; HandlerOutput::builder() .with_log(handler_output.log) @@ -108,12 +113,12 @@ where } Ics4PacketMsg(msg) => { - let handler_output = - ics04_packet_msg_dispatcher(ctx, msg).map_err(Error::ics04_channel)?; + let handler_output = ics04_packet_msg_dispatcher(ctx, msg) + .map_err(|e| Kind::HandlerRaisedError.context(e))?; // Apply any results to the host chain store. ctx.store_packet_result(handler_output.result) - .map_err(Error::ics04_channel)?; + .map_err(|e| Kind::KeeperRaisedError.context(e))?; HandlerOutput::builder() .with_log(handler_output.log) @@ -168,6 +173,7 @@ mod tests { use crate::mock::context::MockContext; use crate::mock::header::MockHeader; use crate::test_utils::get_dummy_account_id; + use crate::timestamp::Timestamp; use crate::Height; #[test] @@ -254,7 +260,6 @@ mod tests { .unwrap(); let msg_transfer = get_dummy_msg_transfer(35); - let msg_transfer_two = get_dummy_msg_transfer(36); let mut msg_to_on_close = @@ -298,7 +303,9 @@ mod tests { name: "Client update successful".to_string(), msg: Ics26Envelope::Ics2Msg(ClientMsg::UpdateClient(MsgUpdateAnyClient { client_id: client_id.clone(), - header: MockHeader::new(update_client_height).into(), + header: MockHeader::new(update_client_height) + .with_timestamp(Timestamp::now()) + .into(), signer: default_signer.clone(), })), want_pass: true, @@ -373,10 +380,12 @@ mod tests { // The client update is required in this test, because the proof associated with // msg_recv_packet has the same height as the packet TO height (see get_dummy_raw_msg_recv_packet) Test { - name: "Client update successful".to_string(), + name: "Client update successful #2".to_string(), msg: Ics26Envelope::Ics2Msg(ClientMsg::UpdateClient(MsgUpdateAnyClient { client_id: client_id.clone(), - header: MockHeader::new(update_client_height_after_send).into(), + header: MockHeader::new(update_client_height_after_send) + .with_timestamp(Timestamp::now()) + .into(), signer: default_signer.clone(), })), want_pass: true, diff --git a/modules/src/ics26_routing/msgs.rs b/modules/src/ics26_routing/msgs.rs index 1ea271fa73..bd8bc2a7e1 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; +use crate::ics26_routing::error::{Error, Kind}; 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(Error::malformed_message_bytes)?; + .map_err(|e| Kind::MalformedMessageBytes.context(e))?; Ok(Ics26Envelope::Ics2Msg(ClientMsg::CreateClient(domain_msg))) } update_client::TYPE_URL => { let domain_msg = update_client::MsgUpdateAnyClient::decode_vec(&any_msg.value) - .map_err(Error::malformed_message_bytes)?; + .map_err(|e| Kind::MalformedMessageBytes.context(e))?; Ok(Ics26Envelope::Ics2Msg(ClientMsg::UpdateClient(domain_msg))) } upgrade_client::TYPE_URL => { let domain_msg = upgrade_client::MsgUpgradeAnyClient::decode_vec(&any_msg.value) - .map_err(Error::malformed_message_bytes)?; + .map_err(|e| Kind::MalformedMessageBytes.context(e))?; 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(Error::malformed_message_bytes)?; + .map_err(|e| Kind::MalformedMessageBytes.context(e))?; 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(Error::malformed_message_bytes)?; + .map_err(|e| Kind::MalformedMessageBytes.context(e))?; 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(Error::malformed_message_bytes)?; + .map_err(|e| Kind::MalformedMessageBytes.context(e))?; 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(Error::malformed_message_bytes)?; + .map_err(|e| Kind::MalformedMessageBytes.context(e))?; 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(Error::malformed_message_bytes)?; + .map_err(|e| Kind::MalformedMessageBytes.context(e))?; 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(Error::malformed_message_bytes)?; + .map_err(|e| Kind::MalformedMessageBytes.context(e))?; 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(Error::malformed_message_bytes)?; + .map_err(|e| Kind::MalformedMessageBytes.context(e))?; 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(Error::malformed_message_bytes)?; + .map_err(|e| Kind::MalformedMessageBytes.context(e))?; 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(Error::malformed_message_bytes)?; + .map_err(|e| Kind::MalformedMessageBytes.context(e))?; 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(Error::malformed_message_bytes)?; + .map_err(|e| Kind::MalformedMessageBytes.context(e))?; Ok(Ics26Envelope::Ics4ChannelMsg( ChannelMsg::ChannelCloseConfirm(domain_msg), )) @@ -125,39 +125,40 @@ impl TryFrom for Ics26Envelope { // ICS20 - 04 - Send packet transfer::TYPE_URL => { let domain_msg = transfer::MsgTransfer::decode_vec(&any_msg.value) - .map_err(Error::malformed_message_bytes)?; + .map_err(|e| Kind::MalformedMessageBytes.context(e))?; 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(Error::malformed_message_bytes)?; + .map_err(|e| Kind::MalformedMessageBytes.context(e))?; Ok(Ics26Envelope::Ics4PacketMsg(PacketMsg::RecvPacket( domain_msg, ))) } acknowledgement::TYPE_URL => { let domain_msg = acknowledgement::MsgAcknowledgement::decode_vec(&any_msg.value) - .map_err(Error::malformed_message_bytes)?; + .map_err(|e| Kind::MalformedMessageBytes.context(e))?; Ok(Ics26Envelope::Ics4PacketMsg(PacketMsg::AckPacket( domain_msg, ))) } timeout::TYPE_URL => { let domain_msg = timeout::MsgTimeout::decode_vec(&any_msg.value) - .map_err(Error::malformed_message_bytes)?; + .map_err(|e| Kind::MalformedMessageBytes.context(e))?; 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(Error::malformed_message_bytes)?; + .map_err(|e| Kind::MalformedMessageBytes.context(e))?; Ok(Ics26Envelope::Ics4PacketMsg(PacketMsg::ToClosePacket( domain_msg, ))) } - _ => Err(Error::unknown_message_type_url(any_msg.type_url)), + + _ => Err(Kind::UnknownMessageTypeUrl(any_msg.type_url).into()), } } } diff --git a/modules/src/lib.rs b/modules/src/lib.rs index 6feec7056e..e1cfdc510c 100644 --- a/modules/src/lib.rs +++ b/modules/src/lib.rs @@ -37,6 +37,7 @@ 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 d33fe4da8b..8576dab83d 100644 --- a/modules/src/mock/client_def.rs +++ b/modules/src/mock/client_def.rs @@ -3,7 +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::ics02_client::context::ClientReader; use crate::ics03_connection::connection::ConnectionEnd; use crate::ics04_channel::channel::ChannelEnd; use crate::ics04_channel::packet::Sequence; @@ -25,14 +25,15 @@ impl ClientDef for MockClient { fn check_header_and_update_state( &self, + _ctx: &dyn ClientReader, + _client_id: ClientId, client_state: Self::ClientState, header: Self::Header, - ) -> Result<(Self::ClientState, Self::ConsensusState), Error> { + ) -> Result<(Self::ClientState, Self::ConsensusState), Box> { if client_state.latest_height() >= header.height() { - return Err(Error::low_header_height( - header.height(), - client_state.latest_height(), - )); + return Err( + "received header height is lower than (or equal to) client latest height".into(), + ); } Ok((MockClientState(header), MockConsensusState::new(header))) } @@ -46,7 +47,7 @@ impl ClientDef for MockClient { client_id: &ClientId, _consensus_height: Height, _expected_consensus_state: &AnyConsensusState, - ) -> Result<(), Error> { + ) -> Result<(), Box> { let client_prefixed_path = Path::ClientConsensusState { client_id: client_id.clone(), epoch: height.revision_number, @@ -54,8 +55,7 @@ impl ClientDef for MockClient { } .to_string(); - let _path = - apply_prefix(prefix, vec![client_prefixed_path]).map_err(Error::empty_prefix)?; + let _path = apply_prefix(prefix, vec![client_prefixed_path])?; // TODO - add ctx to all client verification functions // let cs = ctx.fetch_self_consensus_state(height); @@ -73,7 +73,7 @@ impl ClientDef for MockClient { _proof: &CommitmentProofBytes, _connection_id: Option<&ConnectionId>, _expected_connection_end: &ConnectionEnd, - ) -> Result<(), Error> { + ) -> Result<(), Box> { Ok(()) } @@ -86,7 +86,7 @@ impl ClientDef for MockClient { _port_id: &PortId, _channel_id: &ChannelId, _expected_channel_end: &ChannelEnd, - ) -> Result<(), Error> { + ) -> Result<(), Box> { Ok(()) } @@ -99,7 +99,7 @@ impl ClientDef for MockClient { _client_id: &ClientId, _proof: &CommitmentProofBytes, _expected_client_state: &AnyClientState, - ) -> Result<(), Error> { + ) -> Result<(), Box> { Ok(()) } @@ -112,7 +112,7 @@ impl ClientDef for MockClient { _channel_id: &ChannelId, _seq: &Sequence, _data: String, - ) -> Result<(), Error> { + ) -> Result<(), Box> { Ok(()) } @@ -125,7 +125,7 @@ impl ClientDef for MockClient { _channel_id: &ChannelId, _seq: &Sequence, _data: Vec, - ) -> Result<(), Error> { + ) -> Result<(), Box> { Ok(()) } @@ -137,7 +137,7 @@ impl ClientDef for MockClient { _port_id: &PortId, _channel_id: &ChannelId, _seq: &Sequence, - ) -> Result<(), Error> { + ) -> Result<(), Box> { Ok(()) } @@ -149,7 +149,7 @@ impl ClientDef for MockClient { _port_id: &PortId, _channel_id: &ChannelId, _seq: &Sequence, - ) -> Result<(), Error> { + ) -> Result<(), Box> { Ok(()) } fn verify_upgrade_and_update_state( @@ -158,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), Error> { + ) -> Result<(Self::ClientState, Self::ConsensusState), Box> { Ok((*client_state, consensus_state.clone())) } } diff --git a/modules/src/mock/client_state.rs b/modules/src/mock/client_state.rs index 2f2cc35451..5c86f9259a 100644 --- a/modules/src/mock/client_state.rs +++ b/modules/src/mock/client_state.rs @@ -1,5 +1,4 @@ use std::collections::HashMap; -use std::convert::Infallible; use std::convert::{TryFrom, TryInto}; use std::time::Duration; @@ -13,6 +12,7 @@ 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,7 +133,9 @@ impl TryFrom for MockConsensusState { type Error = Error; fn try_from(raw: RawMockConsensusState) -> Result { - let raw_header = raw.header.ok_or_else(Error::missing_raw_consensus_state)?; + let raw_header = raw + .header + .ok_or_else(|| ClientKind::InvalidRawConsensusState.context("missing header"))?; Ok(Self { header: MockHeader::try_from(raw_header)?, @@ -160,8 +162,6 @@ 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<(), Infallible> { + fn validate_basic(&self) -> Result<(), Box> { Ok(()) } diff --git a/modules/src/mock/context.rs b/modules/src/mock/context.rs index c7003bfbe6..9e78263ffb 100644 --- a/modules/src/mock/context.rs +++ b/modules/src/mock/context.rs @@ -1,7 +1,10 @@ //! Implementation of a global context mock. Used in testing handlers of all IBC modules. +use ::tracing::info; + use std::cmp::min; use std::collections::HashMap; +use std::error::Error; use prost_types::Any; use sha2::Digest; @@ -19,13 +22,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 ChannelError; +use crate::ics04_channel::error::{Error as Ics4Error, Kind as Ics4Kind}; 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; +use crate::ics18_relayer::error::{Error as Ics18Error, Kind as Ics18ErrorKind}; use crate::ics23_commitment::commitment::CommitmentPrefix; use crate::ics24_host::identifier::{ChainId, ChannelId, ClientId, ConnectionId, PortId}; use crate::ics26_routing::context::Ics26Context; @@ -216,6 +219,7 @@ impl MockContext { self.host_chain_id.clone(), cs_height.revision_height, ); + let consensus_state = AnyConsensusState::from(light_block.clone()); let client_state = get_dummy_tendermint_client_state(light_block.signed_header.header); @@ -226,11 +230,79 @@ impl MockContext { }; let consensus_states = vec![(cs_height, consensus_state)].into_iter().collect(); + info!("Consensus state {:?}", consensus_states); + + let client_record = MockClientRecord { + client_type, + client_state, + consensus_states, + }; + self.clients.insert(client_id.clone(), client_record); + self + } + + pub fn with_client_parametrized_history( + mut self, + client_id: &ClientId, + client_state_height: Height, + client_type: Option, + consensus_state_height: Option, + ) -> Self { + let cs_height = consensus_state_height.unwrap_or(client_state_height); + let prev_cs_height = cs_height.clone().sub(1).unwrap_or(client_state_height); + + let client_type = client_type.unwrap_or(ClientType::Mock); + + let (client_state, consensus_state) = match client_type { + // If it's a mock client, create the corresponding mock states. + ClientType::Mock => ( + Some(MockClientState(MockHeader::new(client_state_height)).into()), + MockConsensusState::new(MockHeader::new(cs_height)).into(), + ), + // If it's a Tendermint client, we need TM states. + ClientType::Tendermint => { + let light_block = HostBlock::generate_tm_block( + self.host_chain_id.clone(), + cs_height.revision_height, + ); + + let consensus_state = AnyConsensusState::from(light_block.clone()); + let client_state = + get_dummy_tendermint_client_state(light_block.signed_header.header); + + // Return the tuple. + (Some(client_state), consensus_state) + } + }; + + let prev_consensus_state = match client_type { + // If it's a mock client, create the corresponding mock states. + ClientType::Mock => MockConsensusState::new(MockHeader::new(prev_cs_height)).into(), + // If it's a Tendermint client, we need TM states. + ClientType::Tendermint => { + let light_block = HostBlock::generate_tm_block( + self.host_chain_id.clone(), + prev_cs_height.revision_height, + ); + AnyConsensusState::from(light_block) + } + }; + + let consensus_states = vec![ + (prev_cs_height, prev_consensus_state), + (cs_height, consensus_state), + ] + .into_iter() + .collect(); + + info!("Consensus state {:?}", consensus_states); + let client_record = MockClientRecord { client_type, client_state, consensus_states, }; + self.clients.insert(client_id.clone(), client_record); self } @@ -345,7 +417,7 @@ impl MockContext { /// Accessor for a block of the local (host) chain from this context. /// Returns `None` if the block at the requested height does not exist. - fn host_block(&self, target_height: Height) -> Option<&HostBlock> { + pub fn host_block(&self, target_height: Height) -> Option<&HostBlock> { let target = target_height.revision_height as usize; let latest = self.latest_height.revision_height as usize; @@ -381,17 +453,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(Ics18Error::transaction_failed)?; + dispatch(self, msg).map_err(|e| Ics18ErrorKind::TransactionFailed.context(e))?; // 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<(), String> { + pub fn validate(&self) -> Result<(), Box> { // 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()); + return Err("too many entries".to_string().into()); } // Check the content of the history. @@ -400,7 +472,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()); + return Err("latest height is not updated".to_string().into()); } } @@ -409,7 +481,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()); + return Err("headers in history not sequential".to_string().into()); } } Ok(()) @@ -429,6 +501,21 @@ impl MockContext { }) .collect() } + + pub fn latest_client_states(&self, client_id: &ClientId) -> &AnyClientState { + self.clients[client_id].client_state.as_ref().unwrap() + } + + pub fn latest_consensus_states( + &self, + client_id: &ClientId, + height: &Height, + ) -> &AnyConsensusState { + self.clients[client_id] + .consensus_states + .get(height) + .unwrap() + } } impl Ics26Context for MockContext {} @@ -470,17 +557,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(ChannelError::invalid_port_capability()) + Err(Ics4Kind::InvalidPortCapability.into()) } else { Ok(key) } } - None => Err(ChannelError::no_port_capability(port_id.clone())), + None => Err(Ics4Kind::NoPortCapability(port_id.clone()).into()), } } @@ -533,7 +620,7 @@ impl ChannelKeeper for MockContext { timeout_timestamp: Timestamp, timeout_height: Height, data: Vec, - ) -> Result<(), ChannelError> { + ) -> Result<(), Ics4Error> { let input = format!("{:?},{:?},{:?}", timeout_timestamp, timeout_height, data); self.packet_commitment .insert(key, ChannelReader::hash(self, input)); @@ -544,7 +631,7 @@ impl ChannelKeeper for MockContext { &mut self, key: (PortId, ChannelId, Sequence), ack: Vec, - ) -> Result<(), ChannelError> { + ) -> Result<(), Ics4Error> { let input = format!("{:?}", ack); self.packet_acknowledgement .insert(key, ChannelReader::hash(self, input)); @@ -554,7 +641,7 @@ impl ChannelKeeper for MockContext { fn delete_packet_acknowledgement( &mut self, key: (PortId, ChannelId, Sequence), - ) -> Result<(), ChannelError> { + ) -> Result<(), Ics4Error> { self.packet_acknowledgement.remove(&key); Ok(()) } @@ -563,7 +650,7 @@ impl ChannelKeeper for MockContext { &mut self, cid: ConnectionId, port_channel_id: &(PortId, ChannelId), - ) -> Result<(), ChannelError> { + ) -> Result<(), Ics4Error> { self.connection_channels .entry(cid) .or_insert_with(Vec::new) @@ -575,7 +662,7 @@ impl ChannelKeeper for MockContext { &mut self, port_channel_id: (PortId, ChannelId), channel_end: &ChannelEnd, - ) -> Result<(), ChannelError> { + ) -> Result<(), Ics4Error> { self.channels.insert(port_channel_id, channel_end.clone()); Ok(()) } @@ -584,7 +671,7 @@ impl ChannelKeeper for MockContext { &mut self, port_channel_id: (PortId, ChannelId), seq: Sequence, - ) -> Result<(), ChannelError> { + ) -> Result<(), Ics4Error> { self.next_sequence_send.insert(port_channel_id, seq); Ok(()) } @@ -593,7 +680,7 @@ impl ChannelKeeper for MockContext { &mut self, port_channel_id: (PortId, ChannelId), seq: Sequence, - ) -> Result<(), ChannelError> { + ) -> Result<(), Ics4Error> { self.next_sequence_recv.insert(port_channel_id, seq); Ok(()) } @@ -602,7 +689,7 @@ impl ChannelKeeper for MockContext { &mut self, port_channel_id: (PortId, ChannelId), seq: Sequence, - ) -> Result<(), ChannelError> { + ) -> Result<(), Ics4Error> { self.next_sequence_ack.insert(port_channel_id, seq); Ok(()) } @@ -614,7 +701,7 @@ impl ChannelKeeper for MockContext { fn delete_packet_commitment( &mut self, key: (PortId, ChannelId, Sequence), - ) -> Result<(), ChannelError> { + ) -> Result<(), Ics4Error> { self.packet_commitment.remove(&key); Ok(()) } @@ -623,7 +710,7 @@ impl ChannelKeeper for MockContext { &mut self, key: (PortId, ChannelId, Sequence), receipt: Receipt, - ) -> Result<(), ChannelError> { + ) -> Result<(), Ics4Error> { self.packet_receipt.insert(key, receipt); Ok(()) } @@ -798,7 +885,8 @@ 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(Ics18Error::transaction_failed)?; + let events = + deliver(self, msgs).map_err(|e| Ics18ErrorKind::TransactionFailed.context(e))?; 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 cd926383a2..9156e2368d 100644 --- a/modules/src/mock/header.rs +++ b/modules/src/mock/header.rs @@ -1,4 +1,4 @@ -use std::convert::TryFrom; +use std::convert::{TryFrom, TryInto}; 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::Error; +use crate::ics02_client::error::{self, Error}; use crate::ics02_client::header::AnyHeader; use crate::ics02_client::header::Header; use crate::mock::client_state::MockConsensusState; @@ -27,10 +27,13 @@ impl TryFrom for MockHeader { fn try_from(raw: RawMockHeader) -> Result { Ok(MockHeader { - height: raw.height.ok_or_else(Error::missing_raw_header)?.into(), - + height: raw + .height + .ok_or_else(|| error::Kind::InvalidRawHeader.context("missing height in header"))? + .try_into() + .map_err(|e| error::Kind::InvalidRawHeader.context(e))?, timestamp: Timestamp::from_nanoseconds(raw.timestamp) - .map_err(Error::invalid_packet_timestamp)?, + .map_err(|_| error::Kind::InvalidPacketTimestamp)?, }) } } @@ -52,9 +55,13 @@ impl MockHeader { pub fn new(height: Height) -> Self { Self { height, - timestamp: Default::default(), + timestamp: Timestamp::now() } } + + pub fn with_timestamp(self, timestamp: Timestamp) -> Self { + Self { timestamp, ..self } + } } impl From for AnyHeader { diff --git a/modules/src/mock/host.rs b/modules/src/mock/host.rs index 18f3fb2df0..4a3db0b23c 100644 --- a/modules/src/mock/host.rs +++ b/modules/src/mock/host.rs @@ -1,8 +1,6 @@ //! Host chain types and methods, used by context mock. -use std::convert::TryFrom; - -use tendermint::chain::Id as TMChainId; +use tendermint::time::Time; use tendermint_testgen::light_block::TmLightBlock; use tendermint_testgen::{Generator, LightBlock as TestgenLightBlock}; @@ -14,6 +12,7 @@ use crate::ics24_host::identifier::ChainId; use crate::mock::header::MockHeader; use crate::timestamp::Timestamp; use crate::Height; +use std::{thread, time}; /// Defines the different types of host chains that a mock context can emulate. /// The variants are as follows: @@ -51,7 +50,8 @@ impl HostBlock { match chain_type { HostType::Mock => HostBlock::Mock(MockHeader { height: Height::new(chain_id.version(), height), - timestamp: Timestamp::from_nanoseconds(1).unwrap(), + timestamp: Timestamp::now(), + //Timestamp::from_nanoseconds(1).unwrap(), }), HostType::SyntheticTendermint => { HostBlock::SyntheticTendermint(Box::new(Self::generate_tm_block(chain_id, height))) @@ -60,10 +60,18 @@ impl HostBlock { } pub fn generate_tm_block(chain_id: ChainId, height: u64) -> TmLightBlock { - let mut block = TestgenLightBlock::new_default(height).generate().unwrap(); - block.signed_header.header.chain_id = TMChainId::try_from(chain_id.to_string()).unwrap(); + //Sleep is required otherwise the generator produces blocks with the same timestamp + //as two block can be generated per second. + let ten_millis = time::Duration::from_millis(1000); + thread::sleep(ten_millis); + let time = Time::now() + .duration_since(Time::unix_epoch()) + .unwrap() + .as_secs(); - block + TestgenLightBlock::new_default_with_time_and_chain_id(chain_id.to_string(), time, height) + .generate() + .unwrap() } } diff --git a/modules/src/mock/misbehaviour.rs b/modules/src/mock/misbehaviour.rs index d35fbf6cea..edc8e5f6e2 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::Error; +use crate::ics02_client::error::{self, 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::missing_raw_misbehaviour)? + .ok_or_else(|| error::Kind::InvalidRawMisbehaviour.context("missing header1"))? .try_into()?, header2: raw .header2 - .ok_or_else(Error::missing_raw_misbehaviour)? + .ok_or_else(|| error::Kind::InvalidRawMisbehaviour.context("missing header2"))? .try_into()?, }) } diff --git a/modules/src/proofs.rs b/modules/src/proofs.rs index 5163f9cc85..d0fbfbbb28 100644 --- a/modules/src/proofs.rs +++ b/modules/src/proofs.rs @@ -2,17 +2,6 @@ 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) @@ -36,13 +25,13 @@ impl Proofs { consensus_proof: Option, other_proof: Option, height: Height, - ) -> Result { + ) -> Result { if height.is_zero() { - return Err(ProofError::zero_height()); + return Err("Proofs height cannot be zero".to_string()); } if object_proof.is_empty() { - return Err(ProofError::empty_proof()); + return Err("Object proof cannot be empty".to_string()); } Ok(Self { @@ -87,12 +76,12 @@ impl ConsensusProof { pub fn new( consensus_proof: CommitmentProofBytes, consensus_height: Height, - ) -> Result { + ) -> Result { if consensus_height.is_zero() { - return Err(ProofError::zero_height()); + return Err("Consensus height cannot be zero".to_string()); } if consensus_proof.is_empty() { - return Err(ProofError::empty_proof()); + return Err("Proof cannot be empty".to_string()); } Ok(Self { diff --git a/modules/src/timestamp.rs b/modules/src/timestamp.rs index 35487b09e4..773eda3890 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,12 +125,9 @@ impl Display for Timestamp { } } -define_error! { - TimestampOverflowError { - TimestampOverflow - |_| { "Timestamp overflow when modifying with duration" } - } -} +#[derive(Clone, Debug, Error, PartialEq, Eq)] +#[error("Timestamp overflow when modifying with duration")] +pub struct TimestampOverflowError; impl Add for Timestamp { type Output = Result; @@ -138,8 +135,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::timestamp_overflow())?; + let duration2 = + chrono::Duration::from_std(duration).map_err(|_| TimestampOverflowError)?; Ok(Self::from_datetime(datetime + duration2)) } None => Ok(self), @@ -153,8 +150,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::timestamp_overflow())?; + let duration2 = + chrono::Duration::from_std(duration).map_err(|_| TimestampOverflowError)?; Ok(Self::from_datetime(datetime - duration2)) } None => Ok(self), @@ -162,25 +159,25 @@ impl Sub for Timestamp { } } -define_error! { - ParseTimestampError { - ParseInt - [ TraceError ] - | _ | { "error parsing integer from string"}, +pub type ParseTimestampError = anomaly::Error; - TryFromInt - [ TraceError ] - | _ | { "error converting from u64 to i64" }, - } +#[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), } impl FromStr for Timestamp { type Err = ParseTimestampError; fn from_str(s: &str) -> Result { - let seconds = u64::from_str(s).map_err(ParseTimestampError::parse_int)?; + let seconds = u64::from_str(s).map_err(ParseTimestampErrorKind::ParseIntError)?; - Timestamp::from_nanoseconds(seconds).map_err(ParseTimestampError::try_from_int) + Timestamp::from_nanoseconds(seconds) + .map_err(|err| ParseTimestampErrorKind::TryFromIntError(err).into()) } } diff --git a/modules/src/utils.rs b/modules/src/utils.rs new file mode 100644 index 0000000000..4eb1a6ff69 --- /dev/null +++ b/modules/src/utils.rs @@ -0,0 +1,44 @@ +//! 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 97903618f7..d93ca72b30 100644 --- a/modules/tests/mbt.rs +++ b/modules/tests/mbt.rs @@ -1,8 +1,5 @@ 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 @@ -12,12 +9,12 @@ fn mbt() { } } -fn run_tests() -> Result<(), TestError> { +fn run_tests() -> Result<(), Box> { // 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 = IbcTestRunner::new(); - run(tla_tests_file, tla_config_file, runner)?; + let runner = runner::IbcTestRunner::new(); + modelator::run(tla_tests_file, tla_config_file, runner)?; Ok(()) } diff --git a/modules/tests/runner/mod.rs b/modules/tests/runner/mod.rs index 0571b8807f..4fbdc41f84 100644 --- a/modules/tests/runner/mod.rs +++ b/modules/tests/runner/mod.rs @@ -1,20 +1,21 @@ pub mod step; use std::collections::HashMap; -use std::fmt::Debug; +use std::error::Error; +use std::fmt::{Debug, Display}; 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 as client_error; +use ibc::ics02_client::error::Kind as Ics02ErrorKind; 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 as connection_error; +use ibc::ics03_connection::error::Kind as Ics03ErrorKind; 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; @@ -23,10 +24,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 as relayer_error; +use ibc::ics18_relayer::error::{Error as Ics18Error, Kind as Ics18ErrorKind}; use ibc::ics23_commitment::commitment::{CommitmentPrefix, CommitmentProofBytes}; use ibc::ics24_host::identifier::{ChainId, ClientId, ConnectionId}; -use ibc::ics26_routing::error as routing_error; +use ibc::ics26_routing::error::{Error as Ics26Error, Kind as Ics26ErrorKind}; use ibc::ics26_routing::msgs::Ics26Envelope; use ibc::mock::client_state::{MockClientState, MockConsensusState}; use ibc::mock::context::MockContext; @@ -82,39 +83,31 @@ impl IbcTestRunner { .expect("chain context should have been initialized") } - pub fn extract_ics02_error_kind( - ics18_result: Result<(), relayer_error::Error>, - ) -> client_error::ErrorDetail { + pub fn extract_handler_error_kind(ics18_result: Result<(), Ics18Error>) -> K + where + K: Clone + Debug + Display + Into + 'static, + { 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::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); - } - } + 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() } pub fn chain_id(chain_id: String) -> ChainId { @@ -301,7 +294,7 @@ impl IbcTestRunner { }) } - pub fn apply(&mut self, action: Action) -> Result<(), relayer_error::Error> { + pub fn apply(&mut self, action: Action) -> Result<(), Ics18Error> { match action { Action::None => panic!("unexpected action type"), Action::Ics02CreateClient { @@ -458,43 +451,43 @@ impl modelator::runner::TestRunner for IbcTestRunner { ActionOutcome::Ics02CreateOk => result.is_ok(), ActionOutcome::Ics02UpdateOk => result.is_ok(), ActionOutcome::Ics02ClientNotFound => matches!( - Self::extract_ics02_error_kind(result), - client_error::ErrorDetail::ClientNotFound(_) + Self::extract_handler_error_kind::(result), + Ics02ErrorKind::ClientNotFound(_) ), ActionOutcome::Ics02HeaderVerificationFailure => matches!( - Self::extract_ics02_error_kind(result), - client_error::ErrorDetail::HeaderVerificationFailure(_) + Self::extract_handler_error_kind::(result), + Ics02ErrorKind::HeaderVerificationFailure ), ActionOutcome::Ics03ConnectionOpenInitOk => result.is_ok(), ActionOutcome::Ics03MissingClient => matches!( - Self::extract_ics03_error_kind(result), - connection_error::ErrorDetail::MissingClient(_) + Self::extract_handler_error_kind::(result), + Ics03ErrorKind::MissingClient(_) ), ActionOutcome::Ics03ConnectionOpenTryOk => result.is_ok(), ActionOutcome::Ics03InvalidConsensusHeight => matches!( - Self::extract_ics03_error_kind(result), - connection_error::ErrorDetail::InvalidConsensusHeight(_) + Self::extract_handler_error_kind::(result), + Ics03ErrorKind::InvalidConsensusHeight(_, _) ), ActionOutcome::Ics03ConnectionNotFound => matches!( - Self::extract_ics03_error_kind(result), - connection_error::ErrorDetail::ConnectionNotFound(_) + Self::extract_handler_error_kind::(result), + Ics03ErrorKind::ConnectionNotFound(_) ), ActionOutcome::Ics03ConnectionMismatch => matches!( - Self::extract_ics03_error_kind(result), - connection_error::ErrorDetail::ConnectionMismatch(_) + Self::extract_handler_error_kind::(result), + Ics03ErrorKind::ConnectionMismatch(_) ), ActionOutcome::Ics03MissingClientConsensusState => matches!( - Self::extract_ics03_error_kind(result), - connection_error::ErrorDetail::MissingClientConsensusState(_) + Self::extract_handler_error_kind::(result), + Ics03ErrorKind::MissingClientConsensusState(_, _) ), ActionOutcome::Ics03InvalidProof => matches!( - Self::extract_ics03_error_kind(result), - connection_error::ErrorDetail::InvalidProof(_) + Self::extract_handler_error_kind::(result), + Ics03ErrorKind::InvalidProof ), ActionOutcome::Ics03ConnectionOpenAckOk => result.is_ok(), ActionOutcome::Ics03UninitializedConnection => matches!( - Self::extract_ics03_error_kind(result), - connection_error::ErrorDetail::UninitializedConnection(_) + Self::extract_handler_error_kind::(result), + Ics03ErrorKind::UninitializedConnection(_) ), ActionOutcome::Ics03ConnectionOpenConfirmOk => result.is_ok(), }; diff --git a/no-std-check/.gitignore b/no-std-check/.gitignore deleted file mode 100644 index 2f7896d1d1..0000000000 --- a/no-std-check/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target/ diff --git a/no-std-check/Cargo.lock b/no-std-check/Cargo.lock deleted file mode 100644 index 754c3feed3..0000000000 --- a/no-std-check/Cargo.lock +++ /dev/null @@ -1,980 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "addr2line" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a2e47a1fbe209ee101dd6d61285226744c6c8d3c21c8dc878ba6cb9f467f3a" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "anomaly" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "550632e31568ae1a5f47998c3aa48563030fc49b9ec91913ca337cf64fbc5ccb" -dependencies = [ - "backtrace", -] - -[[package]] -name = "anyhow" -version = "1.0.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486" - -[[package]] -name = "async-stream" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" -dependencies = [ - "async-stream-impl", - "futures-core", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "async-trait" -version = "0.1.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b98e84bbb4cbcdd97da190ba0c58a1bb0de2c1fdf67d159e192ed766aeca722" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "autocfg" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" - -[[package]] -name = "backtrace" -version = "0.3.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7815ea54e4d821e791162e078acbebfd6d8c8939cd559c9335dceb1c8ca7282" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - -[[package]] -name = "bumpalo" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" - -[[package]] -name = "bytes" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" - -[[package]] -name = "cc" -version = "1.0.68" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" -dependencies = [ - "libc", - "num-integer", - "num-traits", - "serde", - "time", - "winapi", -] - -[[package]] -name = "either" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" - -[[package]] -name = "flex-error" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "095f2687f9ef7504f4d2bfbccb6368ece0c1ac43865e79942d71c12c150dbb1b" -dependencies = [ - "paste", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "futures-channel" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e682a68b29a882df0545c143dc3646daefe80ba479bcdede94d5a703de2871e2" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1" - -[[package]] -name = "futures-sink" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a57bead0ceff0d6dde8f465ecd96c9338121bb7717d3e7b108059531870c4282" - -[[package]] -name = "futures-task" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae" - -[[package]] -name = "futures-util" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967" -dependencies = [ - "autocfg", - "futures-core", - "futures-task", - "pin-project-lite", - "pin-utils", -] - -[[package]] -name = "getrandom" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "gimli" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189" - -[[package]] -name = "h2" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "825343c4eef0b63f541f8903f395dc5beb362a979b5799a84062527ef1e37726" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" - -[[package]] -name = "http" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60daa14be0e0786db0f03a9e57cb404c9d756eed2b6c62b9ea98ec5743ec75a9" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a87b616e37e93c22fb19bcd386f02f3af5ea98a25670ad0fce773de23c5e68" - -[[package]] -name = "httpdate" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" - -[[package]] -name = "hyper" -version = "0.14.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7728a72c4c7d72665fde02204bcbd93b247721025b222ef78606f14513e0fd03" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "ibc-proto" -version = "0.8.1" -dependencies = [ - "getrandom", - "prost", - "prost-types", - "tendermint-proto", - "tonic", -] - -[[package]] -name = "indexmap" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" -dependencies = [ - "autocfg", - "hashbrown", -] - -[[package]] -name = "itertools" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" - -[[package]] -name = "js-sys" -version = "0.3.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" - -[[package]] -name = "log" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "memchr" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" - -[[package]] -name = "miniz_oxide" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg", -] - -[[package]] -name = "mio" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" -dependencies = [ - "libc", - "log", - "miow", - "ntapi", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", -] - -[[package]] -name = "no-std-check" -version = "0.1.0" -dependencies = [ - "flex-error", - "ibc-proto", -] - -[[package]] -name = "ntapi" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" -dependencies = [ - "winapi", -] - -[[package]] -name = "num-derive" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "num-integer" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" -dependencies = [ - "autocfg", -] - -[[package]] -name = "object" -version = "0.25.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38f2be3697a57b4060074ff41b44c16870d916ad7877c17696e063257482bc7" -dependencies = [ - "memchr", -] - -[[package]] -name = "paste" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58" - -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - -[[package]] -name = "pin-project" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7509cc106041c40a4518d2af7a61530e1eed0e6285296a3d8c5472806ccc4a4" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c950132583b500556b1efd71d45b319029f2b71518d979fcc208e16b42426f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "ppv-lite86" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" - -[[package]] -name = "proc-macro2" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "prost" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e6984d2f1a23009bd270b8bb56d0926810a3d483f59c987d77969e9d8e840b2" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-derive" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "169a15f3008ecb5160cba7d37bcd690a7601b6d30cfb87a117d45e59d52af5d4" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "prost-types" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b518d7cdd93dab1d1122cf07fa9a60771836c668dde9d9e2a139f957f0d9f1bb" -dependencies = [ - "bytes", - "prost", -] - -[[package]] -name = "quote" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", - "rand_hc", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dead70b0b5e03e9c814bcb6b01e03e68f7c57a80aa48c72ec92152ab3e818d49" - -[[package]] -name = "serde" -version = "1.0.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_bytes" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "slab" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" - -[[package]] -name = "socket2" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3dfc207c526015c632472a77be09cf1b6e46866581aecae5cc38fb4235dea2" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "subtle-encoding" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" -dependencies = [ - "zeroize", -] - -[[package]] -name = "syn" -version = "1.0.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "tendermint-proto" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fadcaea1eecd91dbdd9636fe8ad38d2b41fc0ae814c176e51e00925cdda78a34" -dependencies = [ - "anomaly", - "bytes", - "chrono", - "num-derive", - "num-traits", - "prost", - "prost-types", - "serde", - "serde_bytes", - "subtle-encoding", - "thiserror", -] - -[[package]] -name = "thiserror" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "time" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "tokio" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c8b05dc14c75ea83d63dd391100353789f5f24b8b3866542a5e85c8be8e985" -dependencies = [ - "autocfg", - "bytes", - "libc", - "memchr", - "mio", - "pin-project-lite", - "tokio-macros", - "winapi", -] - -[[package]] -name = "tokio-macros" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54473be61f4ebe4efd09cec9bd5d16fa51d70ea0192213d754d2d500457db110" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-stream" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2f3f698253f03119ac0102beaa64f67a67e08074d03a22d18784104543727f" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1caa0b0c8d94a049db56b5acf8cba99dc0623aab1b26d5b5f5e2d945846b3592" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tonic" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ac42cd97ac6bd2339af5bcabf105540e21e45636ec6fa6aae5e85d44db31be0" -dependencies = [ - "async-stream", - "async-trait", - "base64", - "bytes", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "percent-encoding", - "pin-project", - "prost", - "prost-derive", - "tokio", - "tokio-stream", - "tokio-util", - "tower", - "tower-service", - "tracing", - "tracing-futures", -] - -[[package]] -name = "tower" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60422bc7fefa2f3ec70359b8ff1caff59d785877eb70595904605bcc412470f" -dependencies = [ - "futures-core", - "futures-util", - "indexmap", - "pin-project", - "rand", - "slab", - "tokio", - "tokio-stream", - "tokio-util", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" - -[[package]] -name = "tower-service" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" - -[[package]] -name = "tracing" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d" -dependencies = [ - "cfg-if", - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9ff14f98b1a4b289c6248a023c1c2fa1491062964e9fed67ab29c4e4da4a052" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - -[[package]] -name = "try-lock" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" - -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" - -[[package]] -name = "wasm-bindgen" -version = "0.2.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "zeroize" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" diff --git a/no-std-check/Cargo.toml b/no-std-check/Cargo.toml deleted file mode 100644 index 45697c4759..0000000000 --- a/no-std-check/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "no-std-check" -version = "0.1.0" -edition = "2018" - -[dependencies] -# ibc = { path = "../modules" } -ibc-proto = { path = "../proto" } -flex-error = { version = "0.3.0", default-features = false } - -[profile.dev] -panic = "abort" - -[profile.release] -panic = "abort" diff --git a/no-std-check/README.md b/no-std-check/README.md deleted file mode 100644 index c0853fc240..0000000000 --- a/no-std-check/README.md +++ /dev/null @@ -1,98 +0,0 @@ -# `no_std` Compliance Check - -This crate checks the `no_std` compliance of the supported crates in ibc-rs. -There are two methods that can be used: - -## Panic Handler Conflict - -This follows the outline of the guide by -[Danilo Bargen](https://blog.dbrgn.ch/2019/12/24/testing-for-no-std-compatibility/) -to register a panic handler in the `no-std-check` crate. -Any crate imported `no-std-check` that uses `std` will cause a compile error that -looks like follows: - -``` -$ cargo build - Updating crates.io index - Compiling no-std-check v0.1.0 (/data/development/informal/ibc-rs/no-std-check) -error[E0152]: found duplicate lang item `panic_impl` - --> src/lib.rs:31:1 - | -31 | fn panic(_info: &PanicInfo) -> ! { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the lang item is first defined in crate `std` (which `prost` depends on) - = note: first definition in `std` loaded from /home/ubuntu/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-b6b48477bfa8c673.rlib - = note: second definition in the local crate (`no_std_check`) - -error: aborting due to previous error - -For more information about this error, try `rustc --explain E0152`. -error: could not compile `no-std-check` -``` - -- Pros: - - Can be tested using Rust stable. -- Cons: - - Crates must be listed on both `Cargo.toml` and `lib.rs`. - - Crates that are listed in `Cargo.toml` but not imported inside `lib.rs` are not checked. - -## Overrride std crates using Cargo Nightly - -This uses the unstable `build-std` feature provided by -[Cargo Nightly](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-std). -With this we can explicitly pass the std crates we want to support, `core` and `alloc`, -via command line, and exclude the `std` crate. - -If any of the dependency uses `std`, they will fail to compile at all, albeit with -confusing error messages: - -``` -$ rustup run nightly -- cargo build -j1 -Z build-std=core,alloc --target x86_64-unknown-linux-gnu - ... - Compiling bytes v1.0.1 -error[E0773]: attempted to define built-in macro more than once - --> /home/ubuntu/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/macros/mod.rs:1201:5 - | -1201 | / macro_rules! cfg { -1202 | | ($($cfg:tt)*) => { -1203 | | /* compiler built-in */ -1204 | | }; -1205 | | } - | |_____^ - | -note: previously defined here - --> /home/ubuntu/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/macros/mod.rs:1201:5 - | -1201 | / macro_rules! cfg { -1202 | | ($($cfg:tt)*) => { -1203 | | /* compiler built-in */ -1204 | | }; -1205 | | } - | |_____^ - -error: duplicate lang item in crate `core` (which `std` depends on): `bool`. - | - = note: the lang item is first defined in crate `core` (which `bytes` depends on) - = note: first definition in `core` loaded from /data/development/informal/ibc-rs/no-std-check/target/x86_64-unknown-linux-gnu/debug/deps/libcore-c00d94870d25cd7e.rmeta - = note: second definition in `core` loaded from /home/ubuntu/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-9924c22ae1efcf66.rlib - -error: duplicate lang item in crate `core` (which `std` depends on): `char`. - | - = note: the lang item is first defined in crate `core` (which `bytes` depends on) - = note: first definition in `core` loaded from /data/development/informal/ibc-rs/no-std-check/target/x86_64-unknown-linux-gnu/debug/deps/libcore-c00d94870d25cd7e.rmeta - = note: second definition in `core` loaded from /home/ubuntu/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-9924c22ae1efcf66.rlib -... -``` - -The above error are shown when building the `bytes` crate. This is caused by `bytes` using imports from `std`, -which causes `std` to be included and produce conflicts with the `core` crate that is explicitly built by Cargo. -This produces very long error messages, so we may want to use tools like `less` to scroll through the errors. - -Pros: - - Directly identify use of `std` in dependencies. - - Error is raised on the first dependency that imports `std`. - -Cons: - - Nightly-only feature that is subject to change. - - Confusing and long error messages. diff --git a/no-std-check/src/lib.rs b/no-std-check/src/lib.rs deleted file mode 100644 index 52ebe47b8d..0000000000 --- a/no-std-check/src/lib.rs +++ /dev/null @@ -1,33 +0,0 @@ -// ensure_no_std/src/main.rs -#![no_std] -#![allow(unused_imports)] - -// Import the crates that we want to check if they are fully no-std compliance -// use ibc; -use ibc_proto; -use flex_error; - -use core::panic::PanicInfo; - -/* - -This function definition checks for the compliance of no-std in -dependencies by causing a compile error if this crate is -linked with `std`. When that happens, you should see error messages -such as follows: - -``` -error[E0152]: found duplicate lang item `panic_impl` - --> no-std-check/src/lib.rs - | -12 | fn panic(_info: &PanicInfo) -> ! { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the lang item is first defined in crate `std` (which `offending-crate` depends on) -``` - - */ -#[panic_handler] -fn panic(_info: &PanicInfo) -> ! { - loop {} -} diff --git a/proto/Cargo.toml b/proto/Cargo.toml index 0e06e8f82b..e54d1f352e 100644 --- a/proto/Cargo.toml +++ b/proto/Cargo.toml @@ -24,9 +24,10 @@ all-features = true [dependencies] prost = "0.7" prost-types = "0.7" +anomaly = "0.2" bytes = "1.0" +thiserror = "1.0" tonic = "0.4" -getrandom = { version = "0.2", features = ["js"] } [dependencies.tendermint-proto] version = "=0.21.0" diff --git a/proto/src/lib.rs b/proto/src/lib.rs index 62903f113a..167ba3d922 100644 --- a/proto/src/lib.rs +++ b/proto/src/lib.rs @@ -3,25 +3,11 @@ // Todo: automate the creation of this module setup based on the dots in the filenames. // This module setup is necessary because the generated code contains "super::" calls for dependencies. -#![no_std] #![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")] -extern crate alloc; -extern crate core as std; - -// re-export format! macro from alloc::format to allow its use -// in generated code -#[macro_export] -macro_rules! format { - ($($args:tt)*) => { - ::alloc::format!($( $args )*) - } -} - /// The version (commit hash) of the Cosmos SDK used when generating this library. pub const COSMOS_SDK_VERSION: &str = include_str!("prost/COSMOS_SDK_COMMIT"); diff --git a/relayer-cli/Cargo.toml b/relayer-cli/Cargo.toml index a6a0aac31e..2208e74664 100644 --- a/relayer-cli/Cargo.toml +++ b/relayer-cli/Cargo.toml @@ -17,11 +17,7 @@ description = """ name = "hermes" [features] -default = ["telemetry", "std", "eyre_tracer"] -std = [ - "flex-error/std" -] -eyre_tracer = ["flex-error/eyre_tracer"] +default = ["telemetry"] profiling = ["ibc-relayer/profiling"] telemetry = ["ibc-relayer/telemetry", "ibc-telemetry"] @@ -31,12 +27,14 @@ 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" -futures = "0.3.16" +futures = "0.3.14" toml = "0.5.8" serde_derive = "1.0.116" serde_json = "1" @@ -49,7 +47,6 @@ 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 96b5121a7a..8ccf6faf17 100644 --- a/relayer-cli/src/application.rs +++ b/relayer-cli/src/application.rs @@ -8,12 +8,11 @@ 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::{validate_config, Config}, entry::EntryPoint, }; @@ -126,9 +125,8 @@ impl Application for CliApp { // Configure components self.state.components.after_config(&config)?; - validate_config(&config).map_err(|validation_err| { - FrameworkErrorKind::ConfigError.context(format!("{}", validation_err)) - })?; + validate_config(&config) + .map_err(|validation_err| FrameworkErrorKind::ConfigError.context(validation_err))?; self.config = Some(config); diff --git a/relayer-cli/src/cli_utils.rs b/relayer-cli/src/cli_utils.rs index 599dfe3337..17e88d24f1 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; +use crate::error::{Error, Kind}; #[derive(Clone, Debug)] /// Pair of chain handles that are used by most CLIs. @@ -42,10 +42,12 @@ pub fn spawn_chain_runtime( let chain_config = config .find_chain(chain_id) .cloned() - .ok_or_else(|| Error::missing_config(chain_id.clone()))?; + .ok_or_else(|| format!("missing chain for id ({}) in configuration file", chain_id)) + .map_err(|e| Kind::Config.context(e))?; let rt = Arc::new(TokioRuntime::new().unwrap()); - let handle = ChainRuntime::::spawn(chain_config, rt).map_err(Error::relayer)?; + let handle = ChainRuntime::::spawn(chain_config, rt) + .map_err(|e| Kind::Runtime.context(e))?; Ok(handle) } diff --git a/relayer-cli/src/commands.rs b/relayer-cli/src/commands.rs index 43259bf2bf..db2d62f4ca 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 abb2a4f26b..a877bd572c 100644 --- a/relayer-cli/src/commands/keys/add.rs +++ b/relayer-cli/src/commands/keys/add.rs @@ -5,6 +5,7 @@ use std::{ }; use abscissa_core::{Command, Options, Runnable}; +use anomaly::BoxError; use ibc::ics24_host::identifier::ChainId; use ibc_relayer::{ @@ -38,7 +39,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))?; @@ -95,7 +96,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 efee41c40b..7b272d9572 100644 --- a/relayer-cli/src/commands/keys/list.rs +++ b/relayer-cli/src/commands/keys/list.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use abscissa_core::{Command, Options, Runnable}; +use anomaly::BoxError; use ibc::ics24_host::identifier::ChainId; use ibc_relayer::{ @@ -60,9 +61,7 @@ pub struct KeysListOptions { pub chain_config: ChainConfig, } -pub fn list_keys( - config: ChainConfig, -) -> Result, Box> { +pub fn list_keys(config: ChainConfig) -> Result, BoxError> { 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 ae2f0208c2..45295bc489 100644 --- a/relayer-cli/src/commands/keys/restore.rs +++ b/relayer-cli/src/commands/keys/restore.rs @@ -1,6 +1,7 @@ use std::str::FromStr; use abscissa_core::{Command, Options, Runnable}; +use anomaly::BoxError; use ibc::ics24_host::identifier::ChainId; use ibc_relayer::{ @@ -91,7 +92,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 3e87fbdd68..fd3b0047d9 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, Command, Options, Runnable}; +use abscissa_core::{application::fatal_error, error::BoxError, 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 = Box; + type Err = BoxError; fn from_str(s: &str) -> Result { match s { @@ -67,7 +67,7 @@ pub struct ListenCmd { } impl ListenCmd { - fn cmd(&self) -> Result<(), Box> { + fn cmd(&self) -> Result<(), BoxError> { let config = app_config(); let chain_config = config @@ -92,10 +92,7 @@ impl Runnable for ListenCmd { } /// Listen to events -pub fn listen( - config: &ChainConfig, - filters: &[EventFilter], -) -> Result<(), Box> { +pub fn listen(config: &ChainConfig, filters: &[EventFilter]) -> Result<(), BoxError> { info!( "listening for events `{}` on '{}'...", filters.iter().format(", "), @@ -142,7 +139,7 @@ fn event_match(event: &IbcEvent, filters: &[EventFilter]) -> bool { fn subscribe( chain_config: &ChainConfig, rt: Arc, -) -> Result<(EventMonitor, EventReceiver), Box> { +) -> Result<(EventMonitor, EventReceiver), BoxError> { 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 6cba6d85b7..5ea708a119 100644 --- a/relayer-cli/src/commands/misbehaviour.rs +++ b/relayer-cli/src/commands/misbehaviour.rs @@ -1,11 +1,11 @@ -use abscissa_core::{config, Command, Options, Runnable}; +use abscissa_core::{config, error::BoxError, 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, Box> { +) -> Result, BoxError> { let chain = spawn_chain_runtime(config, chain_id) .map_err(|e| format!("could not spawn the chain runtime for {}: {}", chain_id, e))?; @@ -57,9 +57,10 @@ pub fn monitor_misbehaviour( // process update client events while let Ok(event_batch) = subscription.recv() { - match event_batch.deref() { + let event_batch = event_batch.unwrap_or_clone(); + match event_batch { Ok(event_batch) => { - for event in &event_batch.events { + for event in event_batch.events { match event { IbcEvent::UpdateClient(update) => { debug!("{:?}", update); @@ -67,7 +68,7 @@ pub fn monitor_misbehaviour( chain.clone(), config, update.client_id().clone(), - Some(update.clone()), + Some(update), )?; } @@ -77,7 +78,7 @@ pub fn monitor_misbehaviour( IbcEvent::ClientMisbehaviour(ref _misbehaviour) => { // TODO - submit misbehaviour to the witnesses (our full node) - return Ok(Some(event.clone())); + return Ok(Some(event)); } _ => {} @@ -98,7 +99,7 @@ fn misbehaviour_handling( config: &config::Reader, client_id: ClientId, update: Option, -) -> Result<(), Box> { +) -> Result<(), BoxError> { 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 2d582211a9..b192cb95eb 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; +use crate::error::{Error, Kind}; 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,7 +64,9 @@ impl Runnable for QueryAllClientsCmd { pagination: ibc_proto::cosmos::base::query::pagination::all(), }; - let res: Result<_, Error> = chain.query_clients(req).map_err(Error::relayer); + let res: Result<_, Error> = chain + .query_clients(req) + .map_err(|e| Kind::Query.context(e).into()); match res { Ok(clients) => { diff --git a/relayer-cli/src/commands/query/connection.rs b/relayer-cli/src/commands/query/connection.rs index 9fcc5c2d9e..e549fa2f62 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; +use crate::error::{Error, Kind}; use crate::prelude::*; #[derive(Clone, Command, Debug, Options)] @@ -104,7 +104,9 @@ impl Runnable for QueryConnectionChannelsCmd { pagination: ibc_proto::cosmos::base::query::pagination::all(), }; - let res: Result<_, Error> = chain.query_connection_channels(req).map_err(Error::relayer); + let res: Result<_, Error> = chain + .query_connection_channels(req) + .map_err(|e| Kind::Query.context(e).into()); 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 86ef5762f0..aaf5658b37 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; +use crate::error::{Error, Kind}; 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(Error::relayer) + .map_err(|e| Kind::Query.context(e).into()) .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 0e9164d033..09c7547ceb 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; +use crate::error::{Error, Kind}; 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(Error::relayer) + .map_err(|e| Kind::Query.context(e).into()) .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 d3df3717a6..51f3e63bfa 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; +use crate::error::{Error, Kind}; 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(Error::relayer)?; + .map_err(|e| Kind::Query.context(e))?; 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 0ad5a2b196..0073e30a8d 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; +use crate::error::{Error, Kind}; use crate::prelude::*; #[derive(Serialize, Debug)] @@ -44,7 +44,7 @@ impl QueryPacketCommitmentsCmd { chain .query_packet_commitments(grpc_request) - .map_err(Error::relayer) + .map_err(|e| Kind::Query.context(e).into()) // 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 3923ff6f83..a921bfe51f 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; +use crate::error::{Error, Kind}; 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(Error::supervisor)?; + .map_err(|e| Kind::Query.context(e))?; let channel = channel_connection_client.channel; debug!( @@ -54,7 +54,12 @@ impl QueryUnreceivedAcknowledgementCmd { .counterparty() .channel_id .as_ref() - .ok_or_else(|| Error::missing_counterparty_channel_id(channel.clone()))? + .ok_or_else(|| { + Kind::Query.context(format!( + "the channel {:?} has no counterparty channel id", + channel + )) + })? .to_string(); let counterparty_chain_id = channel_connection_client.client.client_state.chain_id(); @@ -69,7 +74,7 @@ impl QueryUnreceivedAcknowledgementCmd { let sequences: Vec = counterparty_chain .query_packet_acknowledgements(acks_request) - .map_err(Error::relayer) + .map_err(|e| Kind::Query.context(e)) // extract the sequences .map(|(packet_state, _)| packet_state.into_iter().map(|v| v.sequence).collect())?; @@ -81,7 +86,7 @@ impl QueryUnreceivedAcknowledgementCmd { chain .query_unreceived_acknowledgement(request) - .map_err(Error::relayer) + .map_err(|e| Kind::Query.context(e).into()) } } diff --git a/relayer-cli/src/commands/query/packet/unreceived_packets.rs b/relayer-cli/src/commands/query/packet/unreceived_packets.rs index 94a50d501a..e565b4add5 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; +use crate::error::{Error, Kind}; 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(Error::supervisor)?; + .map_err(|e| Kind::Query.context(e))?; let channel = channel_connection_client.channel; debug!( @@ -62,7 +62,12 @@ impl QueryUnreceivedPacketsCmd { .counterparty() .channel_id .as_ref() - .ok_or_else(|| Error::missing_counterparty_channel_id(channel.clone()))? + .ok_or_else(|| { + Kind::Query.context(format!( + "the channel {:?} counterparty has no channel id", + channel + )) + })? .to_string(); let counterparty_chain_id = channel_connection_client.client.client_state.chain_id(); @@ -77,7 +82,7 @@ impl QueryUnreceivedPacketsCmd { let commitments = counterparty_chain .query_packet_commitments(commitments_request) - .map_err(Error::relayer)?; + .map_err(|e| Kind::Query.context(e))?; // extract the sequences let sequences: Vec = commitments.0.into_iter().map(|v| v.sequence).collect(); @@ -96,7 +101,7 @@ impl QueryUnreceivedPacketsCmd { chain .query_unreceived_packets(request) - .map_err(Error::relayer) + .map_err(|e| Kind::Query.context(e).into()) } } diff --git a/relayer-cli/src/commands/query/tx/events.rs b/relayer-cli/src/commands/query/tx/events.rs index 4f8c5f5661..7850e0617b 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::Error; +use crate::error::Kind; use crate::prelude::app_config; /// Query the events emitted by transaction @@ -46,11 +46,14 @@ 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| Error::invalid_hash(self.hash.clone(), e)) + .map_err(|e| { + Kind::CliArg(format!("could not parse '{}' into a valid hash", self.hash)) + .context(e) + }) .and_then(|h| { chain .query_txs(QueryTxRequest::Transaction(QueryTxHash(h))) - .map_err(Error::relayer) + .map_err(|e| Kind::Query.context(e)) }); match res { diff --git a/relayer-cli/src/commands/tx/channel.rs b/relayer-cli/src/commands/tx/channel.rs index 39060015ed..47476a45f6 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; +use crate::error::{Error, Kind}; 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(Error::channel); + let res: Result = channel.$func().map_err(|e| Kind::Tx.context(e).into()); match res { Ok(receipt) => Output::success(receipt).exit(), @@ -67,52 +67,32 @@ pub struct TxRawChanOpenInitCmd { impl Runnable for TxRawChanOpenInitCmd { 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: 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(), - } + 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, + } + } + ); } } @@ -151,53 +131,6 @@ 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 1ed5e9c538..97f06bda77 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; +use crate::error::{Error, Kind}; #[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(Error::foreign_client); + .map_err(|e| Kind::Tx.context(e).into()); 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(Error::foreign_client); + .map_err(|e| Kind::Tx.context(e)); match res { Ok(events) => Output::success(events).exit(), @@ -233,7 +233,7 @@ impl TxUpgradeClientsCmd { }; let outputs = dst_chain .query_clients(req) - .map_err(Error::relayer)? + .map_err(|e| Kind::Query.context(e))? .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(Error::foreign_client) + client.upgrade().map_err(|e| Kind::Query.context(e).into()) } } diff --git a/relayer-cli/src/commands/tx/connection.rs b/relayer-cli/src/commands/tx/connection.rs index f8126715eb..65cd0e1514 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; +use crate::error::{Error, Kind}; use crate::prelude::*; macro_rules! conn_open_cmd { @@ -24,7 +24,8 @@ macro_rules! conn_open_cmd { debug!("Message {}: {:?}", $dbg_string, connection); - let res: Result = connection.$func().map_err(Error::connection); + let res: Result = + connection.$func().map_err(|e| Kind::Tx.context(e).into()); 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 2b0045aeac..d31ad2e0be 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; +use crate::error::{Error, Kind}; 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(Error::link); + .map_err(|e| Kind::Tx.context(e).into()); 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(Error::link); + .map_err(|e| Kind::Tx.context(e).into()); 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 72d9f3c679..5b1525d234 100644 --- a/relayer-cli/src/commands/tx/transfer.rs +++ b/relayer-cli/src/commands/tx/transfer.rs @@ -1,4 +1,5 @@ use abscissa_core::{config::Override, Command, FrameworkErrorKind, Options, Runnable}; +use anomaly::BoxError; use ibc::{ events::IbcEvent, @@ -13,7 +14,7 @@ use ibc_relayer::{ use crate::cli_utils::ChainHandlePair; use crate::conclude::{exit_with_unrecoverable_error, Output}; -use crate::error::Error; +use crate::error::{Error, Kind}; use crate::prelude::*; #[derive(Clone, Command, Debug, Options)] @@ -81,10 +82,7 @@ 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")?; @@ -199,7 +197,8 @@ 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(Error::packet); + build_and_send_transfer_messages(chains.src, chains.dst, opts) + .map_err(|e| Kind::Tx.context(e).into()); 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 f5e49ea86a..74dce68437 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; +use crate::error::{Error, Kind}; 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(Error::relayer); + .map_err(|e| Kind::Runtime.context(e)); 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(Error::relayer); + let dst_chain_res = CosmosSdkChain::bootstrap(opts.dst_chain_config.clone(), rt) + .map_err(|e| Kind::Runtime.context(e)); 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(Error::upgrade_chain); + .map_err(|e| Kind::Tx.context(e).into()); match res { Ok(ev) => Output::success(ev).exit(), diff --git a/relayer-cli/src/components.rs b/relayer-cli/src/components.rs index 8edb0b6ea5..fbb9f81901 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::Error; +use crate::config; /// Custom types to simplify the `Tracing` definition below type JsonFormatter = TracingFormatter, StdWriter>; @@ -111,14 +111,12 @@ 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 ); - let our_err = Error::invalid_log_level(log_level, e); - Err(FrameworkErrorKind::ConfigError - .context(format!("{}", our_err)) - .into()) + Err(FrameworkErrorKind::ConfigError.context(our_err).into()) } } } diff --git a/relayer-cli/src/config.rs b/relayer-cli/src/config.rs index 96c606e2ad..799ad763bd 100644 --- a/relayer-cli/src/config.rs +++ b/relayer-cli/src/config.rs @@ -7,11 +7,12 @@ use std::collections::BTreeSet; use std::path::PathBuf; -use flex_error::{define_error, TraceError}; +use thiserror::Error; + use ibc::ics24_host::identifier::ChainId; -use ibc_relayer::config::Config; use tendermint_light_client::types::TrustThreshold; -use tracing_subscriber::filter::ParseError; + +pub use ibc_relayer::config::Config; use crate::application::app_reader; @@ -21,48 +22,34 @@ pub fn config_path() -> Option { app.config_path().cloned() } -// 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) - }, - } +/// 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), } /// 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.iter() { + for c in &config.chains { if !unique_chain_ids.insert(c.id.clone()) { - return Err(Error::duplicate_chains(c.id.clone())); + return Err(Error::DuplicateChains(c.id.clone())); } validate_trust_threshold(&c.id, c.trust_threshold)?; @@ -78,7 +65,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::invalid_trust_threshold( + return Err(Error::InvalidTrustThreshold( trust_threshold, id.clone(), "trust threshold denominator cannot be zero".to_string(), @@ -86,7 +73,7 @@ fn validate_trust_threshold(id: &ChainId, trust_threshold: TrustThreshold) -> Re } if trust_threshold.numerator() * 3 < trust_threshold.denominator() { - return Err(Error::invalid_trust_threshold( + return Err(Error::InvalidTrustThreshold( trust_threshold, id.clone(), "trust threshold cannot be < 1/3".to_string(), @@ -94,7 +81,7 @@ fn validate_trust_threshold(id: &ChainId, trust_threshold: TrustThreshold) -> Re } if trust_threshold.numerator() >= trust_threshold.denominator() { - return Err(Error::invalid_trust_threshold( + return Err(Error::InvalidTrustThreshold( 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 4d6536f258..98c38c1ad5 100644 --- a/relayer-cli/src/error.rs +++ b/relayer-cli/src/error.rs @@ -1,95 +1,46 @@ -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; +//! Error types -define_error! { - /// An error raised within the relayer CLI - Error { - Config - |_| { "config error" }, +use anomaly::{BoxError, Context}; +use thiserror::Error; - Io - |_| { "I/O error" }, +/// An error raised within the relayer CLI +pub type Error = anomaly::Error; - Query - |_| { "query error" }, +/// Kinds of errors +#[derive(Clone, Debug, Eq, Error, PartialEq)] +pub enum Kind { + /// Error in configuration file + #[error("config error")] + Config, - Runtime - |_| { "chain runtime error" }, + /// Input/output error + #[error("I/O error")] + Io, - Tx - |_| { "tx error" }, + /// Input/output error + #[error("CLI argument error: {0}")] + CliArg(String), - InvalidHash - { hash: String } - [ DisplayOnly> ] - | e | { - format_args!("CLI argument error: could not parse '{}' into a valid hash", - e.hash) - }, + /// Error during network query + #[error("query error")] + Query, - CliArg - { reason: String } - | e | { - format_args!("CLI argument error: {0}", - e.reason) - }, + /// Error while spawning the runtime + #[error("chain runtime error")] + Runtime, - Keys - |_| { "keys error" }, + /// Error during transaction submission + #[error("tx error")] + Tx, - 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" }, + /// Error during transaction submission + #[error("keys error")] + Keys, +} - UpgradeChain - [ UpgradeChainError ] - |_| { "upgrade chain error" }, +impl Kind { + /// Create an error context from this error + pub fn context(self, source: impl Into) -> Context { + Context::new(self, Some(source.into())) } } diff --git a/relayer-cli/src/lib.rs b/relayer-cli/src/lib.rs index 32043b0eb6..2dbdcc9e11 100644 --- a/relayer-cli/src/lib.rs +++ b/relayer-cli/src/lib.rs @@ -14,6 +14,7 @@ #![forbid(unsafe_code)] #![deny( + missing_docs, rust_2018_idioms, trivial_casts, unused_lifetimes, @@ -23,9 +24,8 @@ pub mod application; pub mod commands; pub mod config; -pub mod prelude; - pub mod error; +pub mod prelude; pub(crate) mod cli_utils; pub(crate) mod components; diff --git a/relayer-cli/src/prelude.rs b/relayer-cli/src/prelude.rs index b22d0225de..1d60e31fb2 100644 --- a/relayer-cli/src/prelude.rs +++ b/relayer-cli/src/prelude.rs @@ -7,3 +7,6 @@ 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 132d5036b4..203c0a3367 100644 --- a/relayer/Cargo.toml +++ b/relayer/Cargo.toml @@ -13,11 +13,6 @@ description = """ """ [features] -default = ["std", "eyre_tracer"] -std = [ - "flex-error/std" -] -eyre_tracer = ["flex-error/eyre_tracer"] profiling = [] telemetry = ["ibc-telemetry"] @@ -27,6 +22,7 @@ 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" @@ -41,9 +37,9 @@ serde_json = { version = "1" } bytes = "1.0.0" prost = "0.7" prost-types = "0.7" -futures = "0.3.16" +futures = "0.3.14" crossbeam-channel = "0.5.1" -k256 = { version = "0.9.6", features = ["ecdsa-core", "ecdsa", "sha256"]} +k256 = { version = "0.9.5", features = ["ecdsa-core", "ecdsa", "sha256"]} hex = "0.4" bitcoin = { version = "=0.27", features = ["use-serde"] } tiny-bip39 = "0.8.0" @@ -58,10 +54,6 @@ 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 e9ab706d5b..05f9648a6c 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; +use crate::error::{Error, Kind}; 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(Error::bad_connection_state()); + return Err(Kind::ConnOpenTry("bad connection state".to_string()).into()); } } ConnectionMsgType::OpenAck => { if !connection_end.state_matches(&State::TryOpen) && !connection_end.state_matches(&State::Open) { - return Err(Error::bad_connection_state()); + return Err(Kind::ConnOpenTry("bad connection state".to_string()).into()); } } ConnectionMsgType::OpenConfirm => { if !connection_end.state_matches(&State::Open) { - return Err(Error::bad_connection_state()); + return Err(Kind::ConnOpenTry("bad connection state".to_string()).into()); } } } @@ -350,7 +350,9 @@ pub trait Chain: Sized { CommitmentProofBytes::from(consensus_state_proof), client_state_value.latest_height(), ) - .map_err(Error::consensus_proof)?, + .map_err(|e| { + Kind::ConnOpenTry("failed to build consensus proof".to_string()).context(e) + })?, ); client_state = Some(client_state_value); @@ -367,7 +369,7 @@ pub trait Chain: Sized { None, height.increment(), ) - .map_err(Error::malformed_proof)?, + .map_err(|_| Kind::MalformedProof)?, )) } @@ -382,8 +384,10 @@ pub trait Chain: Sized { let channel_proof = CommitmentProofBytes::from(self.proven_channel(port_id, channel_id, height)?.1); - Proofs::new(channel_proof, None, None, None, height.increment()) - .map_err(Error::malformed_proof) + Ok( + Proofs::new(channel_proof, None, None, None, height.increment()) + .map_err(|_| Kind::MalformedProof)?, + ) } /// Builds the proof for packet messages. @@ -413,7 +417,7 @@ pub trait Chain: Sized { channel_proof, height.increment(), ) - .map_err(Error::malformed_proof)?; + .map_err(|_| Kind::MalformedProof)?; Ok((bytes, proofs)) } diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index 5800d6193c..7eb39cba96 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -8,6 +8,7 @@ use std::{ time::{Duration, Instant}, }; +use anomaly::fail; use bech32::{ToBase32, Variant}; use bitcoin::hashes::hex::ToHex; use itertools::Itertools; @@ -74,7 +75,7 @@ use ibc_proto::ibc::core::connection::v1::{ }; use crate::config::{ChainConfig, GasPrice}; -use crate::error::Error; +use crate::error::{Error, Kind}; use crate::event::monitor::{EventMonitor, EventReceiver}; use crate::keyring::{KeyEntry, KeyRing, Store}; use crate::light_client::tendermint::LightClient as TmLightClient; @@ -86,15 +87,11 @@ use super::Chain; mod compatibility; -/// Default gas limit when submitting a transaction. const DEFAULT_MAX_GAS: u64 = 300_000; - -/// Fraction of the estimated gas to add to the gas limit when submitting a transaction. const DEFAULT_GAS_PRICE_ADJUSTMENT: f64 = 0.1; -/// Upper limit on the size of transactions submitted by Hermes, expressed as a -/// fraction of the maximum block size defined in the Tendermint core consensus parameters. -pub const GENESIS_MAX_BYTES_MAX_FRACTION: f64 = 0.9; +const DEFAULT_MAX_MSG_NUM: usize = 30; +const DEFAULT_MAX_TX_SIZE: usize = 2 * 1048576; // 2 MBytes mod retry_strategy { use crate::util::retry::Fixed; @@ -119,12 +116,12 @@ pub struct CosmosSdkChain { impl CosmosSdkChain { /// Does multiple RPC calls to the full node, to check for - /// reachability and some basic APIs are available. + /// reachability and that some basic APIs are available. /// /// Currently this checks that: /// - the node responds OK to `/health` RPC call; /// - the node has transaction indexing enabled; - /// - the SDK version is supported; + /// - the SDK version is supported. /// /// Emits a log warning in case anything is amiss. /// Exits early if any health check fails, without doing any @@ -136,14 +133,16 @@ 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| { - Error::health_check_json_rpc( - chain_id.clone(), - rpc_address.clone(), - "/health".to_string(), - e, - ) - })?; + 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, + })?; // Checkup on transaction indexing chain @@ -156,103 +155,66 @@ impl CosmosSdkChain { Order::Ascending, ) .await - .map_err(|e| { - Error::health_check_json_rpc( - chain_id.clone(), - rpc_address.clone(), - "/tx_search".to_string(), - e, - ) + .map_err(|e| Kind::HealthCheckJsonRpc { + chain_id: chain_id.clone(), + address: rpc_address.clone(), + endpoint: "/tx_search".to_string(), + cause: e, })?; - // Construct a grpc client let mut client = ServiceClient::connect(chain.grpc_addr.clone()) .await .map_err(|e| { - Error::health_check_grpc_transport( - chain_id.clone(), - rpc_address.clone(), - "tendermint::ServiceClient".to_string(), - 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(), + } })?; let request = tonic::Request::new(GetNodeInfoRequest {}); - let response = client.get_node_info(request).await.map_err(|e| { - Error::health_check_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(), - ) - })?; + 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(), + })?; // Checkup on the underlying SDK version if let Some(diagnostic) = compatibility::run_diagnostic(version) { - return Err(Error::sdk_module_version( - chain_id.clone(), - grpc_address.clone(), - diagnostic.to_string(), - )); + return Err(Kind::SdkModuleVersion { + chain_id: chain_id.clone(), + address: grpc_address.clone(), + cause: diagnostic.to_string(), + } + .into()); } Ok(()) } if let Err(e) = self.block_on(do_health_checkup(self)) { - warn!("Health checkup for chain '{}' failed", self.id()); - warn!(" Reason: {}", e); - warn!(" Some Hermes features may not work in this mode!"); - } - } - - /// Performs validation of chain-specific configuration - /// parameters against the chain's genesis configuration. - /// - /// Currently, validates the following: - /// - the configured `max_tx_size` is appropriate. - /// - /// Emits a log warning in case any error is encountered and - /// exits early without doing subsequent validations. - pub fn validate_params(&self) { - fn do_validate_params(chain: &CosmosSdkChain) -> Result<(), Error> { - // Check on the configured max_tx_size against genesis block max_bytes parameter - let genesis = chain.block_on(chain.rpc_client.genesis()).map_err(|e| { - Error::config_validation_json_rpc( - chain.id().clone(), - chain.config.rpc_addr.to_string(), - "/genesis".to_string(), - e, - ) - })?; - - let genesis_max_bound = genesis.consensus_params.block.max_bytes; - let max_allowed = mul_ceil(genesis_max_bound, GENESIS_MAX_BYTES_MAX_FRACTION) as usize; - - if chain.max_tx_size() > max_allowed { - return Err(Error::config_validation_tx_size_out_of_bounds( - chain.id().clone(), - chain.max_tx_size(), - genesis_max_bound, - )); - } - - Ok(()) - } - - if let Err(e) = do_validate_params(self) { - warn!("Hermes might be misconfigured for chain '{}'", self.id()); - warn!(" Reason: {}", e); - warn!(" Some Hermes features may not work in this mode!"); + warn!("{}", e); + warn!("some Hermes features may not work in this mode!"); } } @@ -266,21 +228,21 @@ impl CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(Error::grpc_transport)?; + .map_err(|e| Kind::Grpc.context(e))?; let request = tonic::Request::new(ibc_proto::cosmos::staking::v1beta1::QueryParamsRequest {}); let response = self .block_on(client.params(request)) - .map_err(Error::grpc_status)?; + .map_err(|e| Kind::Grpc.context(e))?; let res = response .into_inner() .params - .ok_or_else(|| Error::grpc_response_param("none staking params".to_string()))? + .ok_or_else(|| Kind::Grpc.context("none staking params".to_string()))? .unbonding_time - .ok_or_else(|| Error::grpc_response_param("none unbonding time".to_string()))?; + .ok_or_else(|| Kind::Grpc.context("none unbonding time".to_string()))?; Ok(Duration::new(res.seconds as u64, res.nanos as u32)) } @@ -300,7 +262,7 @@ impl CosmosSdkChain { Ok(self .block_on(self.rpc_client().genesis()) - .map_err(|e| Error::rpc(self.config.rpc_addr.clone(), e))? + .map_err(|e| Kind::Rpc(self.config.rpc_addr.clone()).context(e))? .consensus_params) } @@ -347,11 +309,12 @@ impl CosmosSdkChain { }); if estimated_gas > self.max_gas() { - return Err(Error::tx_simulate_gas_estimate_exceeded( - self.id().clone(), + return Err(Kind::TxSimulateGasEstimateExceeded { + chain_id: self.id().clone(), estimated_gas, - self.max_gas(), - )); + max_gas: self.max_gas(), + } + .into()); } let adjusted_fee = self.fee_with_gas(estimated_gas); @@ -377,7 +340,9 @@ 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))?; + let response = self + .block_on(broadcast_tx_sync(self, tx_bytes)) + .map_err(|e| Kind::Rpc(self.config.rpc_addr.clone()).context(e))?; debug!("[{}] send_tx: broadcast_tx_sync: {:?}", self.id(), response); @@ -425,12 +390,12 @@ impl CosmosSdkChain { /// The maximum number of messages included in a transaction fn max_msg_num(&self) -> usize { - self.config.max_msg_num.into() + self.config.max_msg_num.unwrap_or(DEFAULT_MAX_MSG_NUM) } /// The maximum size of any transaction sent by the relayer to this chain fn max_tx_size(&self) -> usize { - self.config.max_tx_size.into() + self.config.max_tx_size.unwrap_or(DEFAULT_MAX_TX_SIZE) } fn query(&self, data: Path, height: ICSHeight, prove: bool) -> Result { @@ -438,10 +403,13 @@ impl CosmosSdkChain { let path = TendermintABCIPath::from_str(IBC_QUERY_PATH).unwrap(); - let height = Height::try_from(height.revision_height).map_err(Error::invalid_height)?; + let height = + Height::try_from(height.revision_height).map_err(|e| Kind::InvalidHeight.context(e))?; if !data.is_provable() & prove { - return Err(Error::private_store()); + return Err(Kind::Store + .context("requested proof for a path in the privateStore") + .into()); } let response = self.block_on(abci_query(self, path, data.to_string(), height, prove))?; @@ -458,10 +426,11 @@ impl CosmosSdkChain { data: ClientUpgradePath, height: Height, ) -> Result<(MerkleProof, ICSHeight), Error> { - let prev_height = Height::try_from(height.value() - 1).map_err(Error::invalid_height)?; + let prev_height = + Height::try_from(height.value() - 1).map_err(|e| Kind::InvalidHeight.context(e))?; let path = TendermintABCIPath::from_str(SDK_UPGRADE_QUERY_PATH).unwrap(); - let response: QueryResponse = self.block_on(abci_query( + let response = self.block_on(abci_query( self, path, Path::Upgrade(data).to_string(), @@ -469,7 +438,7 @@ impl CosmosSdkChain { true, ))?; - let proof = response.proof.ok_or_else(Error::empty_response_proof)?; + let proof = response.proof.ok_or(Kind::EmptyResponseProof)?; let height = ICSHeight::new( self.config.id.version(), @@ -488,21 +457,22 @@ impl CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(Error::grpc_transport)?; + .map_err(|e| Kind::Grpc.context(e))?; let request = tonic::Request::new(request); let response = self .block_on(client.simulate(request)) - .map_err(Error::grpc_status)? + .map_err(|e| Kind::Grpc.context(e))? .into_inner(); Ok(response) } fn key(&self) -> Result { - self.keybase() + Ok(self + .keybase() .get_key(&self.config.key_name) - .map_err(Error::key_base) + .map_err(|e| Kind::KeyBase.context(e))?) } fn key_bytes(&self, key: &KeyEntry) -> Result, Error> { @@ -519,7 +489,9 @@ 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))?; + let account = self + .block_on(query_account(self, self.key()?.account)) + .map_err(|e| Kind::Grpc.context(e))?; debug!( sequence = %account.sequence, @@ -608,7 +580,7 @@ impl CosmosSdkChain { let signed = self .keybase .sign_msg(&self.config.key_name, signdoc_buf) - .map_err(Error::key_base)?; + .map_err(|e| Kind::KeyBase.context(e))?; Ok(signed) } @@ -682,7 +654,12 @@ impl CosmosSdkChain { // All transactions confirmed Ok(()) => Ok(tx_sync_results), // Did not find confirmation - Err(_) => Err(Error::tx_no_confirmation()), + Err(_) => Err(Kind::TxNoConfirmation(format!( + "from chain {} for hash(es) {}", + self.id(), + hashes + )) + .into()), } } } @@ -705,14 +682,14 @@ impl Chain for CosmosSdkChain { fn bootstrap(config: ChainConfig, rt: Arc) -> Result { let rpc_client = HttpClient::new(config.rpc_addr.clone()) - .map_err(|e| Error::rpc(config.rpc_addr.clone(), e))?; + .map_err(|e| Kind::Rpc(config.rpc_addr.clone()).context(e))?; // Initialize key store and load key let keybase = KeyRing::new(Store::Test, &config.account_prefix, &config.id) - .map_err(Error::key_base)?; + .map_err(|e| Kind::KeyBase.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 grpc_addr = + Uri::from_str(&config.grpc_addr.to_string()).map_err(|e| Kind::Grpc.context(e))?; let chain = Self { config, @@ -724,7 +701,6 @@ impl Chain for CosmosSdkChain { }; chain.health_checkup(); - chain.validate_params(); Ok(chain) } @@ -738,7 +714,7 @@ impl Chain for CosmosSdkChain { .rt .block_on(self.rpc_client.status()) .map(|s| s.node_info.id) - .map_err(|e| Error::rpc(self.config.rpc_addr.clone(), e))?; + .map_err(|e| Kind::Rpc(self.config.rpc_addr.clone()).context(e))?; let light_client = TmLightClient::from_config(&self.config, peer_id)?; @@ -756,9 +732,9 @@ impl Chain for CosmosSdkChain { self.config.websocket_addr.clone(), rt, ) - .map_err(Error::event_monitor)?; + .map_err(Kind::EventMonitor)?; - event_monitor.subscribe().map_err(Error::event_monitor)?; + event_monitor.subscribe().map_err(Kind::EventMonitor)?; thread::spawn(move || event_monitor.run()); @@ -846,7 +822,7 @@ impl Chain for CosmosSdkChain { let key = self .keybase() .get_key(&self.config.key_name) - .map_err(Error::key_base)?; + .map_err(|e| Kind::KeyBase.context(e))?; let bech32 = encode_to_bech32(&key.address.to_hex(), &self.config.account_prefix)?; Ok(Signer::new(bech32)) @@ -860,7 +836,7 @@ impl Chain for CosmosSdkChain { let key = self .keybase() .get_key(&self.config.key_name) - .map_err(Error::key_base)?; + .map_err(|e| Kind::KeyBase.context(e))?; Ok(key) } @@ -880,13 +856,15 @@ impl Chain for CosmosSdkChain { let status = self .block_on(self.rpc_client().status()) - .map_err(|e| Error::rpc(self.config.rpc_addr.clone(), e))?; + .map_err(|e| Kind::Rpc(self.config.rpc_addr.clone()).context(e))?; if status.sync_info.catching_up { - return Err(Error::chain_not_caught_up( - self.config.rpc_addr.to_string(), - self.config().id.clone(), - )); + fail!( + Kind::LightClient(self.config.rpc_addr.to_string()), + "node at {} running chain {} not caught up", + self.config().rpc_addr, + self.config().id, + ); } Ok(ICSHeight { @@ -907,12 +885,12 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(Error::grpc_transport)?; + .map_err(|e| Kind::Grpc.context(e))?; let request = tonic::Request::new(request); let response = self .block_on(client.client_states(request)) - .map_err(Error::grpc_status)? + .map_err(|e| Kind::Grpc.context(e))? .into_inner(); // Deserialize into domain type @@ -941,9 +919,15 @@ impl Chain for CosmosSdkChain { let client_state = self .query(ClientStatePath(client_id.clone()), height, false) - .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)))?; + .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") + })?; Ok(client_state) } @@ -959,28 +943,31 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(Error::grpc_transport)?; + .map_err(|e| Kind::Grpc.context(e))?; let req = tonic::Request::new(QueryCurrentPlanRequest {}); let response = self .block_on(client.current_plan(req)) - .map_err(Error::grpc_status)?; + .map_err(|e| Kind::Grpc.context(e))?; let upgraded_client_state_raw = response .into_inner() .plan - .ok_or_else(Error::empty_response_value)? + .ok_or(Kind::EmptyResponseValue)? .upgraded_client_state - .ok_or_else(Error::empty_upgraded_client_state)?; - let client_state = - AnyClientState::try_from(upgraded_client_state_raw).map_err(Error::ics02)?; + .ok_or(Kind::EmptyUpgradedClientState)?; + let client_state = AnyClientState::try_from(upgraded_client_state_raw) + .map_err(|e| Kind::Grpc.context(e))?; // TODO: Better error kinds here. - let tm_client_state = downcast!(client_state.clone() => AnyClientState::Tendermint) - .ok_or_else(|| Error::client_state_type(format!("{:?}", client_state)))?; + let tm_client_state = + downcast!(client_state => AnyClientState::Tendermint).ok_or_else(|| { + Kind::Query("upgraded client state".into()).context("unexpected client state type") + })?; // Query for the proof. - let tm_height = Height::try_from(height.revision_height).map_err(Error::invalid_height)?; + let tm_height = + Height::try_from(height.revision_height).map_err(|e| Kind::InvalidHeight.context(e))?; let (proof, _proof_height) = self.query_client_upgrade_proof( ClientUpgradePath::UpgradedClientState(height.revision_height), tm_height, @@ -995,7 +982,8 @@ 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(Error::invalid_height)?; + let tm_height = + Height::try_from(height.revision_height).map_err(|e| Kind::InvalidHeight.context(e))?; let mut client = self .block_on( @@ -1003,27 +991,29 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(Error::grpc_transport)?; + .map_err(|e| Kind::Grpc.context(e))?; let req = tonic::Request::new(QueryUpgradedConsensusStateRequest { last_height: tm_height.into(), }); let response = self .block_on(client.upgraded_consensus_state(req)) - .map_err(Error::grpc_status)?; + .map_err(|e| Kind::Grpc.context(e))?; let upgraded_consensus_state_raw = response .into_inner() .upgraded_consensus_state - .ok_or_else(Error::empty_response_value)?; + .ok_or(Kind::EmptyResponseValue)?; // 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(Error::ics02)?; + let consensus_state = AnyConsensusState::try_from(upgraded_consensus_state_raw) + .map_err(|e| Kind::Grpc.context(e))?; - let tm_consensus_state = - downcast!(consensus_state.clone() => AnyConsensusState::Tendermint) - .ok_or_else(|| Error::client_state_type(format!("{:?}", consensus_state)))?; + let tm_consensus_state = downcast!(consensus_state => AnyConsensusState::Tendermint) + .ok_or_else(|| { + Kind::Query("upgraded consensus state".into()) + .context("unexpected consensus state type") + })?; // Fetch the proof. let (proof, _proof_height) = self.query_client_upgrade_proof( @@ -1047,12 +1037,12 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(Error::grpc_transport)?; + .map_err(|e| Kind::Grpc.context(e))?; let request = tonic::Request::new(request); let response = self .block_on(client.consensus_states(request)) - .map_err(Error::grpc_status)? + .map_err(|e| Kind::Grpc.context(e))? .into_inner(); let mut consensus_states: Vec = response @@ -1091,14 +1081,14 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(Error::grpc_transport)?; + .map_err(|e| Kind::Grpc.context(e))?; 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(Error::grpc_status(e)), + Err(e) => return Err(Kind::Grpc.context(e).into()), }; // TODO: add warnings for any identifiers that fail to parse (below). @@ -1125,13 +1115,13 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(Error::grpc_transport)?; + .map_err(|e| Kind::Grpc.context(e))?; let request = tonic::Request::new(request); let response = self .block_on(client.connections(request)) - .map_err(Error::grpc_status)? + .map_err(|e| Kind::Grpc.context(e))? .into_inner(); // TODO: add warnings for any identifiers that fail to parse (below). @@ -1162,7 +1152,7 @@ impl Chain for CosmosSdkChain { let mut client = connection::query_client::QueryClient::connect(chain.grpc_addr.clone()) .await - .map_err(Error::grpc_transport)?; + .map_err(|e| Kind::Grpc.context(e))?; let mut request = connection::QueryConnectionRequest { connection_id: connection_id.to_string(), @@ -1170,7 +1160,7 @@ impl Chain for CosmosSdkChain { .into_request(); let height_param = MetadataValue::from_str(&height.revision_height.to_string()) - .map_err(Error::invalid_metadata)?; + .map_err(|e| Kind::Grpc.context(e))?; request .metadata_mut() @@ -1178,15 +1168,17 @@ impl Chain for CosmosSdkChain { let response = client.connection(request).await.map_err(|e| { if e.code() == tonic::Code::NotFound { - Error::connection_not_found(connection_id.clone()) + Kind::ConnectionNotFound(connection_id.clone()).into() } else { - Error::grpc_status(e) + Kind::Grpc.context(e) } })?; match response.into_inner().connection { Some(raw_connection) => { - let connection_end = raw_connection.try_into().map_err(Error::ics03)?; + let connection_end = raw_connection + .try_into() + .map_err(|e| Kind::Grpc.context(e))?; Ok(connection_end) } @@ -1195,7 +1187,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(Error::connection_not_found(connection_id.clone())) + Err(Kind::ConnectionNotFound(connection_id.clone()).into()) } } } @@ -1215,13 +1207,13 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(Error::grpc_transport)?; + .map_err(|e| Kind::Grpc.context(e))?; let request = tonic::Request::new(request); let response = self .block_on(client.connection_channels(request)) - .map_err(Error::grpc_status)? + .map_err(|e| Kind::Grpc.context(e))? .into_inner(); // TODO: add warnings for any identifiers that fail to parse (below). @@ -1247,13 +1239,13 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(Error::grpc_transport)?; + .map_err(|e| Kind::Grpc.context(e))?; let request = tonic::Request::new(request); let response = self .block_on(client.channels(request)) - .map_err(Error::grpc_status)? + .map_err(|e| Kind::Grpc.context(e))? .into_inner(); let channels = response @@ -1275,7 +1267,9 @@ impl Chain for CosmosSdkChain { height, false, )?; - let channel_end = ChannelEnd::decode_vec(&res.value).map_err(Error::decode)?; + let channel_end = ChannelEnd::decode_vec(&res.value).map_err(|e| { + Kind::Query(format!("port '{}' & channel '{}'", port_id, channel_id)).context(e) + })?; Ok(channel_end) } @@ -1292,13 +1286,13 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(Error::grpc_transport)?; + .map_err(|e| Kind::Grpc.context(e))?; let request = tonic::Request::new(request); let response = self .block_on(client.channel_client_state(request)) - .map_err(Error::grpc_status)? + .map_err(|e| Kind::Grpc.context(e))? .into_inner(); let client_state: Option = response @@ -1321,21 +1315,22 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(Error::grpc_transport)?; + .map_err(|e| Kind::Grpc.context(e))?; let request = tonic::Request::new(request); let response = self .block_on(client.packet_commitments(request)) - .map_err(Error::grpc_status)? + .map_err(|e| Kind::Grpc.context(e))? .into_inner(); let pc = response.commitments; let height = response .height - .ok_or_else(|| Error::grpc_response_param("height".to_string()))? - .into(); + .ok_or_else(|| Kind::Grpc.context("missing height in response"))? + .try_into() + .map_err(|_| Kind::Grpc.context("invalid height in response"))?; Ok((pc, height)) } @@ -1353,13 +1348,13 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(Error::grpc_transport)?; + .map_err(|e| Kind::Grpc.context(e))?; let request = tonic::Request::new(request); let mut response = self .block_on(client.unreceived_packets(request)) - .map_err(Error::grpc_status)? + .map_err(|e| Kind::Grpc.context(e))? .into_inner(); response.sequences.sort_unstable(); @@ -1379,21 +1374,22 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(Error::grpc_transport)?; + .map_err(|e| Kind::Grpc.context(e))?; let request = tonic::Request::new(request); let response = self .block_on(client.packet_acknowledgements(request)) - .map_err(Error::grpc_status)? + .map_err(|e| Kind::Grpc.context(e))? .into_inner(); let pc = response.acknowledgements; let height = response .height - .ok_or_else(|| Error::grpc_response_param("height".to_string()))? - .into(); + .ok_or_else(|| Kind::Grpc.context("missing height in response"))? + .try_into() + .map_err(|_| Kind::Grpc.context("invalid height in response"))?; Ok((pc, height)) } @@ -1411,13 +1407,13 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(Error::grpc_transport)?; + .map_err(|e| Kind::Grpc.context(e))?; let request = tonic::Request::new(request); let mut response = self .block_on(client.unreceived_acks(request)) - .map_err(Error::grpc_status)? + .map_err(|e| Kind::Grpc.context(e))? .into_inner(); response.sequences.sort_unstable(); @@ -1436,13 +1432,13 @@ impl Chain for CosmosSdkChain { self.grpc_addr.clone(), ), ) - .map_err(Error::grpc_transport)?; + .map_err(|e| Kind::Grpc.context(e))?; let request = tonic::Request::new(request); let response = self .block_on(client.next_sequence_receive(request)) - .map_err(Error::grpc_status)? + .map_err(|e| Kind::Grpc.context(e))? .into_inner(); Ok(Sequence::from(response.next_sequence_receive)) @@ -1478,7 +1474,7 @@ impl Chain for CosmosSdkChain { 1, // get only the first Tx matching the query Order::Ascending, )) - .map_err(|e| Error::rpc(self.config.rpc_addr.clone(), e))?; + .map_err(|e| Kind::Grpc.context(e))?; assert!( response.txs.len() <= 1, @@ -1518,7 +1514,7 @@ impl Chain for CosmosSdkChain { 1, // get only the first Tx matching the query Order::Ascending, )) - .map_err(|e| Error::rpc(self.config.rpc_addr.clone(), e))?; + .map_err(|e| Kind::Grpc.context(e))?; if response.txs.is_empty() { return Ok(vec![]); @@ -1545,7 +1541,7 @@ impl Chain for CosmosSdkChain { 1, // get only the first Tx matching the query Order::Ascending, )) - .map_err(|e| Error::rpc(self.config.rpc_addr.clone(), e))?; + .map_err(|e| Kind::Grpc.context(e))?; if response.txs.is_empty() { Ok(vec![]) @@ -1564,16 +1560,23 @@ impl Chain for CosmosSdkChain { ) -> Result<(Self::ClientState, MerkleProof), Error> { crate::time!("proven_client_state"); - let res = self.query(ClientStatePath(client_id.clone()), height, true)?; + let res = self + .query(ClientStatePath(client_id.clone()), height, true) + .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 = AnyClientState::decode_vec(&res.value) + .map_err(|e| Kind::Query("client state".into()).context(e))?; - let client_state = downcast!(client_state.clone() => AnyClientState::Tendermint) - .ok_or_else(|| Error::client_state_type(format!("{:?}", client_state)))?; + let client_state = + downcast!(client_state => AnyClientState::Tendermint).ok_or_else(|| { + Kind::Query("client state".into()).context("unexpected client state type") + })?; Ok(( client_state, - res.proof.ok_or_else(Error::empty_response_proof)?, + res.proof.ok_or_else(|| { + Kind::Query("client state".into()).context("empty proof".to_string()) + })?, )) } @@ -1585,25 +1588,31 @@ 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, - )?; + 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 consensus_state = AnyConsensusState::decode_vec(&res.value).map_err(Error::decode)?; + let consensus_state = AnyConsensusState::decode_vec(&res.value) + .map_err(|e| Kind::Query("client consensus".into()).context(e))?; - let consensus_state = - downcast!(consensus_state.clone() => AnyConsensusState::Tendermint) - .ok_or_else(|| Error::client_state_type(format!("{:?}", consensus_state)))?; + let consensus_state = downcast!(consensus_state => AnyConsensusState::Tendermint) + .ok_or_else(|| { + Kind::Query("client consensus".into()).context("unexpected client consensus type") + })?; Ok(( consensus_state, - res.proof.ok_or_else(Error::empty_response_proof)?, + res.proof.ok_or_else(|| { + Kind::Query("client consensus".into()).context("empty proof".to_string()) + })?, )) } @@ -1612,12 +1621,17 @@ impl Chain for CosmosSdkChain { connection_id: &ConnectionId, height: ICSHeight, ) -> Result<(ConnectionEnd, MerkleProof), Error> { - let res = self.query(Path::Connections(connection_id.clone()), height, true)?; - let connection_end = ConnectionEnd::decode_vec(&res.value).map_err(Error::decode)?; + 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))?; Ok(( connection_end, - res.proof.ok_or_else(Error::empty_response_proof)?, + res.proof.ok_or_else(|| { + Kind::Query("proven connection".into()).context("empty proof".to_string()) + })?, )) } @@ -1627,17 +1641,22 @@ 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, - )?; + 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 channel_end = ChannelEnd::decode_vec(&res.value).map_err(Error::decode)?; + let channel_end = ChannelEnd::decode_vec(&res.value) + .map_err(|e| Kind::Query("proven channel".into()).context(e))?; Ok(( channel_end, - res.proof.ok_or_else(Error::empty_response_proof)?, + res.proof.ok_or_else(|| { + Kind::Query("proven channel".into()).context("empty proof".to_string()) + })?, )) } @@ -1676,16 +1695,20 @@ impl Chain for CosmosSdkChain { }, }; - let res = self.query(data, height, true)?; + let res = self + .query(data, height, true) + .map_err(|e| Kind::Query(packet_type.to_string()).context(e))?; - let commitment_proof_bytes = res.proof.ok_or_else(Error::empty_response_proof)?; + let commitment_proof_bytes = res.proof.ok_or_else(|| { + Kind::Query(packet_type.to_string()).context("empty proof".to_string()) + })?; Ok((res.value, commitment_proof_bytes)) } fn build_client_state(&self, height: ICSHeight) -> Result { // Build the client state. - ClientState::new( + Ok(ClientState::new( self.id().clone(), self.config.trust_threshold, self.config.trusting_period, @@ -1699,7 +1722,7 @@ impl Chain for CosmosSdkChain { after_misbehaviour: true, }, ) - .map_err(Error::ics07) + .map_err(|e| Kind::BuildClientStateFailure.context(e))?) } fn build_consensus_state( @@ -1883,23 +1906,25 @@ async fn abci_query( .rpc_client() .abci_query(Some(path), data.into_bytes(), height, prove) .await - .map_err(|e| Error::rpc(chain.config.rpc_addr.clone(), e))?; + .map_err(|e| Kind::Rpc(chain.config.rpc_addr.clone()).context(e))?; if !response.code.is_ok() { // Fail with response log. - return Err(Error::abci_query(response)); + return Err(Kind::Rpc(chain.config.rpc_addr.clone()) + .context(response.log.to_string()) + .into()); } if prove && response.proof.is_none() { // Fail due to empty proof - return Err(Error::empty_response_proof()); + return Err(Kind::EmptyResponseProof.into()); } let proof = response .proof .map(|p| convert_tm_to_ics_merkle_proof(&p)) .transpose() - .map_err(Error::ics23)?; + .map_err(Kind::Ics023)?; let response = QueryResponse { value: response.value, @@ -1911,12 +1936,15 @@ 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| Error::rpc(chain.config.rpc_addr.clone(), e))?; + .map_err(|e| Kind::Rpc(chain.config.rpc_addr.clone()).context(e))?; Ok(response) } @@ -1927,7 +1955,7 @@ async fn query_account(chain: &CosmosSdkChain, address: String) -> Result Result Result { - let account = AccountId::from_str(address) - .map_err(|e| Error::invalid_key_address(address.to_string(), e))?; + let account = + AccountId::from_str(address).map_err(|_| Kind::InvalidKeyAddress(address.to_string()))?; let encoded = bech32::encode(account_prefix, account.to_base32(), Variant::Bech32) - .map_err(Error::bech32_encoding)?; + .map_err(Kind::Bech32Encoding)?; Ok(encoded) } @@ -2012,7 +2040,6 @@ fn calculate_fee(adjusted_gas_amount: u64, gas_price: &GasPrice) -> Coin { } } -/// Multiply `a` with `f` and round to result up to the nearest integer. fn mul_ceil(a: u64, f: f64) -> u64 { use fraction::Fraction as F; diff --git a/relayer/src/chain/counterparty.rs b/relayer/src/chain/counterparty.rs index bb0d65e412..f3977f44e0 100644 --- a/relayer/src/chain/counterparty.rs +++ b/relayer/src/chain/counterparty.rs @@ -48,14 +48,20 @@ fn connection_on_destination( client_id: counterparty_client_id.to_string(), }; - let counterparty_connections = counterparty_chain - .query_client_connections(req) - .map_err(Error::relayer)?; + 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 + )) + })?; for counterparty_connection in counterparty_connections.into_iter() { let counterparty_connection_end = counterparty_chain .query_connection(&counterparty_connection, Height::zero()) - .map_err(Error::relayer)?; + .map_err(|e| Error::QueryFailed(format!("{}", e)))?; let local_connection_end = &counterparty_connection_end.counterparty(); if let Some(local_connection_id) = local_connection_end.connection_id() { @@ -74,7 +80,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(Error::relayer)?; + .map_err(|e| Error::QueryFailed(format!("{}", e)))?; Ok(connection_end.state) } else { @@ -125,10 +131,10 @@ pub fn channel_connection_client( ) -> Result { let channel_end = chain .query_channel(port_id, channel_id, Height::zero()) - .map_err(Error::relayer)?; + .map_err(|e| Error::QueryFailed(format!("{}", e)))?; if channel_end.state_matches(&State::Uninitialized) { - return Err(Error::channel_uninitialized( + return Err(Error::ChannelUninitialized( port_id.clone(), channel_id.clone(), chain.id(), @@ -138,14 +144,14 @@ pub fn channel_connection_client( let connection_id = channel_end .connection_hops() .first() - .ok_or_else(|| Error::missing_connection_hops(channel_id.clone(), chain.id()))?; + .ok_or_else(|| Error::MissingConnectionHops(channel_id.clone(), chain.id()))?; let connection_end = chain .query_connection(connection_id, Height::zero()) - .map_err(Error::relayer)?; + .map_err(|e| Error::QueryFailed(format!("{}", e)))?; if !connection_end.is_open() { - return Err(Error::connection_not_open( + return Err(Error::ConnectionNotOpen( connection_id.clone(), channel_id.clone(), chain.id(), @@ -155,7 +161,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(Error::relayer)?; + .map_err(|e| Error::QueryFailed(format!("{}", e)))?; let client = IdentifiedAnyClientState::new(client_id.clone(), client_state); let connection = IdentifiedConnectionEnd::new(connection_id.clone(), connection_end); @@ -186,7 +192,7 @@ fn fetch_channel_on_destination( let counterparty_channels = counterparty_chain .query_connection_channels(req) - .map_err(Error::relayer)?; + .map_err(|e| Error::QueryFailed(format!("{}", e)))?; for counterparty_channel in counterparty_channels.into_iter() { let local_channel_end = &counterparty_channel.channel_end.remote; @@ -223,7 +229,7 @@ pub fn channel_on_destination( remote_channel_id, Height::zero(), ) - .map_err(Error::relayer)?; + .map_err(|e| Error::QueryFailed(format!("{}", e)))?; Ok(Some(counterparty)) } else if let Some(remote_connection_id) = connection.end().counterparty().connection_id() { @@ -253,7 +259,7 @@ pub fn check_channel_counterparty( &target_pchan.channel_id, Height::zero(), ) - .map_err(|e| ChannelError::query(target_chain.id(), e))?; + .map_err(|e| ChannelError::QueryError(target_chain.id(), e))?; let counterparty = channel_end_dst.remote; match counterparty.channel_id { @@ -263,9 +269,9 @@ pub fn check_channel_counterparty( port_id: counterparty.port_id, }; if &actual != expected { - return Err(ChannelError::mismatch_channel_ends( - target_chain.id(), + return Err(ChannelError::MismatchingChannelEnds( target_pchan.clone(), + target_chain.id(), expected.clone(), actual, )); @@ -277,9 +283,9 @@ pub fn check_channel_counterparty( target_pchan, target_chain.id() ); - return Err(ChannelError::incomplete_channel_state( - target_chain.id(), + return Err(ChannelError::IncompleteChannelState( target_pchan.clone(), + target_chain.id(), )); } } diff --git a/relayer/src/chain/handle/prod.rs b/relayer/src/chain/handle/prod.rs index bc7f526836..41c93453e3 100644 --- a/relayer/src/chain/handle/prod.rs +++ b/relayer/src/chain/handle/prod.rs @@ -35,7 +35,11 @@ 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, keyring::KeyEntry}; +use crate::{ + connection::ConnectionMsgType, + error::{Error, Kind}, + keyring::KeyEntry, +}; use super::{reply_channel, ChainHandle, ChainRequest, ReplyTo, Subscription}; @@ -64,9 +68,11 @@ impl ProdChainHandle { let (sender, receiver) = reply_channel(); let input = f(sender); - self.runtime_sender.send(input).map_err(Error::send)?; + self.runtime_sender + .send(input) + .map_err(|e| Kind::Channel.context(e))?; - receiver.recv().map_err(Error::channel_receive)? + receiver.recv().map_err(|e| Kind::Channel.context(e))? } } diff --git a/relayer/src/chain/mock.rs b/relayer/src/chain/mock.rs index 1e8c2b8c9b..03defb81d3 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; +use crate::error::{Error, Kind}; use crate::event::monitor::{EventReceiver, EventSender, TxMonitorCmd}; use crate::keyring::{KeyEntry, KeyRing}; use crate::light_client::Verified; @@ -110,7 +110,10 @@ 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(Error::ics18)?; + let events = self + .context + .send(proto_msgs) + .map_err(|e| Kind::Rpc(self.config.rpc_addr.clone()).context(e))?; Ok(events) } @@ -147,9 +150,10 @@ impl Chain for MockChain { let any_state = self .context .query_client_full_state(client_id) - .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_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(client_state) } @@ -311,7 +315,7 @@ impl Chain for MockChain { after_misbehaviour: false, }, ) - .map_err(Error::ics07)?; + .map_err(|e| Kind::BuildClientStateFailure.context(e))?; Ok(client_state) } @@ -406,8 +410,8 @@ pub mod test_utils { max_gas: None, gas_price: GasPrice::new(0.001, "uatom".to_string()), gas_adjustment: None, - max_msg_num: Default::default(), - max_tx_size: Default::default(), + max_msg_num: None, + max_tx_size: None, clock_drift: Duration::from_secs(5), trusting_period: Duration::from_secs(14 * 24 * 60 * 60), // 14 days trust_threshold: Default::default(), diff --git a/relayer/src/chain/runtime.rs b/relayer/src/chain/runtime.rs index 8550267498..31e3588cb4 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, + error::{Error, Kind}, event::{ bus::EventBus, monitor::{EventBatch, EventReceiver, MonitorCmd, Result as MonitorResult, TxMonitorCmd}, @@ -175,19 +175,17 @@ impl ChainRuntime { }, Err(e) => { error!("received error via event bus: {}", e); - return Err(Error::channel_receive(e)); + return Err(Kind::Channel.into()); }, } }, recv(self.request_receiver) -> event => { match event { Ok(ChainRequest::Shutdown { reply_to }) => { - self.tx_monitor_cmd.send(MonitorCmd::Shutdown) - .map_err(Error::send)?; + self.tx_monitor_cmd.send(MonitorCmd::Shutdown).map_err(Kind::channel)?; let res = self.chain.shutdown(); - reply_to.send(res) - .map_err(Error::send)?; + reply_to.send(res).map_err(Kind::channel)?; break; } @@ -352,7 +350,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(Error::send)?; + reply_to.send(Ok(subscription)).map_err(Kind::channel)?; Ok(()) } @@ -364,7 +362,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.send_msgs(proto_msgs); - reply_to.send(result).map_err(Error::send)?; + reply_to.send(result).map_err(Kind::channel)?; Ok(()) } @@ -372,7 +370,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(Error::send)?; + reply_to.send(latest_height).map_err(Kind::channel)?; Ok(()) } @@ -380,7 +378,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(Error::send)?; + reply_to.send(result).map_err(Kind::channel)?; Ok(()) } @@ -388,7 +386,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(Error::send)?; + reply_to.send(result).map_err(Kind::channel)?; Ok(()) } @@ -396,7 +394,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(Error::send)?; + reply_to.send(Ok(result)).map_err(Kind::channel)?; Ok(()) } @@ -422,7 +420,7 @@ impl ChainRuntime { (header, support) }); - reply_to.send(result).map_err(Error::send)?; + reply_to.send(result).map_err(Kind::channel)?; Ok(()) } @@ -438,7 +436,7 @@ impl ChainRuntime { .build_client_state(height) .map(|cs| cs.wrap_any()); - reply_to.send(client_state).map_err(Error::send)?; + reply_to.send(client_state).map_err(Kind::channel)?; Ok(()) } @@ -458,7 +456,7 @@ impl ChainRuntime { .build_consensus_state(verified.target) .map(|cs| cs.wrap_any()); - reply_to.send(consensus_state).map_err(Error::send)?; + reply_to.send(consensus_state).map_err(Kind::channel)?; Ok(()) } @@ -474,7 +472,7 @@ impl ChainRuntime { .light_client .check_misbehaviour(update_event, &client_state); - reply_to.send(misbehaviour).map_err(Error::send)?; + reply_to.send(misbehaviour).map_err(Kind::channel)?; Ok(()) } @@ -497,7 +495,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(Error::send)?; + reply_to.send(result).map_err(Kind::channel)?; Ok(()) } @@ -509,7 +507,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.query_clients(request); - reply_to.send(result).map_err(Error::send)?; + reply_to.send(result).map_err(Kind::channel)?; Ok(()) } @@ -521,7 +519,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.query_client_connections(request); - reply_to.send(result).map_err(Error::send)?; + reply_to.send(result).map_err(Kind::channel)?; Ok(()) } @@ -537,7 +535,7 @@ impl ChainRuntime { .query_client_state(&client_id, height) .map(|cs| cs.wrap_any()); - reply_to.send(client_state).map_err(Error::send)?; + reply_to.send(client_state).map_err(Kind::channel)?; Ok(()) } @@ -552,7 +550,7 @@ impl ChainRuntime { .query_upgraded_client_state(height) .map(|(cl, proof)| (cl.wrap_any(), proof)); - reply_to.send(result).map_err(Error::send)?; + reply_to.send(result).map_err(Kind::channel)?; Ok(()) } @@ -564,7 +562,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let consensus_states = self.chain.query_consensus_states(request); - reply_to.send(consensus_states).map_err(Error::send)?; + reply_to.send(consensus_states).map_err(Kind::channel)?; Ok(()) } @@ -580,7 +578,7 @@ impl ChainRuntime { self.chain .query_consensus_state(client_id, consensus_height, query_height); - reply_to.send(consensus_state).map_err(Error::send)?; + reply_to.send(consensus_state).map_err(Kind::channel)?; Ok(()) } @@ -595,7 +593,7 @@ impl ChainRuntime { .query_upgraded_consensus_state(height) .map(|(cs, proof)| (cs.wrap_any(), proof)); - reply_to.send(result).map_err(Error::send)?; + reply_to.send(result).map_err(Kind::channel)?; Ok(()) } @@ -603,7 +601,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(Error::send)?; + reply_to.send(prefix).map_err(Kind::channel)?; Ok(()) } @@ -611,7 +609,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(Error::send)?; + reply_to.send(versions).map_err(Kind::channel)?; Ok(()) } @@ -624,7 +622,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let connection_end = self.chain.query_connection(&connection_id, height); - reply_to.send(connection_end).map_err(Error::send)?; + reply_to.send(connection_end).map_err(Kind::channel)?; Ok(()) } @@ -636,7 +634,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.query_connections(request); - reply_to.send(result).map_err(Error::send)?; + reply_to.send(result).map_err(Kind::channel)?; Ok(()) } @@ -648,7 +646,9 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.query_connection_channels(request); - reply_to.send(result).map_err(Error::send)?; + reply_to + .send(result) + .map_err(|e| Kind::Channel.context(e))?; Ok(()) } @@ -660,7 +660,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.query_channels(request); - reply_to.send(result).map_err(Error::send)?; + reply_to.send(result).map_err(Kind::channel)?; 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(Error::send)?; + reply_to.send(result).map_err(Kind::channel)?; Ok(()) } @@ -686,7 +686,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.query_channel_client_state(request); - reply_to.send(result).map_err(Error::send)?; + reply_to.send(result).map_err(Kind::channel)?; 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(Error::send)?; + reply_to.send(result).map_err(Kind::channel)?; Ok(()) } @@ -715,7 +715,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.proven_connection(&connection_id, height); - reply_to.send(result).map_err(Error::send)?; + reply_to.send(result).map_err(Kind::channel)?; 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(Error::send)?; + reply_to.send(result).map_err(Kind::channel)?; Ok(()) } @@ -748,7 +748,7 @@ impl ChainRuntime { .chain .build_channel_proofs(&port_id, &channel_id, height); - reply_to.send(result).map_err(Error::send)?; + reply_to.send(result).map_err(Kind::channel)?; 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(Error::send)?; + reply_to.send(result).map_err(Kind::channel)?; Ok(()) } @@ -778,7 +778,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.query_packet_commitments(request); - reply_to.send(result).map_err(Error::send)?; + reply_to.send(result).map_err(Kind::channel)?; Ok(()) } @@ -790,7 +790,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.query_unreceived_packets(request); - reply_to.send(result).map_err(Error::send)?; + reply_to.send(result).map_err(Kind::channel)?; Ok(()) } @@ -802,7 +802,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.query_packet_acknowledgements(request); - reply_to.send(result).map_err(Error::send)?; + reply_to.send(result).map_err(Kind::channel)?; Ok(()) } @@ -814,7 +814,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.query_unreceived_acknowledgements(request); - reply_to.send(result).map_err(Error::send)?; + reply_to.send(result).map_err(Kind::channel)?; Ok(()) } @@ -826,7 +826,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.query_next_sequence_receive(request); - reply_to.send(result).map_err(Error::send)?; + reply_to.send(result).map_err(Kind::channel)?; Ok(()) } @@ -838,7 +838,7 @@ impl ChainRuntime { ) -> Result<(), Error> { let result = self.chain.query_txs(request); - reply_to.send(result).map_err(Error::send)?; + reply_to.send(result).map_err(Kind::channel)?; Ok(()) } diff --git a/relayer/src/channel.rs b/relayer/src/channel.rs index 063bb4d552..d94576390c 100644 --- a/relayer/src/channel.rs +++ b/relayer/src/channel.rs @@ -1,8 +1,10 @@ #![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; @@ -23,11 +25,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::Error as SupervisorError; -use crate::util::retry::retry_with_index; +use crate::supervisor::Error as WorkerChannelError; use crate::util::retry::RetryResult; +use crate::util::retry::{retry_count, retry_with_index}; -pub mod error; +mod error; pub use error::ChannelError; mod retry_strategy { @@ -47,25 +49,6 @@ 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, @@ -136,15 +119,15 @@ impl Channel { let version = version.unwrap_or( b_side_chain .module_version(&a_port) - .map_err(|e| ChannelError::query(b_side_chain.id(), e))?, + .map_err(|e| ChannelError::QueryError(b_side_chain.id(), e))?, ); let src_connection_id = connection .src_connection_id() - .ok_or_else(|| ChannelError::missing_local_connection(connection.src_chain().id()))?; + .ok_or_else(|| ChannelError::MissingLocalConnection(connection.src_chain().id()))?; let dst_connection_id = connection .dst_connection_id() - .ok_or_else(|| ChannelError::missing_local_connection(connection.dst_chain().id()))?; + .ok_or_else(|| ChannelError::MissingLocalConnection(connection.dst_chain().id()))?; let mut channel = Self { ordering, @@ -175,28 +158,29 @@ 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::invalid_event(channel_open_event.clone()))?; + ) -> 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 + )) + })?; 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::query(counterparty_chain.id(), e))?; + .map_err(|e| ChannelError::QueryError(counterparty_chain.id(), e))?; let connection_id = channel_event_attributes.connection_id.clone(); - let connection = chain - .query_connection(&connection_id, Height::zero()) - .map_err(ChannelError::relayer)?; - + let connection = chain.query_connection(&connection_id, Height::zero())?; let connection_counterparty = connection.counterparty(); let counterparty_connection_id = connection_counterparty .connection_id() - .ok_or_else(ChannelError::missing_counterparty_connection)?; + .ok_or(ChannelError::MissingCounterpartyConnection)?; Ok(Channel { // The event does not include the channel ordering. @@ -231,32 +215,25 @@ impl Channel { counterparty_chain: Box, channel: WorkerChannelObject, height: Height, - ) -> Result<(Channel, State), ChannelError> { - let a_channel = chain - .query_channel(&channel.src_port_id, &channel.src_channel_id, height) - .map_err(ChannelError::relayer)?; + ) -> Result<(Channel, State), BoxError> { + let a_channel = + chain.query_channel(&channel.src_port_id, &channel.src_channel_id, height)?; let a_connection_id = a_channel.connection_hops().first().ok_or_else(|| { - ChannelError::supervisor(SupervisorError::missing_connection_hops( - channel.src_channel_id.clone(), - chain.id(), - )) + WorkerChannelError::MissingConnectionHops(channel.src_channel_id.clone(), chain.id()) })?; - let a_connection = chain - .query_connection(a_connection_id, Height::zero()) - .map_err(ChannelError::relayer)?; - + let a_connection = chain.query_connection(a_connection_id, Height::zero())?; let b_connection_id = a_connection .counterparty() .connection_id() .cloned() .ok_or_else(|| { - ChannelError::supervisor(SupervisorError::channel_connection_uninitialized( + WorkerChannelError::ChannelConnectionUninitialized( channel.src_channel_id.clone(), chain.id(), a_connection.counterparty().clone(), - )) + ) })?; let mut handshake_channel = Channel { @@ -285,9 +262,8 @@ impl Channel { pagination: ibc_proto::cosmos::base::query::pagination::all(), }; - let channels: Vec = counterparty_chain - .query_connection_channels(req) - .map_err(ChannelError::relayer)?; + let channels: Vec = + counterparty_chain.query_connection_channels(req)?; for chan in channels { if let Some(remote_channel_id) = chan.channel_end.remote.channel_id() { @@ -353,7 +329,13 @@ impl Channel { } fn do_chan_open_init_and_send(&mut self) -> Result<(), ChannelError> { - let event = self.flipped().build_chan_open_init_and_send()?; + let event = self + .flipped() + .build_chan_open_init_and_send() + .map_err(|e| { + error!("Failed ChanInit {:?}: {:?}", self.a_side, e); + e + })?; info!("done {} => {:#?}\n", self.src_chain().id(), event); @@ -371,11 +353,11 @@ impl Channel { }) .map_err(|err| { error!("failed to open channel after {} retries", err); - - from_retry_error( - err, - format!("Failed to finish channel open init for {:?}", self), - ) + ChannelError::Failed(format!( + "Failed to finish channel open init in {} iterations for {:?}", + retry_count(&err), + self + )) })?; Ok(()) @@ -400,11 +382,11 @@ impl Channel { }) .map_err(|err| { error!("failed to open channel after {} retries", err); - - from_retry_error( - err, - format!("Failed to finish channel open try for {:?}", self), - ) + ChannelError::Failed(format!( + "Failed to finish channel open try in {} iterations for {:?}", + retry_count(&err), + self + )) })?; Ok(()) @@ -428,11 +410,11 @@ impl Channel { fn query_channel_states(channel: &Channel) -> Result<(State, State), ChannelError> { let src_channel_id = channel .src_channel_id() - .ok_or_else(ChannelError::missing_local_channel_id)?; + .ok_or(ChannelError::MissingLocalChannelId)?; let dst_channel_id = channel .dst_channel_id() - .ok_or_else(ChannelError::missing_counterparty_connection)?; + .ok_or(ChannelError::MissingCounterpartyChannelId)?; debug!( "do_chan_open_finalize for src_channel_id: {}, dst_channel_id: {}", @@ -444,11 +426,11 @@ impl Channel { .src_chain() .query_channel(channel.src_port_id(), src_channel_id, Height::zero()) .map_err(|e| { - ChannelError::handshake_finalize( + ChannelError::HandshakeFinalize( channel.src_port_id().clone(), src_channel_id.clone(), channel.src_chain().id(), - e, + e.to_string(), ) })?; @@ -456,11 +438,11 @@ impl Channel { .dst_chain() .query_channel(channel.dst_port_id(), dst_channel_id, Height::zero()) .map_err(|e| { - ChannelError::handshake_finalize( + ChannelError::HandshakeFinalize( channel.dst_port_id().clone(), dst_channel_id.clone(), channel.dst_chain().id(), - e, + e.to_string(), ) })?; @@ -482,7 +464,7 @@ impl Channel { // One more step (confirm) left. // Returning error signals that the caller should retry. - Err(ChannelError::partial_open_handshake(a1, b1)) + Err(ChannelError::PartialOpenHandshake(a2, b2)) } } @@ -559,10 +541,11 @@ impl Channel { retry_with_index(retry_strategy::default(), |_| self.do_chan_open_finalize()).map_err( |err| { error!("failed to open channel after {} retries", err); - from_retry_error( - err, - format!("Failed to finish channel handshake for {:?}", self), - ) + ChannelError::Failed(format!( + "Failed to finish channel handshake in {} iterations for {:?}", + retry_count(&err), + self + )) }, )?; @@ -580,18 +563,28 @@ impl Channel { // Source channel ID must be specified let channel_id = self .src_channel_id() - .ok_or_else(ChannelError::missing_local_channel_id)?; + .ok_or(ChannelError::MissingLocalChannelId)?; let channel_deps = channel_connection_client(self.src_chain().as_ref(), self.src_port_id(), channel_id) - .map_err(|e| ChannelError::query_channel(channel_id.clone(), e))?; + .map_err(|_| { + ChannelError::Failed(format!( + "failed to query the channel dependecies for {}", + channel_id + )) + })?; channel_state_on_destination( &channel_deps.channel, &channel_deps.connection, self.dst_chain().as_ref(), ) - .map_err(|e| ChannelError::query_channel(channel_id.clone(), e)) + .map_err(|_| { + ChannelError::Failed(format!( + "failed to query the channel state on destination for {}", + channel_id + )) + }) } pub fn handshake_step(&mut self, state: State) -> Result, ChannelError> { @@ -640,7 +633,7 @@ impl Channel { ); client.build_update_client(height).map_err(|e| { - ChannelError::client_operation(self.dst_client_id().clone(), self.dst_chain().id(), e) + ChannelError::ClientOperation(self.dst_client_id().clone(), self.dst_chain().id(), e) }) } @@ -649,28 +642,47 @@ 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::query(self.dst_chain().id(), e))?, - )) + 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 + )) + })? + )) } /// 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::query(self.src_chain().id(), e))?, - )) + 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 + )) + })? + )) } pub fn build_chan_open_init(&self) -> Result, ChannelError> { - let signer = self - .dst_chain() - .get_signer() - .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 counterparty = Counterparty::new(self.src_port_id().clone(), None); @@ -698,7 +710,7 @@ impl Channel { let events = self .dst_chain() .send_msgs(dst_msgs) - .map_err(|e| ChannelError::submit(self.dst_chain().id(), e))?; + .map_err(|e| ChannelError::SubmitError(self.dst_chain().id(), e))?; // Find the relevant event for channel open init let result = events @@ -708,13 +720,18 @@ impl Channel { || matches!(event, IbcEvent::ChainError(_)) }) .ok_or_else(|| { - ChannelError::missing_event("no chan init event was in the response".to_string()) + ChannelError::Failed("no chan init event was in the response".to_string()) })?; match result { IbcEvent::OpenInitChannel(_) => Ok(result), - IbcEvent::ChainError(e) => Err(ChannelError::tx_response(e)), - _ => Err(ChannelError::invalid_event(result)), + IbcEvent::ChainError(e) => { + Err(ChannelError::Failed(format!("tx response error: {}", e))) + } + _ => Err(ChannelError::Failed(format!( + "unexpected IBC event: {}", + result + ))), } } @@ -729,7 +746,7 @@ impl Channel { // Destination channel ID must be specified let dst_channel_id = self .dst_channel_id() - .ok_or_else(ChannelError::missing_counterparty_channel_id)?; + .ok_or(ChannelError::MissingCounterpartyChannelId)?; // If there is a channel present on the destination chain, it should look like this: let counterparty = @@ -755,12 +772,14 @@ impl Channel { let dst_channel = self .dst_chain() .query_channel(self.dst_port_id(), dst_channel_id, Height::zero()) - .map_err(|e| ChannelError::query(self.dst_chain().id(), e))?; + .map_err(|e| ChannelError::QueryError(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::missing_channel_on_destination()); + return Err(ChannelError::Failed( + "missing channel on destination chain".to_string(), + )); } check_destination_channel_state( @@ -776,38 +795,40 @@ impl Channel { // Source channel ID must be specified let src_channel_id = self .src_channel_id() - .ok_or_else(ChannelError::missing_local_channel_id)?; + .ok_or(ChannelError::MissingLocalChannelId)?; // 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::query(self.src_chain().id(), e))?; + .map_err(|e| ChannelError::QueryError(self.src_chain().id(), e))?; if src_channel.counterparty().port_id() != self.dst_port_id() { - return Err(ChannelError::mismatch_port( + return Err(ChannelError::Failed(format!( + "channel open try to chain `{}` and destination port `{}` does not match \ + the source chain `{}` counterparty port `{}` for channel_id {}", self.dst_chain().id(), - self.dst_port_id().clone(), + self.dst_port_id(), self.src_chain().id(), - src_channel.counterparty().port_id.clone(), - src_channel_id.clone(), - )); + src_channel.counterparty().port_id, + src_channel_id + ))); } // Connection must exist on destination self.dst_chain() .query_connection(self.dst_connection_id(), Height::zero()) - .map_err(|e| ChannelError::query(self.dst_chain().id(), e))?; + .map_err(|e| ChannelError::QueryError(self.dst_chain().id(), e))?; let query_height = self .src_chain() .query_latest_height() - .map_err(|e| ChannelError::query(self.src_chain().id(), e))?; + .map_err(|e| ChannelError::QueryError(self.src_chain().id(), e))?; let proofs = self .src_chain() .build_channel_proofs(self.src_port_id(), src_channel_id, query_height) - .map_err(ChannelError::channel_proof)?; + .map_err(|e| ChannelError::Failed(format!("failed to build channel proofs: {}", e)))?; // Build message(s) to update client on destination let mut msgs = self.build_update_client_on_dst(proofs.height())?; @@ -824,10 +845,13 @@ impl Channel { ); // Get signer - let signer = self - .dst_chain() - .get_signer() - .map_err(|e| ChannelError::fetch_signer(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 previous_channel_id = if src_channel.counterparty().channel_id.is_none() { self.b_side.channel_id.clone() @@ -855,7 +879,7 @@ impl Channel { let events = self .dst_chain() .send_msgs(dst_msgs) - .map_err(|e| ChannelError::submit(self.dst_chain().id(), e))?; + .map_err(|e| ChannelError::SubmitError(self.dst_chain().id(), e))?; // Find the relevant event for channel open try let result = events @@ -865,13 +889,18 @@ impl Channel { || matches!(event, IbcEvent::ChainError(_)) }) .ok_or_else(|| { - ChannelError::missing_event("no chan try event was in the response".to_string()) + ChannelError::Failed("no chan try event was in the response".to_string()) })?; match result { IbcEvent::OpenTryChannel(_) => Ok(result), - IbcEvent::ChainError(e) => Err(ChannelError::tx_response(e)), - _ => Err(ChannelError::invalid_event(result)), + IbcEvent::ChainError(e) => { + Err(ChannelError::Failed(format!("tx response error: {}", e))) + } + _ => Err(ChannelError::Failed(format!( + "unexpected IBC event: {}", + result + ))), } } @@ -879,10 +908,10 @@ impl Channel { // Source and destination channel IDs must be specified let src_channel_id = self .src_channel_id() - .ok_or_else(ChannelError::missing_local_channel_id)?; + .ok_or(ChannelError::MissingLocalChannelId)?; let dst_channel_id = self .dst_channel_id() - .ok_or_else(ChannelError::missing_counterparty_channel_id)?; + .ok_or(ChannelError::MissingCounterpartyChannelId)?; // Check that the destination chain will accept the message self.validated_expected_channel(ChannelMsgType::OpenAck)?; @@ -890,31 +919,39 @@ 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::query(self.src_chain().id(), e))?; + .map_err(|e| ChannelError::QueryError(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::query(self.dst_chain().id(), e))?; + .map_err(|e| ChannelError::QueryError(self.dst_chain().id(), e))?; let query_height = self .src_chain() .query_latest_height() - .map_err(|e| ChannelError::query(self.src_chain().id(), e))?; + .map_err(|e| ChannelError::QueryError(self.src_chain().id(), e))?; let proofs = self .src_chain() .build_channel_proofs(self.src_port_id(), src_channel_id, query_height) - .map_err(ChannelError::channel_proof)?; + .map_err(|e| { + ChannelError::Failed(format!( + "failed while building the channel proofs at ACK step with error: {}", + e + )) + })?; // 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::fetch_signer(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 + )) + })?; // Build the domain type message let new_msg = MsgChannelOpenAck { @@ -937,7 +974,7 @@ impl Channel { let events = channel .dst_chain() .send_msgs(dst_msgs) - .map_err(|e| ChannelError::submit(channel.dst_chain().id(), e))?; + .map_err(|e| ChannelError::SubmitError(channel.dst_chain().id(), e))?; // Find the relevant event for channel open ack let event = events @@ -947,7 +984,7 @@ impl Channel { || matches!(event, IbcEvent::ChainError(_)) }) .ok_or_else(|| { - ChannelError::missing_event("no chan ack event was in the response".to_string()) + ChannelError::Failed("no chan ack event was in the response".to_string()) })?; match event { @@ -960,8 +997,13 @@ impl Channel { Ok(event) } - IbcEvent::ChainError(e) => Err(ChannelError::tx_response(e)), - _ => Err(ChannelError::invalid_event(event)), + IbcEvent::ChainError(e) => { + Err(ChannelError::Failed(format!("tx response error: {}", e))) + } + _ => Err(ChannelError::Failed(format!( + "unexpected IBC event: {}", + event + ))), } } @@ -975,10 +1017,10 @@ impl Channel { // Source and destination channel IDs must be specified let src_channel_id = self .src_channel_id() - .ok_or_else(ChannelError::missing_local_channel_id)?; + .ok_or(ChannelError::MissingLocalChannelId)?; let dst_channel_id = self .dst_channel_id() - .ok_or_else(ChannelError::missing_counterparty_channel_id)?; + .ok_or(ChannelError::MissingCounterpartyChannelId)?; // Check that the destination chain will accept the message self.validated_expected_channel(ChannelMsgType::OpenConfirm)?; @@ -986,31 +1028,34 @@ 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::query(self.src_chain().id(), e))?; + .map_err(|e| ChannelError::QueryError(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::query(self.dst_chain().id(), e))?; + .map_err(|e| ChannelError::QueryError(self.dst_chain().id(), e))?; let query_height = self .src_chain() .query_latest_height() - .map_err(|e| ChannelError::query(self.src_chain().id(), e))?; + .map_err(|e| ChannelError::QueryError(self.src_chain().id(), e))?; let proofs = self .src_chain() .build_channel_proofs(self.src_port_id(), src_channel_id, query_height) - .map_err(ChannelError::channel_proof)?; + .map_err(|e| ChannelError::Failed(format!("failed to build channel proofs: {}", e)))?; // 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::fetch_signer(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 + )) + })?; // Build the domain type message let new_msg = MsgChannelOpenConfirm { @@ -1033,7 +1078,7 @@ impl Channel { let events = channel .dst_chain() .send_msgs(dst_msgs) - .map_err(|e| ChannelError::submit(channel.dst_chain().id(), e))?; + .map_err(|e| ChannelError::SubmitError(channel.dst_chain().id(), e))?; // Find the relevant event for channel open confirm let event = events @@ -1043,9 +1088,7 @@ impl Channel { || matches!(event, IbcEvent::ChainError(_)) }) .ok_or_else(|| { - ChannelError::missing_event( - "no chan confirm event was in the response".to_string(), - ) + ChannelError::Failed("no chan confirm event was in the response".to_string()) })?; match event { @@ -1053,8 +1096,13 @@ impl Channel { info!("done {} => {:#?}\n", channel.dst_chain().id(), event); Ok(event) } - IbcEvent::ChainError(e) => Err(ChannelError::tx_response(e)), - _ => Err(ChannelError::invalid_event(event)), + IbcEvent::ChainError(e) => { + Err(ChannelError::Failed(format!("tx response error: {}", e))) + } + _ => Err(ChannelError::Failed(format!( + "unexpected IBC event: {}", + event + ))), } } @@ -1068,17 +1116,20 @@ impl Channel { // Destination channel ID must be specified let dst_channel_id = self .dst_channel_id() - .ok_or_else(ChannelError::missing_counterparty_channel_id)?; + .ok_or(ChannelError::MissingCounterpartyChannelId)?; // Channel must exist on destination self.dst_chain() .query_channel(self.dst_port_id(), dst_channel_id, Height::zero()) - .map_err(|e| ChannelError::query(self.dst_chain().id(), e))?; + .map_err(|e| ChannelError::QueryError(self.dst_chain().id(), e))?; - let signer = self - .dst_chain() - .get_signer() - .map_err(|e| ChannelError::fetch_signer(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 + )) + })?; // Build the domain type message let new_msg = MsgChannelCloseInit { @@ -1096,7 +1147,7 @@ impl Channel { let events = self .dst_chain() .send_msgs(dst_msgs) - .map_err(|e| ChannelError::submit(self.dst_chain().id(), e))?; + .map_err(|e| ChannelError::SubmitError(self.dst_chain().id(), e))?; // Find the relevant event for channel close init let result = events @@ -1106,13 +1157,19 @@ impl Channel { || matches!(event, IbcEvent::ChainError(_)) }) .ok_or_else(|| { - ChannelError::missing_event("no chan init event was in the response".to_string()) + ChannelError::Failed("no chan init event was in the response".to_string()) })?; match result { IbcEvent::CloseInitChannel(_) => Ok(result), - IbcEvent::ChainError(e) => Err(ChannelError::tx_response(e)), - _ => Err(ChannelError::invalid_event(result)), + IbcEvent::ChainError(e) => Err(ChannelError::Failed(format!( + "tx response event consists of an error: {}", + e + ))), + _ => Err(ChannelError::Failed(format!( + "unexpected IBC event: {}", + result + ))), } } @@ -1120,10 +1177,10 @@ impl Channel { // Source and destination channel IDs must be specified let src_channel_id = self .src_channel_id() - .ok_or_else(ChannelError::missing_local_channel_id)?; + .ok_or(ChannelError::MissingLocalChannelId)?; let dst_channel_id = self .dst_channel_id() - .ok_or_else(ChannelError::missing_counterparty_channel_id)?; + .ok_or(ChannelError::MissingCounterpartyChannelId)?; // Check that the destination chain will accept the message self.validated_expected_channel(ChannelMsgType::CloseConfirm)?; @@ -1131,31 +1188,34 @@ 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::query(self.src_chain().id(), e))?; + .map_err(|e| ChannelError::QueryError(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::query(self.dst_chain().id(), e))?; + .map_err(|e| ChannelError::QueryError(self.dst_chain().id(), e))?; let query_height = self .src_chain() .query_latest_height() - .map_err(|e| ChannelError::query(self.src_chain().id(), e))?; + .map_err(|e| ChannelError::QueryError(self.src_chain().id(), e))?; let proofs = self .src_chain() .build_channel_proofs(self.src_port_id(), src_channel_id, query_height) - .map_err(ChannelError::channel_proof)?; + .map_err(|e| ChannelError::Failed(format!("failed to build channel proofs: {}", e)))?; // 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::fetch_signer(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 + )) + })?; // Build the domain type message let new_msg = MsgChannelCloseConfirm { @@ -1175,7 +1235,7 @@ impl Channel { let events = self .dst_chain() .send_msgs(dst_msgs) - .map_err(|e| ChannelError::submit(self.dst_chain().id(), e))?; + .map_err(|e| ChannelError::SubmitError(self.dst_chain().id(), e))?; // Find the relevant event for channel close confirm let result = events @@ -1185,13 +1245,18 @@ impl Channel { || matches!(event, IbcEvent::ChainError(_)) }) .ok_or_else(|| { - ChannelError::missing_event("no chan confirm event was in the response".to_string()) + ChannelError::Failed("no chan confirm event was in the response".to_string()) })?; match result { IbcEvent::CloseConfirmChannel(_) => Ok(result), - IbcEvent::ChainError(e) => Err(ChannelError::tx_response(e)), - _ => Err(ChannelError::invalid_event(result)), + IbcEvent::ChainError(e) => { + Err(ChannelError::Failed(format!("tx response error: {}", e))) + } + _ => Err(ChannelError::Failed(format!( + "unexpected IBC event: {}", + result + ))), } } } @@ -1204,7 +1269,7 @@ pub fn extract_channel_id(event: &IbcEvent) -> Result<&ChannelId, ChannelError> IbcEvent::OpenConfirmChannel(ev) => ev.channel_id(), _ => None, } - .ok_or_else(|| ChannelError::missing_event("cannot extract channel_id from result".to_string())) + .ok_or_else(|| ChannelError::Failed("cannot extract channel_id from result".to_string())) } /// Enumeration of proof carrying ICS4 message, helper for relayer. @@ -1237,6 +1302,9 @@ fn check_destination_channel_state( if good_state && good_connection_hops && good_channel_port_ids { Ok(()) } else { - Err(ChannelError::channel_already_exist(channel_id)) + Err(ChannelError::Failed(format!( + "channel {} already exist in an incompatible state", + channel_id + ))) } } diff --git a/relayer/src/channel/error.rs b/relayer/src/channel/error.rs index 65ec15e9e4..3b179c1fb7 100644 --- a/relayer/src/channel/error.rs +++ b/relayer/src/channel/error.rs @@ -1,195 +1,48 @@ -use flex_error::define_error; -use ibc::events::IbcEvent; -use ibc::ics02_client::error::Error as ClientError; +use thiserror::Error; + 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) }, - Submit - { chain_id: ChainId } - [ Error ] - |_| { "failed during a transaction submission step to chain id {0}" }, +#[derive(Debug, Error)] +pub enum ChannelError { + #[error("failed with underlying cause: {0}")] + Failed(String), - 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 local channel id")] + MissingLocalChannelId, - 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 channel id")] + MissingCounterpartyChannelId, - 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("failed due to missing counterparty connection")] + MissingCounterpartyConnection, - ChannelAlreadyExist - { channel_id: ChannelId } - |e| { format_args!("channel {} already exist in an incompatible state", e.channel_id) }, + #[error("channel constructor failed due to missing connection id on chain id {0}")] + MissingLocalConnection(ChainId), - 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 an operation on client ({0}) hosted by chain ({1}) with error: {2}")] + ClientOperation(ClientId, ChainId, ForeignClientError), - 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 query to chain id {0} with underlying error: {1}")] + QueryError(ChainId, Error), - MissingEvent - { - description: String - } - | e | { - format_args!("missing event: {}", e.description) - }, + #[error( + "failed during a transaction submission step to chain id {0} with underlying error: {1}" + )] + SubmitError(ChainId, Error), - 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("failed to finalize a channel open handshake while querying for channel end {0}/{1} on chain chain {2}: {3}")] + HandshakeFinalize(PortId, ChannelId, ChainId, String), - RetryInternal - { reason: String } - | e | { - format_args!("Encountered internal error during retry: {}", - e.reason) - }, + #[error("the channel is partially open ({0}, {1})")] + PartialOpenHandshake(State, State), - TxResponse - { reason: String } - | e | { - format_args!("tx response error: {}", - e.reason) - }, + #[error("channel {0} on chain {1} has no counterparty channel id")] + IncompleteChannelState(PortChannelId, ChainId), - InvalidEvent - { event: IbcEvent } - | e | { - format_args!("channel object cannot be built from event: {}", - e.event) - }, - } + #[error("channel {0} on chain {1} expected to have counterparty {2} (but instead has {3})")] + MismatchingChannelEnds(PortChannelId, ChainId, PortChannelId, PortChannelId), } diff --git a/relayer/src/config.rs b/relayer/src/config.rs index cce08b279e..03b58b1504 100644 --- a/relayer/src/config.rs +++ b/relayer/src/config.rs @@ -1,7 +1,6 @@ //! Relayer configuration pub mod reload; -pub mod types; use std::collections::{HashMap, HashSet}; use std::{fmt, fs, fs::File, io::Write, path::Path, time::Duration}; @@ -12,8 +11,7 @@ use tendermint_light_client::types::TrustThreshold; use ibc::ics24_host::identifier::{ChainId, ChannelId, PortId}; use ibc::timestamp::ZERO_DURATION; -use crate::config::types::{MaxMsgNum, MaxTxSize}; -use crate::error::Error; +use crate::error; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GasPrice { @@ -256,10 +254,8 @@ pub struct ChainConfig { pub store_prefix: String, pub max_gas: Option, pub gas_adjustment: Option, - #[serde(default)] - pub max_msg_num: MaxMsgNum, - #[serde(default)] - pub max_tx_size: MaxTxSize, + pub max_msg_num: Option, + pub max_tx_size: Option, #[serde(default = "default::clock_drift", with = "humantime_serde")] pub clock_drift: Duration, #[serde(default = "default::trusting_period", with = "humantime_serde")] @@ -274,31 +270,34 @@ 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(Error::config_io)?; +pub fn load(path: impl AsRef) -> Result { + let config_toml = + std::fs::read_to_string(&path).map_err(|e| error::Kind::ConfigIo.context(e))?; - let config = toml::from_str::(&config_toml[..]).map_err(Error::config_decode)?; + let config = + toml::from_str::(&config_toml[..]).map_err(|e| error::Kind::Config.context(e))?; Ok(config) } /// Serialize the given `Config` as TOML to the given config file. -pub fn store(config: &Config, path: impl AsRef) -> Result<(), Error> { +pub fn store(config: &Config, path: impl AsRef) -> Result<(), error::Error> { let mut file = if path.as_ref().exists() { fs::OpenOptions::new().write(true).truncate(true).open(path) } else { File::create(path) } - .map_err(Error::config_io)?; + .map_err(|e| error::Kind::Config.context(e))?; 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> { - let toml_config = toml::to_string_pretty(&config).map_err(Error::config_encode)?; +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))?; - writeln!(writer, "{}", toml_config).map_err(Error::config_io)?; + writeln!(writer, "{}", toml_config).map_err(|e| error::Kind::Config.context(e))?; Ok(()) } diff --git a/relayer/src/config/types.rs b/relayer/src/config/types.rs deleted file mode 100644 index 0f2b58c6cd..0000000000 --- a/relayer/src/config/types.rs +++ /dev/null @@ -1,101 +0,0 @@ -//! Configuration-related types. -//! -//! Implements defaults, as well as serializing and -//! deserializing with upper-bound verification. - -use serde::de::Unexpected; -use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; - -#[derive(Debug, Clone, Copy)] -pub struct MaxMsgNum(usize); - -impl MaxMsgNum { - const DEFAULT: usize = 30; - const MAX_BOUND: usize = 100; -} - -impl Default for MaxMsgNum { - fn default() -> Self { - Self(Self::DEFAULT) - } -} - -impl<'de> Deserialize<'de> for MaxMsgNum { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let u = usize::deserialize(deserializer)?; - - if u > Self::MAX_BOUND { - return Err(D::Error::invalid_value( - Unexpected::Unsigned(u as u64), - &format!("a usize less than {}", Self::MAX_BOUND).as_str(), - )); - } - - Ok(MaxMsgNum(u)) - } -} - -impl Serialize for MaxMsgNum { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.0.serialize(serializer) - } -} - -impl From for usize { - fn from(m: MaxMsgNum) -> Self { - m.0 - } -} - -#[derive(Debug, Clone, Copy)] -pub struct MaxTxSize(usize); - -impl MaxTxSize { - const DEFAULT: usize = 2 * 1048576; // 2 MBytes - const MAX_BOUND: usize = 8 * 1048576; // 8 MBytes -} - -impl Default for MaxTxSize { - fn default() -> Self { - Self(Self::DEFAULT) - } -} - -impl<'de> Deserialize<'de> for MaxTxSize { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let u = usize::deserialize(deserializer)?; - - if u > Self::MAX_BOUND { - return Err(D::Error::invalid_value( - Unexpected::Unsigned(u as u64), - &format!("a usize less than {}", Self::MAX_BOUND).as_str(), - )); - } - - Ok(MaxTxSize(u)) - } -} - -impl Serialize for MaxTxSize { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.0.serialize(serializer) - } -} - -impl From for usize { - fn from(m: MaxTxSize) -> Self { - m.0 - } -} diff --git a/relayer/src/connection.rs b/relayer/src/connection.rs index 155a0660b1..0c945a5802 100644 --- a/relayer/src/connection.rs +++ b/relayer/src/connection.rs @@ -2,10 +2,11 @@ use std::time::Duration; use crate::chain::counterparty::connection_state_on_destination; use crate::util::retry::RetryResult; -use flex_error::define_error; +use anomaly::BoxError; 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}; @@ -23,171 +24,39 @@ use ibc::timestamp::ZERO_DURATION; use ibc::tx_msg::Msg; use crate::chain::handle::ChainHandle; -use crate::error::Error as RelayerError; +use crate::error::Error; 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; -define_error! { - ConnectionError { - Relayer - [ RelayerError ] - |_| { "relayer error" }, +#[derive(Debug, Error)] +pub enum ConnectionError { + #[error("failed with underlying cause: {0}")] + Failed(String), - MissingLocalConnectionId - |_| { "failed due to missing local channel id" }, + #[error("connection constructor error: {0}")] + ConstructorFailed(String), - 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) - }, - - TxResponse - { event: String } - |e| { - format!("tx response event consists of an error: {}", - e.event) - }, - - 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) - }, - - 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" }, + #[error("failed due to missing local channel id")] + MissingLocalConnectionId, - MissingConnectionConfirmEvent - |_| { "no conn confirm event was in the response" }, + #[error("failed due to missing counterparty connection id ")] + MissingCounterpartyConnectionId, - ConnectionProof - [ RelayerError ] - |_| { "failed to build connection proofs" }, + #[error("failed during a query to chain id {0} due to underlying error: {1}")] + QueryError(ChainId, Error), - ConnectionAlreadyExist - { connection_id: ConnectionId } - |e| { - format!("connection {} already exist in an incompatible state", e.connection_id) - }, + #[error("failed during an operation on client ({0}) hosted by chain ({1}) with error {2}")] + ClientOperation(ClientId, ChainId, ForeignClientError), - } + #[error( + "failed during a transaction submission step to chain id {0} with underlying error: {1}" + )] + SubmitError(ChainId, Error), } #[derive(Clone, Debug)] @@ -253,7 +122,10 @@ impl Connection { // Validate the delay period against the upper bound if delay_period > MAX_PACKET_DELAY { - return Err(ConnectionError::max_delay_period(delay_period)); + return Err(ConnectionError::ConstructorFailed(format!( + "Invalid delay period '{:?}': should be max '{:?}'", + delay_period, MAX_PACKET_DELAY + ))); } let mut c = Self { @@ -278,10 +150,15 @@ 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::invalid_event(connection_open_event.clone()))?; + .ok_or_else(|| { + ConnectionError::Failed(format!( + "a connection object cannot be built from {}", + connection_open_event + )) + })?; let connection_id = connection_event_attributes.connection_id.clone(); @@ -311,11 +188,8 @@ impl Connection { counterparty_chain: Box, connection: WorkerConnectionObject, height: Height, - ) -> Result<(Connection, State), ConnectionError> { - let a_connection = chain - .query_connection(&connection.src_connection_id, height) - .map_err(ConnectionError::relayer)?; - + ) -> Result<(Connection, State), BoxError> { + let a_connection = chain.query_connection(&connection.src_connection_id, height)?; let client_id = a_connection.client_id(); let delay_period = a_connection.delay_period(); @@ -341,9 +215,8 @@ impl Connection { let req = QueryConnectionsRequest { pagination: ibc_proto::cosmos::base::query::pagination::all(), }; - let connections: Vec = counterparty_chain - .query_connections(req) - .map_err(ConnectionError::relayer)?; + let connections: Vec = + counterparty_chain.query_connections(req)?; for conn in connections { if !conn @@ -375,21 +248,22 @@ impl Connection { // Validate the connection end if conn_end_a.end().client_id().ne(a_client.id()) { - return Err(ConnectionError::connection_client_id_mismatch( - conn_end_a.end().client_id().clone(), - a_client.id().clone(), - )); + 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() + ))); } if 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(), - )); + 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() + ))); } if !conn_end_a.end().state_matches(&State::Open) { - return Err(ConnectionError::connection_not_open( - *conn_end_a.end().state(), - )); + return Err(ConnectionError::ConstructorFailed(format!( + "the connection end is expected to be in state 'Open'; found state: {:?}", + conn_end_a.end().state() + ))); } let b_conn_id = conn_end_a .end() @@ -397,9 +271,10 @@ impl Connection { .connection_id() .cloned() .ok_or_else(|| { - ConnectionError::missing_counterparty_connection_id_field( - conn_end_a.end().counterparty().clone(), - ) + ConnectionError::ConstructorFailed(format!( + "the connection end has no connection id field in the counterparty: {:?}", + conn_end_a.end().counterparty() + )) })?; let c = Connection { @@ -425,17 +300,19 @@ impl Connection { b_client: &ForeignClient, ) -> Result<(), ConnectionError> { if a_client.src_chain().id() != b_client.dst_chain().id() { - return Err(ConnectionError::chain_id_mismatch( + return Err(ConnectionError::ConstructorFailed(format!( + "the source chain of client a ({}) does not not match the destination chain of client b ({})", 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::chain_id_mismatch( + return Err(ConnectionError::ConstructorFailed(format!( + "the destination chain of client a ({}) does not not match the source chain of client b ({})", a_client.dst_chain().id(), - b_client.src_chain().id(), - )); + b_client.src_chain().id() + ))); } Ok(()) @@ -520,10 +397,10 @@ impl Connection { let src_connection_id = self .src_connection_id() - .ok_or_else(ConnectionError::missing_local_connection_id)?; + .ok_or(ConnectionError::MissingLocalConnectionId)?; let dst_connection_id = self .dst_connection_id() - .ok_or_else(ConnectionError::missing_counterparty_connection_id)?; + .ok_or(ConnectionError::MissingCounterpartyConnectionId)?; // Continue loop if query error let a_connection = a_chain.query_connection(src_connection_id, Height::zero()); @@ -574,14 +451,17 @@ impl Connection { } } - Err(ConnectionError::max_retry()) + Err(ConnectionError::Failed(format!( + "Failed to finish connection handshake in {:?} iterations", + MAX_RETRIES + ))) } pub fn counterparty_state(&self) -> Result { // Source connection ID must be specified let connection_id = self .src_connection_id() - .ok_or_else(ConnectionError::missing_local_connection_id)?; + .ok_or(ConnectionError::MissingLocalConnectionId)?; let connection_end = self .src_chain() @@ -593,8 +473,12 @@ impl Connection { connection_id: connection_id.clone(), }; - connection_state_on_destination(connection, self.dst_chain().as_ref()) - .map_err(ConnectionError::supervisor) + 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 + )) + }) } pub fn handshake_step(&mut self, state: State) -> Result, ConnectionError> { @@ -644,12 +528,12 @@ impl Connection { ) -> Result { let dst_connection_id = self .dst_connection_id() - .ok_or_else(ConnectionError::missing_counterparty_connection_id)?; + .ok_or(ConnectionError::MissingCounterpartyConnectionId)?; let prefix = self .src_chain() .query_commitment_prefix() - .map_err(|e| ConnectionError::chain_query(self.src_chain().id(), e))?; + .map_err(|e| ConnectionError::QueryError(self.src_chain().id(), e))?; // If there is a connection present on the destination chain, it should look like this: let counterparty = Counterparty::new( @@ -668,7 +552,7 @@ impl Connection { let versions = self .src_chain() .query_compatible_versions() - .map_err(|e| ConnectionError::chain_query(self.src_chain().id(), e))?; + .map_err(|e| ConnectionError::QueryError(self.src_chain().id(), e))?; let dst_expected_connection = ConnectionEnd::new( highest_state, @@ -682,14 +566,16 @@ impl Connection { let dst_connection = self .dst_chain() .query_connection(dst_connection_id, Height::zero()) - .map_err(|e| ConnectionError::chain_query(self.dst_chain().id(), e))?; + .map_err(|e| ConnectionError::QueryError(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::missing_connection_id( - self.dst_chain().id(), - )); + return Err(ConnectionError::Failed(format!( + "missing connection {:?} on source chain {}", + self.src_connection_id().clone(), + self.dst_chain().id() + ))); } check_destination_connection_state( @@ -704,43 +590,38 @@ 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::client_operation( - self.src_client_id().clone(), - self.src_chain().id(), - e, - ) + ConnectionError::ClientOperation(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::client_operation( - self.dst_client_id().clone(), - self.dst_chain().id(), - e, - ) + ConnectionError::ClientOperation(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::signer(self.dst_chain().id(), e))?; + 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 prefix = self .src_chain() .query_commitment_prefix() - .map_err(|e| ConnectionError::chain_query(self.src_chain().id(), e))?; + .map_err(|e| ConnectionError::QueryError(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::chain_query(self.dst_chain().id(), e))?[0] + .map_err(|e| ConnectionError::QueryError(self.dst_chain().id(), e))?[0] .clone(); // Build the domain type message @@ -761,7 +642,7 @@ impl Connection { let events = self .dst_chain() .send_msgs(dst_msgs) - .map_err(|e| ConnectionError::submit(self.dst_chain().id(), e))?; + .map_err(|e| ConnectionError::SubmitError(self.dst_chain().id(), e))?; // Find the relevant event for connection init let result = events @@ -770,12 +651,17 @@ impl Connection { matches!(event, IbcEvent::OpenInitConnection(_)) || matches!(event, IbcEvent::ChainError(_)) }) - .ok_or_else(ConnectionError::missing_connection_init_event)?; + .ok_or_else(|| { + ConnectionError::Failed("no conn init event was in the response".to_string()) + })?; // TODO - make chainError an actual error match result { IbcEvent::OpenInitConnection(_) => Ok(result), - IbcEvent::ChainError(e) => Err(ConnectionError::tx_response(e)), + IbcEvent::ChainError(e) => Err(ConnectionError::Failed(format!( + "tx response event consists of an error: {}", + e + ))), _ => panic!("internal error"), } } @@ -784,12 +670,12 @@ impl Connection { pub fn build_conn_try(&self) -> Result, ConnectionError> { let src_connection_id = self .src_connection_id() - .ok_or_else(ConnectionError::missing_local_connection_id)?; + .ok_or(ConnectionError::MissingLocalConnectionId)?; let src_connection = self .src_chain() .query_connection(src_connection_id, Height::zero()) - .map_err(|e| ConnectionError::chain_query(self.src_chain().id(), e))?; + .map_err(|e| ConnectionError::QueryError(self.src_chain().id(), e))?; // TODO - check that the src connection is consistent with the try options @@ -813,16 +699,16 @@ impl Connection { let src_client_target_height = self .dst_chain() .query_latest_height() - .map_err(|e| ConnectionError::chain_query(self.dst_chain().id(), e))?; + .map_err(|e| ConnectionError::QueryError(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::submit(self.src_chain().id(), e))?; + .map_err(|e| ConnectionError::SubmitError(self.src_chain().id(), e))?; let query_height = self .src_chain() .query_latest_height() - .map_err(|e| ConnectionError::chain_query(self.src_chain().id(), e))?; + .map_err(|e| ConnectionError::QueryError(self.src_chain().id(), e))?; let (client_state, proofs) = self .src_chain() .build_connection_proofs_and_client_state( @@ -831,7 +717,9 @@ impl Connection { self.src_client_id(), query_height, ) - .map_err(ConnectionError::connection_proof)?; + .map_err(|e| { + ConnectionError::Failed(format!("failed to build connection proofs: {}", e)) + })?; // Build message(s) for updating client on destination let mut msgs = self.build_update_client_on_dst(proofs.height())?; @@ -839,21 +727,24 @@ impl Connection { let counterparty_versions = if src_connection.versions().is_empty() { self.src_chain() .query_compatible_versions() - .map_err(|e| ConnectionError::chain_query(self.src_chain().id(), e))? + .map_err(|e| ConnectionError::QueryError(self.src_chain().id(), e))? } else { src_connection.versions() }; // Get signer - let signer = self - .dst_chain() - .get_signer() - .map_err(|e| ConnectionError::signer(self.dst_chain().id(), e))?; + 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 prefix = self .src_chain() .query_commitment_prefix() - .map_err(|e| ConnectionError::chain_query(self.src_chain().id(), e))?; + .map_err(|e| ConnectionError::QueryError(self.src_chain().id(), e))?; let counterparty = Counterparty::new( self.src_client_id().clone(), @@ -888,7 +779,7 @@ impl Connection { let events = self .dst_chain() .send_msgs(dst_msgs) - .map_err(|e| ConnectionError::submit(self.dst_chain().id(), e))?; + .map_err(|e| ConnectionError::SubmitError(self.dst_chain().id(), e))?; // Find the relevant event for connection try transaction let result = events @@ -897,11 +788,15 @@ impl Connection { matches!(event, IbcEvent::OpenTryConnection(_)) || matches!(event, IbcEvent::ChainError(_)) }) - .ok_or_else(ConnectionError::missing_connection_try_event)?; + .ok_or_else(|| { + ConnectionError::Failed("no conn try event was in the response".to_string()) + })?; match result { IbcEvent::OpenTryConnection(_) => Ok(result), - IbcEvent::ChainError(e) => Err(ConnectionError::tx_response(e)), + IbcEvent::ChainError(e) => { + Err(ConnectionError::Failed(format!("tx response error: {}", e))) + } _ => panic!("internal error"), } } @@ -910,18 +805,22 @@ impl Connection { pub fn build_conn_ack(&self) -> Result, ConnectionError> { let src_connection_id = self .src_connection_id() - .ok_or_else(ConnectionError::missing_local_connection_id)?; + .ok_or(ConnectionError::MissingLocalConnectionId)?; let dst_connection_id = self .dst_connection_id() - .ok_or_else(ConnectionError::missing_counterparty_connection_id)?; + .ok_or(ConnectionError::MissingCounterpartyConnectionId)?; - let _expected_dst_connection = - self.validated_expected_connection(ConnectionMsgType::OpenAck)?; + 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 src_connection = self .src_chain() .query_connection(src_connection_id, Height::zero()) - .map_err(|e| ConnectionError::chain_query(self.src_chain().id(), e))?; + .map_err(|e| ConnectionError::QueryError(self.src_chain().id(), e))?; // TODO - check that the src connection is consistent with the ack options @@ -930,17 +829,16 @@ impl Connection { let src_client_target_height = self .dst_chain() .query_latest_height() - .map_err(|e| ConnectionError::chain_query(self.dst_chain().id(), e))?; + .map_err(|e| ConnectionError::QueryError(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::submit(self.src_chain().id(), e))?; + .map_err(|e| ConnectionError::SubmitError(self.src_chain().id(), e))?; let query_height = self .src_chain() .query_latest_height() - .map_err(|e| ConnectionError::chain_query(self.src_chain().id(), e))?; - + .map_err(|e| ConnectionError::QueryError(self.src_chain().id(), e))?; let (client_state, proofs) = self .src_chain() .build_connection_proofs_and_client_state( @@ -949,16 +847,21 @@ impl Connection { self.src_client_id(), query_height, ) - .map_err(ConnectionError::connection_proof)?; + .map_err(|e| { + ConnectionError::Failed(format!("failed to build connection proofs: {}", e)) + })?; // 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::signer(self.dst_chain().id(), e))?; + 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 new_msg = MsgConnectionOpenAck { connection_id: dst_connection_id.clone(), @@ -979,7 +882,7 @@ impl Connection { let events = self .dst_chain() .send_msgs(dst_msgs) - .map_err(|e| ConnectionError::submit(self.dst_chain().id(), e))?; + .map_err(|e| ConnectionError::SubmitError(self.dst_chain().id(), e))?; // Find the relevant event for connection ack let result = events @@ -988,11 +891,15 @@ impl Connection { matches!(event, IbcEvent::OpenAckConnection(_)) || matches!(event, IbcEvent::ChainError(_)) }) - .ok_or_else(ConnectionError::missing_connection_ack_event)?; + .ok_or_else(|| { + ConnectionError::Failed("no conn ack event was in the response".to_string()) + })?; match result { IbcEvent::OpenAckConnection(_) => Ok(result), - IbcEvent::ChainError(e) => Err(ConnectionError::tx_response(e)), + IbcEvent::ChainError(e) => { + Err(ConnectionError::Failed(format!("tx response error: {}", e))) + } _ => panic!("internal error"), } } @@ -1001,23 +908,32 @@ impl Connection { pub fn build_conn_confirm(&self) -> Result, ConnectionError> { let src_connection_id = self .src_connection_id() - .ok_or_else(ConnectionError::missing_local_connection_id)?; + .ok_or(ConnectionError::MissingLocalConnectionId)?; let dst_connection_id = self .dst_connection_id() - .ok_or_else(ConnectionError::missing_counterparty_connection_id)?; + .ok_or(ConnectionError::MissingCounterpartyConnectionId)?; - let _expected_dst_connection = - self.validated_expected_connection(ConnectionMsgType::OpenAck)?; + 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 query_height = self .src_chain() .query_latest_height() - .map_err(|e| ConnectionError::chain_query(self.src_chain().id(), e))?; + .map_err(|e| ConnectionError::QueryError(self.src_chain().id(), e))?; let _src_connection = self .src_chain() .query_connection(src_connection_id, query_height) - .map_err(|e| ConnectionError::connection_query(src_connection_id.clone(), e))?; + .map_err(|_| { + ConnectionError::Failed(format!( + "missing connection {} on source chain", + src_connection_id + )) + })?; // TODO - check that the src connection is consistent with the confirm options @@ -1029,16 +945,21 @@ impl Connection { self.src_client_id(), query_height, ) - .map_err(ConnectionError::connection_proof)?; + .map_err(|e| { + ConnectionError::Failed(format!("failed to build connection proofs: {}", e)) + })?; // 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::signer(self.dst_chain().id(), e))?; + 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 new_msg = MsgConnectionOpenConfirm { connection_id: dst_connection_id.clone(), @@ -1056,7 +977,7 @@ impl Connection { let events = self .dst_chain() .send_msgs(dst_msgs) - .map_err(|e| ConnectionError::submit(self.dst_chain().id(), e))?; + .map_err(|e| ConnectionError::SubmitError(self.dst_chain().id(), e))?; // Find the relevant event for connection confirm let result = events @@ -1065,11 +986,15 @@ impl Connection { matches!(event, IbcEvent::OpenConfirmConnection(_)) || matches!(event, IbcEvent::ChainError(_)) }) - .ok_or_else(ConnectionError::missing_connection_confirm_event)?; + .ok_or_else(|| { + ConnectionError::Failed("no conn confirm event was in the response".to_string()) + })?; match result { IbcEvent::OpenConfirmConnection(_) => Ok(result), - IbcEvent::ChainError(e) => Err(ConnectionError::tx_response(e)), + IbcEvent::ChainError(e) => { + Err(ConnectionError::Failed(format!("tx response error: {}", e))) + } _ => panic!("internal error"), } } @@ -1099,7 +1024,7 @@ fn extract_connection_id(event: &IbcEvent) -> Result<&ConnectionId, ConnectionEr IbcEvent::OpenConfirmConnection(ev) => ev.connection_id().as_ref(), _ => None, } - .ok_or_else(ConnectionError::missing_connection_id_from_event) + .ok_or_else(|| ConnectionError::Failed("cannot extract connection_id from result".to_string())) } /// Enumeration of proof carrying ICS3 message, helper for relayer. @@ -1130,6 +1055,9 @@ fn check_destination_connection_state( if good_state && good_client_ids && good_connection_ids { Ok(()) } else { - Err(ConnectionError::connection_already_exist(connection_id)) + Err(ConnectionError::Failed(format!( + "connection {} already exist in an incompatible state", + connection_id + ))) } } diff --git a/relayer/src/error.rs b/relayer/src/error.rs index 07c7686116..8b5f35599f 100644 --- a/relayer/src/error.rs +++ b/relayer/src/error.rs @@ -1,418 +1,244 @@ //! This module defines the various errors that be raised in the relayer. -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 anomaly::{BoxError, Context}; +use thiserror::Error; use ibc::{ - 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, + ics02_client::client_type::ClientType, ics24_host::identifier::{ChainId, ChannelId, ConnectionId}, - proofs::ProofError, }; -use crate::chain::cosmos::GENESIS_MAX_BYTES_MAX_FRACTION; -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) }, +/// 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, - AbciQuery - { query: AbciQuery } - |e| { format!("ABCI query returns error: {:?}", e.query) }, + /// I/O error + #[error("I/O error")] + Io, - CheckTx - { - detail: SdkError, - tx: TxResult - } - |e| { format!("CheckTX Commit returns error: {0}. RawResult: {1:?}", e.detail, e.tx) }, + /// Invalid configuration + #[error("invalid configuration")] + Config, - 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!("health check failed for endpoint {0} on the JSON-RPC interface of chain {1}:{2}", - e.endpoint, e.chain_id, e.address) - }, - - HealthCheckGrpcTransport - { - chain_id: ChainId, - address: String, - endpoint: String, - } - [ DisplayOnly ] - |e| { - format!("health check failed for endpoint {0} on the gRPC interface of chain {1}:{2}", - e.endpoint, e.chain_id, e.address) - }, - - HealthCheckGrpcStatus - { - chain_id: ChainId, - address: String, - endpoint: String, - status: tonic::Status - } - |e| { - format!("health check failed for endpoint {0} on the gRPC 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!("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) - }, - - ConfigValidationJsonRpc - { - chain_id: ChainId, - address: String, - endpoint: String, - } - [ DisplayOnly ] - |e| { - format!("semantic config validation: failed to reach endpoint {0} on the JSON-RPC interface of chain {1}:{2}", - e.endpoint, e.chain_id, e.address) - }, - - ConfigValidationTxSizeOutOfBounds - { - chain_id: ChainId, - configured_bound: usize, - genesis_bound: u64, - } - |e| { - format!("semantic config validation failed for option `max_tx_size` chain '{}', reason: `max_tx_size` = {} is greater than {}% of the genesis block param `max_size` = {}", - e.chain_id, e.configured_bound, GENESIS_MAX_BYTES_MAX_FRACTION * 100.0, e.genesis_bound) - }, - - 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) - }, + /// 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, + + /// Response does not contain data + #[error("Empty response value")] + EmptyResponseValue, + + /// Response does not contain a proof + #[error("Empty response proof")] + EmptyResponseProof, + + /// Response does not contain a proof + #[error("Malformed proof")] + MalformedProof, + + /// 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 Error { - pub fn send(_: crossbeam_channel::SendError) -> Error { - Error::channel_send() +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) } } diff --git a/relayer/src/event/monitor.rs b/relayer/src/event/monitor.rs index 24afdb0c8e..942153e532 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,63 +41,50 @@ mod retry_strategy { } } -define_error! { - #[derive(Debug, Clone)] - Error { - WebSocketDriver - [ TraceError ] - |_| { "WebSocket driver failed" }, +#[derive(Debug, Clone, Error)] +pub enum Error { + #[error("WebSocket driver failed: {0}")] + WebSocketDriver(RpcError), - ClientCreationFailed - [ TraceError ] - |_| { "failed to create WebSocket driver" }, + #[error("failed to create WebSocket driver: {0}")] + ClientCreationFailed(RpcError), - ClientTerminationFailed - [ TraceError ] - |_| { "failed to terminate previous WebSocket driver" }, + #[error("failed to terminate previous WebSocket driver: {0}")] + ClientTerminationFailed(Arc), - ClientCompletionFailed - [ TraceError ] - |_| { "failed to run previous WebSocket driver to completion" }, + #[error("failed to run previous WebSocket driver to completion: {0}")] + ClientCompletionFailed(RpcError), - ClientSubscriptionFailed - [ TraceError ] - |_| { "failed to run previous WebSocket driver to completion" }, + #[error("failed to subscribe to events via WebSocket client: {0}")] + ClientSubscriptionFailed(RpcError), - NextEventBatchFailed - [ TraceError ] - |_| { "failed to collect events over WebSocket subscription" }, + #[error("failed to collect events over WebSocket subscription: {0}")] + NextEventBatchFailed(RpcError), - CollectEventsFailed - { reason: String } - |e| { format!("failed to extract IBC events: {0}", e.reason) }, + #[error("failed to extract IBC events: {0}")] + CollectEventsFailed(String), - ChannelSendFailed - |_| { "failed to send event batch through channel" }, + #[error("{0}")] + SubscriptionCancelled(RpcError), - SubscriptionCancelled - [ TraceError ] - |_| { "subscription cancelled" }, + #[error("RPC error: {0}")] + GenericRpcError(RpcError), - Rpc - [ TraceError ] - |_| { "RPC error" }, - } + #[error("event monitor failed to dispatch event batch to subscribers")] + ChannelSendFailed, } 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::subscription_cancelled(e) + Self::SubscriptionCancelled(e) } - _ => Self::rpc(e), + _ => Self::GenericRpcError(e), } } } -pub type Result = std::result::Result; - /// A batch of events from a chain at a specific height #[derive(Clone, Debug)] pub struct EventBatch { @@ -106,9 +93,21 @@ 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; @@ -165,7 +164,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::client_creation_failed)?; + .map_err(Error::ClientCreationFailed)?; let (tx_err, rx_err) = mpsc::unbounded_channel(); let websocket_driver_handle = rt.spawn(run_driver(driver, tx_err.clone())); @@ -216,7 +215,7 @@ impl EventMonitor { let subscription = self .rt .block_on(self.client.subscribe(query.clone())) - .map_err(Error::client_subscription_failed)?; + .map_err(Error::ClientSubscriptionFailed)?; subscriptions.push(subscription); } @@ -239,7 +238,7 @@ impl EventMonitor { let (mut client, driver) = self .rt .block_on(WebSocketClient::new(self.node_addr.clone())) - .map_err(Error::client_creation_failed)?; + .map_err(Error::ClientCreationFailed)?; let mut driver_handle = self.rt.spawn(run_driver(driver, self.tx_err.clone())); @@ -264,7 +263,7 @@ impl EventMonitor { self.rt .block_on(driver_handle) - .map_err(Error::client_termination_failed)?; + .map_err(|e| Error::ClientTerminationFailed(Arc::new(e)))?; trace!("[{}] previous client successfully shutdown", self.chain_id); @@ -364,7 +363,7 @@ impl EventMonitor { let result = rt.block_on(async { tokio::select! { Some(batch) = batches.next() => batch, - Some(e) = self.rx_err.recv() => Err(Error::web_socket_driver(e)), + Some(err) = self.rx_err.recv() => Err(Error::WebSocketDriver(err)), } }); @@ -372,40 +371,37 @@ 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) => { - 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; - } - } + 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; } } } @@ -421,7 +417,7 @@ impl EventMonitor { fn propagate_error(&self, error: Error) -> Result<()> { self.tx_batch .send(Err(error)) - .map_err(|_| Error::channel_send_failed())?; + .map_err(|_| Error::ChannelSendFailed)?; Ok(()) } @@ -430,7 +426,7 @@ impl EventMonitor { fn process_batch(&self, batch: EventBatch) -> Result<()> { self.tx_batch .send(Ok(batch)) - .map_err(|_| Error::channel_send_failed())?; + .map_err(|_| Error::ChannelSendFailed)?; Ok(()) } diff --git a/relayer/src/event/rpc.rs b/relayer/src/event/rpc.rs index 34073a3927..676caae9fa 100644 --- a/relayer/src/event/rpc.rs +++ b/relayer/src/event/rpc.rs @@ -1,12 +1,13 @@ 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::{Error as EventError, IbcEvent, RawObject}, + events::{IbcEvent, RawObject}, ics02_client::events as ClientEvents, ics03_connection::events as ConnectionEvents, ics04_channel::events as ChannelEvents, @@ -56,7 +57,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( @@ -127,7 +128,7 @@ pub fn build_event(mut object: RawObject) -> Result { )) } - event_type => Err(EventError::incorrect_event_type(event_type.to_string())), + event_type => Err(format!("Incorrect event type: '{}'", event_type).into()), } } diff --git a/relayer/src/foreign_client.rs b/relayer/src/foreign_client.rs index 55ae552307..9c10e7608b 100644 --- a/relayer/src/foreign_client.rs +++ b/relayer/src/foreign_client.rs @@ -3,17 +3,15 @@ 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; @@ -34,182 +32,37 @@ const MAX_MISBEHAVIOUR_CHECK_DURATION: Duration = Duration::from_secs(120); const MAX_RETRIES: usize = 5; -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(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), } #[derive(Clone, Debug)] @@ -254,7 +107,11 @@ impl ForeignClient { ) -> Result { // Sanity check if src_chain.id().eq(&dst_chain.id()) { - return Err(ForeignClientError::same_chain_id(src_chain.id())); + return Err(ForeignClientError::ClientCreate(format!( + "the source ({}) and destination ({}) chains must be different", + src_chain.id(), + dst_chain.id(), + ))); } let mut client = ForeignClient { @@ -295,7 +152,7 @@ impl ForeignClient { match host_chain.query_client_state(client_id, height) { Ok(cs) => { if cs.chain_id() != expected_target_chain.id() { - Err(ForeignClientError::mismatch_chain_id( + Err(ForeignClientError::ClientFind( client_id.clone(), expected_target_chain.id(), cs.chain_id(), @@ -309,10 +166,10 @@ impl ForeignClient { )) } } - Err(e) => Err(ForeignClientError::client_query( + Err(e) => Err(ForeignClientError::ClientQuery( client_id.clone(), host_chain.id(), - e, + format!("{}", e), )), } } @@ -320,11 +177,14 @@ 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::client_upgrade( + ForeignClientError::ClientUpgrade( self.id.clone(), - self.src_chain.id(), - "failed while querying src chain for latest height".to_string(), - e, + self.dst_chain.id(), + format!( + "failed while querying src chain ({}) for latest height: {}", + self.src_chain.id(), + e + ), ) })?; @@ -337,11 +197,14 @@ impl ForeignClient { .src_chain .query_upgraded_client_state(src_height) .map_err(|e| { - ForeignClientError::client_upgrade( + ForeignClientError::ClientUpgrade( self.id.clone(), - self.src_chain.id(), - "failed while fetching from chain the upgraded client state".to_string(), - e, + self.dst_chain.id(), + format!( + "failed while fetching from chain {} the upgraded client state: {}", + self.src_chain.id(), + e + ), ) })?; @@ -350,15 +213,12 @@ impl ForeignClient { let (consensus_state, proof_upgrade_consensus_state) = self .src_chain .query_upgraded_consensus_state(src_height) - .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, - ) - })?; + .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)))?; debug!( "[{}] upgraded client consensus state {:?}", @@ -367,11 +227,10 @@ impl ForeignClient { // Get signer let signer = self.dst_chain.get_signer().map_err(|e| { - ForeignClientError::client_upgrade( + ForeignClientError::ClientUpgrade( self.id.clone(), self.dst_chain.id(), - "failed while fetching the destination chain signer".to_string(), - e, + format!("failed while fetching the destination chain signer: {}", e), ) })?; @@ -388,11 +247,13 @@ impl ForeignClient { msgs.push(msg_upgrade); let res = self.dst_chain.send_msgs(msgs).map_err(|e| { - ForeignClientError::client_upgrade( + ForeignClientError::ClientUpgrade( self.id.clone(), self.dst_chain.id(), - "failed while sending message to destination chain".to_string(), - e, + format!( + "failed while sending message to destination chain with err: {}", + e + ), ) })?; @@ -417,53 +278,46 @@ impl ForeignClient { pub fn build_create_client(&self) -> Result { // Get signer let signer = self.dst_chain.get_signer().map_err(|e| { - ForeignClientError::client_create( + ForeignClientError::ClientCreate(format!( + "failed while fetching the destination chain ({}) signer: {}", self.dst_chain.id(), - "failed while fetching the destination chain signer".to_string(), - e, - ) + 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::client_create( - self.dst_chain.id(), - "failed while querying src chain ({}) for latest height: {}".to_string(), - e, - ) + ForeignClientError::ClientCreate(format!( + "failed while querying src chain ({}) for latest height: {}", + self.src_chain.id(), + e + )) })?; let client_state = self .src_chain .build_client_state(latest_height) .map_err(|e| { - ForeignClientError::client_create( + ForeignClientError::ClientCreate(format!( + "failed while building client state from src chain ({}) with error: {}", self.src_chain.id(), - "failed while querying src chain for latest height".to_string(), - e, - ) + 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::client_create( - self.src_chain.id(), - "failed while building client consensus state from src chain".to_string(), - e, - ) - })? + 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)))? .wrap_any(); //TODO Get acct_prefix - let msg = MsgCreateAnyClient::new(client_state, consensus_state, signer) - .map_err(ForeignClientError::client)?; + let msg = MsgCreateAnyClient::new(client_state, consensus_state, signer).map_err(|e| { + ForeignClientError::ClientCreate(format!( + "failed while building the create client message: {}", + e + )) + })?; Ok(msg) } @@ -476,11 +330,11 @@ impl ForeignClient { .dst_chain .send_msgs(vec![new_msg.to_any()]) .map_err(|e| { - ForeignClientError::client_create( + ForeignClientError::ClientCreate(format!( + "failed sending message to dst chain ({}) with err: {}", self.dst_chain.id(), - "failed sending message to dst chain ".to_string(), - e, - ) + e + )) })?; assert!(!res.is_empty()); @@ -489,14 +343,19 @@ impl ForeignClient { /// Sends the client creation transaction & subsequently sets the id of this ForeignClient fn create(&mut self) -> Result<(), ForeignClientError> { - 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); - + 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); + } + } Ok(()) } @@ -505,11 +364,10 @@ impl ForeignClient { .dst_chain .query_client_state(self.id(), Height::zero()) .map_err(|e| { - ForeignClientError::client_refresh( - self.id().clone(), - "failed querying client state on dst chain".to_string(), - e, - ) + ForeignClientError::ClientUpdate(format!( + "failed querying client state on dst chain {} with error: {}", + self.id, e + )) })?; let last_update_time = self @@ -520,7 +378,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::expired_or_frozen( + return Err(ForeignClientError::ExpiredOrFrozen( self.id().clone(), self.dst_chain.id(), )); @@ -557,11 +415,10 @@ impl ForeignClient { ) -> Result, ForeignClientError> { // Wait for source chain to reach `target_height` while self.src_chain().query_latest_height().map_err(|e| { - ForeignClientError::client_create( - self.src_chain.id(), - "failed fetching src chain latest height with error".to_string(), - e, - ) + ForeignClientError::ClientUpdate(format!( + "failed fetching src chain latest height with error: {}", + e + )) })? < target_height { thread::sleep(Duration::from_millis(100)) @@ -572,11 +429,10 @@ impl ForeignClient { .dst_chain() .query_client_state(&self.id, Height::default()) .map_err(|e| { - ForeignClientError::client_create( - self.dst_chain.id(), - "failed querying client state on dst chain".to_string(), - e, - ) + ForeignClientError::ClientUpdate(format!( + "failed querying client state on dst chain {} with error: {}", + self.id, e + )) })?; // If not specified, set trusted state to the highest height smaller than target height. @@ -588,18 +444,22 @@ impl ForeignClient { .into_iter() .find(|h| h < &target_height) .ok_or_else(|| { - ForeignClientError::missing_smaller_trusted_height( + ForeignClientError::ClientUpdate(format!( + "chain {} is missing trusted state smaller than target height {}", self.dst_chain().id(), - target_height, - ) - // )) + target_height + )) })? } else { cs_heights .into_iter() .find(|h| h == &trusted_height) .ok_or_else(|| { - ForeignClientError::missing_trusted_height(self.dst_chain().id(), target_height) + ForeignClientError::ClientUpdate(format!( + "chain {} is missing trusted state at height {}", + self.dst_chain().id(), + trusted_height + )) })? }; @@ -615,19 +475,18 @@ impl ForeignClient { .src_chain() .build_header(trusted_height, target_height, client_state) .map_err(|e| { - ForeignClientError::client_update( - self.src_chain.id(), - "failed building header with error".to_string(), - e, - ) + ForeignClientError::ClientUpdate(format!( + "failed building header with error: {}", + e + )) })?; let signer = self.dst_chain().get_signer().map_err(|e| { - ForeignClientError::client_update( + ForeignClientError::ClientUpdate(format!( + "failed getting signer for dst chain ({}) with error: {}", self.dst_chain.id(), - "failed getting signer for dst chain".to_string(), - e, - ) + e + )) })?; let mut msgs = vec![]; @@ -679,11 +538,11 @@ impl ForeignClient { ) -> Result, ForeignClientError> { let h = if height == Height::zero() { self.src_chain.query_latest_height().map_err(|e| { - ForeignClientError::client_update( + ForeignClientError::ClientUpdate(format!( + "failed while querying src chain ({}) for latest height: {}", self.src_chain.id(), - "failed while querying src chain ({}) for latest height".to_string(), - e, - ) + e + )) })? } else { height @@ -691,19 +550,20 @@ impl ForeignClient { let new_msgs = self.build_update_client_with_trusted(h, trusted_height)?; if new_msgs.is_empty() { - return Err(ForeignClientError::client_already_up_to_date( - self.id.clone(), + return Err(ForeignClientError::ClientUpdate(format!( + "Client {} is already up-to-date with chain {}@{}", + self.id, self.src_chain.id(), - h, - )); + h + ))); } let events = self.dst_chain().send_msgs(new_msgs).map_err(|e| { - ForeignClientError::client_update( + ForeignClientError::ClientUpdate(format!( + "failed sending message to dst chain ({}) with err: {}", self.dst_chain.id(), - "failed sending message to dst chain".to_string(), - e, - ) + e + )) })?; Ok(events) @@ -711,7 +571,9 @@ 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()?; + let res = self.build_latest_update_client_and_send().map_err(|e| { + ForeignClientError::ClientUpdate(format!("build_create_client_and_send {:?}", e)) + })?; debug!("[{}] client updated with return message {:?}\n", self, res); @@ -740,11 +602,10 @@ impl ForeignClient { .dst_chain .query_txs(QueryTxRequest::Client(request.clone())) .map_err(|e| { - ForeignClientError::client_event_query( + ForeignClientError::ClientEventQuery( self.id().clone(), self.dst_chain.id(), - consensus_height, - e, + format!("update event for {}: {}", consensus_height, e), ) }); match result { @@ -774,10 +635,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::unexpected_event( + ForeignClientError::ClientEventQuery( self.id().clone(), self.dst_chain.id(), - event.to_json(), + format!("query Tx-es returned unexpected event {}", event.to_json()), ) })?; Ok(Some(update)) @@ -794,7 +655,11 @@ impl ForeignClient { pagination: ibc_proto::cosmos::base::query::pagination::all(), }) .map_err(|e| { - ForeignClientError::client_query(self.id().clone(), self.src_chain.id(), e) + ForeignClientError::ClientQuery( + self.id().clone(), + self.src_chain.id(), + format!("{}", e), + ) })?; consensus_states.sort_by_key(|a| std::cmp::Reverse(a.height)); Ok(consensus_states) @@ -806,7 +671,14 @@ impl ForeignClient { .dst_chain .query_consensus_state(self.id.clone(), height, Height::zero()) .map_err(|e| { - ForeignClientError::client_query(self.id.clone(), self.dst_chain.id(), e) + ForeignClientError::ClientQuery( + self.id.clone(), + self.dst_chain.id(), + format!( + "failed querying consensus state at height {} with error {}", + height, e + ), + ) })?; Ok(res) @@ -869,10 +741,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 {}", self.id), - e, - ) + ForeignClientError::Misbehaviour(format!( + "failed querying client state on dst chain {} with error: {}", + self.id, e + )) })?; // Get the list of consensus state heights in descending order. @@ -935,7 +807,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::misbehaviour_exit( + return Err(ForeignClientError::MisbehaviourExit( "no header in update client events".to_string(), )); } @@ -947,14 +819,12 @@ 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() { @@ -987,13 +857,11 @@ 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 ({})", - self.dst_chain.id() - ), - e, - ) + ForeignClientError::Misbehaviour(format!( + "failed getting signer for destination chain ({}), error: {}", + self.dst_chain.id(), + e + )) })?; let mut msgs = vec![]; @@ -1019,13 +887,11 @@ impl ForeignClient { ); let events = self.dst_chain().send_msgs(msgs).map_err(|e| { - ForeignClientError::misbehaviour( - format!( - "failed sending evidence to destination chain ({})", - self.dst_chain.id(), - ), - e, - ) + ForeignClientError::Misbehaviour(format!( + "failed sending evidence to destination chain ({}), error: {}", + self.dst_chain.id(), + e + )) })?; Ok(events) @@ -1053,7 +919,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(ForeignClientErrorDetail::MisbehaviourExit(s), _)) => { + Err(ForeignClientError::MisbehaviourExit(s)) => { warn!( "[{}] misbehaviour checking is being disabled: {:?}", self, s @@ -1071,23 +937,14 @@ impl ForeignClient { MisbehaviourResults::ValidClient } } - Err(e) => match e.detail() { - ForeignClientErrorDetail::MisbehaviourExit(s) => { - error!( - "[{}] misbehaviour checking is being disabled: {:?}", - self, s - ); + Err(e) => { + if update_event.is_some() { 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 - } - } - }, + } } } } @@ -1104,9 +961,10 @@ 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()), - _ => Err(ForeignClientError::missing_client_id_from_event( - event.clone(), - )), + other => Err(ForeignClientError::ClientCreate(format!( + "cannot extract client_id from result: {:?}", + other + ))), } } diff --git a/relayer/src/keyring.rs b/relayer/src/keyring.rs index ad52f6ac37..3fe0cd83cd 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; +use errors::{Error, Kind}; pub use pub_key::EncodedPubKey; pub mod errors; @@ -82,18 +82,19 @@ 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 { - 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, - }) + return Err(Kind::PublicKeyMismatch { + keyfile: keyfile_pubkey_bytes, + mnemonic: derived_pubkey_bytes, + } + .into()); } + + Ok(Self { + public_key: derived_pubkey, + private_key, + account: key_file.address, + address: keyfile_address_bytes, + }) } } @@ -123,17 +124,17 @@ impl KeyStore for Memory { self.keys .get(key_name) .cloned() - .ok_or_else(Error::key_not_found) + .ok_or_else(|| Kind::KeyNotFound.into()) } fn add_key(&mut self, key_name: &str, key_entry: KeyEntry) -> Result<(), Error> { if self.keys.contains_key(key_name) { - Err(Error::key_already_exist()) - } else { - self.keys.insert(key_name.to_string(), key_entry); - - Ok(()) + return Err(Kind::ExistingKey.into()); } + + self.keys.insert(key_name.to_string(), key_entry); + + Ok(()) } fn keys(&self) -> Result, Error> { @@ -166,19 +167,22 @@ impl KeyStore for Test { key_file.set_extension(KEYSTORE_FILE_EXTENSION); if !key_file.as_path().exists() { - return Err(Error::key_file_not_found(format!("{}", key_file.display()))); + return Err(Kind::KeyStore + .context(format!("cannot find key file at '{}'", key_file.display())) + .into()); } - 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 file = File::open(&key_file).map_err(|_| { + Kind::KeyStore.context(format!("cannot open key file at '{}'", key_file.display())) })?; - let key_entry = serde_json::from_reader(file) - .map_err(|e| Error::key_file_decode(format!("{}", key_file.display()), e))?; + let key_entry = serde_json::from_reader(file).map_err(|e| { + Kind::KeyStore.context(format!( + "invalid key file at '{}': {}", + key_file.display(), + e + )) + })?; Ok(key_entry) } @@ -186,26 +190,19 @@ 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(|e| { - Error::key_file_io(file_path.clone(), "failed to create file".to_string(), e) - })?; + let file = File::create(filename) + .map_err(|_| Kind::KeyStore.context("error creating the key file"))?; serde_json::to_writer_pretty(file, &key_entry) - .map_err(|e| Error::key_file_encode(file_path, e))?; + .map_err(|_| Kind::KeyStore.context("error writing the key file"))?; Ok(()) } fn keys(&self) -> Result, Error> { - 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 dir = fs::read_dir(&self.store) + .map_err(|e| Kind::KeyStore.context(format!("cannot list keys: {}", e)))?; let ext = OsStr::new(KEYSTORE_FILE_EXTENSION); @@ -238,15 +235,13 @@ 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())?; + let keys_folder = disk_store_path(chain_id.as_str()).map_err(|e| { + Kind::KeyStore.context(format!("failed to compute keys folder path: {:?}", e)) + })?; // Create keys folder if it does not exist fs::create_dir_all(&keys_folder).map_err(|e| { - Error::key_file_io( - keys_folder.display().to_string(), - "failed to create keys folder".to_string(), - e, - ) + Kind::KeyStore.context(format!("failed to create keys folder: {:?}", e)) })?; Ok(Self::Test(Test::new( @@ -284,7 +279,8 @@ impl KeyRing { key_file_content: &str, hd_path: &HDPath, ) -> Result { - let key_file: KeyFile = serde_json::from_str(key_file_content).map_err(Error::encode)?; + let key_file: KeyFile = + serde_json::from_str(key_file_content).map_err(|e| Kind::InvalidKey.context(e))?; KeyEntry::from_key_file(key_file, hd_path) } @@ -306,7 +302,7 @@ impl KeyRing { // Compute Bech32 account let account = bech32::encode(self.account_prefix(), address.to_base32(), Variant::Bech32) - .map_err(Error::bech32)?; + .map_err(|e| Kind::Bech32Account.context(e))?; Ok(KeyEntry { public_key, @@ -321,8 +317,9 @@ 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(Error::invalid_key)?; + 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 signature: Signature = signing_key.sign(&msg); Ok(signature.as_ref().to_vec()) @@ -342,13 +339,13 @@ fn private_key_from_mnemonic( hd_path: &StandardHDPath, ) -> Result { let mnemonic = Mnemonic::from_phrase(mnemonic_words, Language::English) - .map_err(Error::invalid_mnemonic)?; + .map_err(|e| Kind::InvalidMnemonic.context(e))?; 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(Error::private_key)?; + .map_err(|e| Kind::PrivateKey.context(e))?; Ok(private_key) } @@ -374,13 +371,14 @@ fn decode_bech32(input: &str) -> Result, Error> { let bytes = bech32::decode(input) .and_then(|(_, data, _)| Vec::from_base32(&data)) - .map_err(Error::bech32_account)?; + .map_err(|e| Kind::Bech32Account.context(e))?; Ok(bytes) } fn disk_store_path(folder_name: &str) -> Result { - let home = dirs_next::home_dir().ok_or_else(Error::home_location_unavailable)?; + let home = dirs_next::home_dir() + .ok_or_else(|| Kind::KeyStore.context("cannot retrieve home folder location"))?; 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 35c3b54abc..ca2b44ba50 100644 --- a/relayer/src/keyring/errors.rs +++ b/relayer/src/keyring/errors.rs @@ -1,105 +1,46 @@ -use flex_error::{define_error, DisplayOnly, TraceError}; -use std::io::Error as IoError; +use anomaly::{BoxError, Context}; +use thiserror::Error; -define_error! { - Error { - InvalidKey - [ TraceError ] - |_| { "invalid key: could not build signing key from private key bytes" }, +pub type Error = anomaly::Error; - KeyNotFound - |_| { "key not found" }, +#[derive(Clone, Debug, Error)] +pub enum Kind { + #[error("invalid key")] + InvalidKey, - KeyAlreadyExist - |_| { "key already exist" }, + #[error("key not found")] + KeyNotFound, - InvalidMnemonic - [ DisplayOnly ] - |_| { "invalid mnemonic" }, + #[error("key already exists")] + ExistingKey, - PrivateKey - [ TraceError ] - |_| { "cannot generate private key" }, + #[error("invalid mnemonic")] + InvalidMnemonic, - UnsupportedPublicKey - { key_type: String } - |e| { - format!("unsupported public key: {}. only secp256k1 pub keys are currently supported", - e.key_type) - }, + #[error("cannot generate private key")] + PrivateKey, - EncodedPublicKey - { - key: String, - } - [ TraceError ] - |e| { - format!("cannot deserialize the encoded public key {0}", - e.key) - }, + #[error("cannot deserialize the encoded public key {0} with error {1}")] + EncodedPublicKey(String, String), - Bech32Account - [ TraceError ] - |_| { "cannot generate bech32 account" }, + #[error("cannot generate bech32 account")] + Bech32Account, - Bech32 - [ TraceError ] - |_| { "bech32 error" }, + #[error("bech32 error")] + Bech32, - PublicKeyMismatch - { keyfile: Vec, mnemonic: Vec } - |_| { "mismatch between the public key in the key file and the public key in the mnemonic" }, + #[error("mismatch between the public key in the key file and the public key in the mnemonic")] + PublicKeyMismatch { keyfile: Vec, mnemonic: Vec }, - KeyFileEncode - { file_path: String } - [ TraceError ] - |e| { - format!("error encoding key file at '{}'", - e.file_path) - }, + #[error("key store error")] + KeyStore, - 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" }, + #[error("invalid HD path: {0}")] + InvalidHdPath(String), +} - InvalidHdPath - { - path: String, - } - |e| { - format!("invalid HD path: {0}", e.path) - }, +impl Kind { + pub fn context(self, source: impl Into) -> Context { + Context::new(self, Some(source.into())) } } diff --git a/relayer/src/keyring/pub_key.rs b/relayer/src/keyring/pub_key.rs index 97ba01bc09..1974985fe2 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; +use super::errors::{Error, Kind}; #[derive(Debug)] pub enum EncodedPubKey { @@ -64,10 +64,14 @@ impl FromStr for EncodedPubKey { ); if proto.tpe != "/cosmos.crypto.secp256k1.PubKey" { - Err(Error::unsupported_public_key(proto.tpe)) - } else { - Ok(EncodedPubKey::Proto(proto)) + return Err(Kind::EncodedPublicKey( + s.to_string(), + "only secp256k1 pub keys are currently supported".to_string(), + ) + .into()); } + + Ok(EncodedPubKey::Proto(proto)) } Err(e) if e.classify() == serde_json::error::Category::Syntax => { // Input is not syntactically-correct JSON. @@ -81,7 +85,7 @@ impl FromStr for EncodedPubKey { s, e ); - Err(Error::encoded_public_key(s.to_string(), e)) + Err(Kind::EncodedPublicKey(s.to_string(), e.to_string()).into()) } } } diff --git a/relayer/src/lib.rs b/relayer/src/lib.rs index ea56af7282..a6be011169 100644 --- a/relayer/src/lib.rs +++ b/relayer/src/lib.rs @@ -29,7 +29,6 @@ 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 c67285be91..170091488f 100644 --- a/relayer/src/light_client/tendermint.rs +++ b/relayer/src/light_client/tendermint.rs @@ -30,7 +30,12 @@ use ibc::{ }; use tracing::trace; -use crate::{chain::CosmosSdkChain, config::ChainConfig, error::Error}; +use crate::error::Kind; +use crate::{ + chain::CosmosSdkChain, + config::ChainConfig, + error::{self, Error}, +}; use super::Verified; @@ -60,8 +65,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(Error::invalid_height)?; + let target_height = TMHeight::try_from(target.revision_height) + .map_err(|e| error::Kind::InvalidHeight.context(e))?; let client = self.prepare_client(client_state)?; let mut state = self.prepare_state(trusted)?; @@ -69,7 +74,7 @@ impl super::LightClient for LightClient { // Verify the target header let target = client .verify_to_target(target_height, &mut state) - .map_err(|e| Error::light_client(self.chain_id.to_string(), e))?; + .map_err(|e| error::Kind::LightClient(self.chain_id.to_string()).context(e))?; // Collect the verification trace for the target block let target_trace = state.get_trace(target.height()); @@ -88,7 +93,8 @@ 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(Error::invalid_height)?; + let height = TMHeight::try_from(height.revision_height) + .map_err(|e| error::Kind::InvalidHeight.context(e))?; self.fetch_light_block(AtHeight::At(height)) } @@ -106,14 +112,14 @@ impl super::LightClient for LightClient { crate::time!("light client check_misbehaviour"); let update_header = update.header.clone().ok_or_else(|| { - Error::misbehaviour(format!( + Kind::Misbehaviour(format!( "missing header in update client event {}", self.chain_id )) })?; let update_header = downcast!(update_header => AnyHeader::Tendermint).ok_or_else(|| { - Error::misbehaviour(format!( + Kind::Misbehaviour(format!( "header type incompatible for chain {}", self.chain_id )) @@ -166,7 +172,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::rpc(config.rpc_addr.clone(), e))?; + .map_err(|e| error::Kind::LightClient(config.rpc_addr.to_string()).context(e))?; let io = components::io::ProdIo::new(peer_id, rpc_client, Some(config.rpc_timeout)); @@ -185,7 +191,10 @@ impl LightClient { let client_state = downcast!(client_state => AnyClientState::Tendermint).ok_or_else(|| { - Error::client_type_mismatch(ClientType::Tendermint, client_state.client_type()) + error::Kind::ClientTypeMismatch { + expected: ClientType::Tendermint, + got: client_state.client_type(), + } })?; let params = TmOptions { @@ -206,8 +215,8 @@ impl LightClient { } fn prepare_state(&self, trusted: ibc::Height) -> Result { - let trusted_height = - TMHeight::try_from(trusted.revision_height).map_err(Error::invalid_height)?; + let trusted_height = TMHeight::try_from(trusted.revision_height) + .map_err(|e| error::Kind::InvalidHeight.context(e))?; let trusted_block = self.fetch_light_block(AtHeight::At(trusted_height))?; @@ -220,9 +229,11 @@ 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::light_client_io(self.chain_id.to_string(), e)) + self.io.fetch_light_block(height).map_err(|e| { + error::Kind::LightClient(self.chain_id.to_string()) + .context(e) + .into() + }) } fn adjust_headers( diff --git a/relayer/src/link.rs b/relayer/src/link.rs index 98eaf2184b..2292e8f5e5 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; -pub mod error; +mod error; mod operational_data; mod relay_path; mod relay_summary; @@ -45,7 +45,12 @@ impl Link { .src_chain() .query_channel(self.a_to_b.src_port_id(), a_channel_id, Height::default()) .map_err(|e| { - LinkError::channel_not_found(a_channel_id.clone(), self.a_to_b.src_chain().id(), e) + LinkError::Failed(format!( + "channel {} does not exist on chain {}; context={}", + a_channel_id, + self.a_to_b.src_chain().id(), + e + )) })?; let b_channel_id = self.a_to_b.dst_channel_id()?; @@ -55,7 +60,12 @@ impl Link { .dst_chain() .query_channel(self.a_to_b.dst_port_id(), b_channel_id, Height::default()) .map_err(|e| { - LinkError::channel_not_found(b_channel_id.clone(), self.a_to_b.dst_chain().id(), e) + LinkError::Failed(format!( + "channel {} does not exist on chain {}; context={}", + b_channel_id, + self.a_to_b.dst_chain().id(), + e + )) })?; if a_channel.state_matches(&ChannelState::Closed) @@ -76,28 +86,38 @@ 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::channel_not_found(a_channel_id.clone(), a_chain.id(), e))?; + .map_err(|e| { + LinkError::Failed(format!( + "channel {} does not exist on chain {}; context={}", + a_channel_id.clone(), + a_chain.id(), + e + )) + })?; if !a_channel.state_matches(&ChannelState::Open) && !a_channel.state_matches(&ChannelState::Closed) { - return Err(LinkError::invalid_channel_state( + return Err(LinkError::ConstructorFailed( 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::counterparty_channel_not_found(a_channel_id.clone()))?; + 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 + )) + })?; if a_channel.connection_hops().is_empty() { - return Err(LinkError::no_connection_hop( + return Err(LinkError::Failed(format!( + "channel {} on chain {} has no connection hops", a_channel_id.clone(), - a_chain.id(), - )); + a_chain.id() + ))); } // Check that the counterparty details on the destination chain matches the source chain @@ -112,19 +132,18 @@ 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()) - .map_err(LinkError::relayer)?; + let a_connection = a_chain.query_connection(&a_connection_id, Height::zero())?; if !a_connection.state_matches(&ConnectionState::Open) { - return Err(LinkError::channel_not_opened( + return Err(LinkError::Failed(format!( + "connection for channel {} on chain {} not in open state", 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 0c3c96f783..e41d19fb43 100644 --- a/relayer/src/link/error.rs +++ b/relayer/src/link/error.rs @@ -1,8 +1,7 @@ -use flex_error::define_error; +use thiserror::Error; + use ibc::events::IbcEvent; -use ibc::ics02_client::error::Error as Ics02Error; -use ibc::ics24_host::identifier::{ChainId, ChannelId}; -use ibc::Height; +use ibc::ics24_host::identifier::{ChainId, ChannelId, PortId}; use crate::channel::ChannelError; use crate::connection::ConnectionError; @@ -10,130 +9,41 @@ use crate::error::Error; use crate::foreign_client::ForeignClientError; use crate::transfer::PacketError; -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" }, +#[derive(Debug, Error)] +pub enum LinkError { + #[error("failed with underlying error: {0}")] + Failed(String), - OldPacketClearingFailed - |_| { "clearing of old packets failed" }, + #[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), - Send - { event: IbcEvent } - |e| { - format!("chain error when sending messages: {0}", e.event) - }, + #[error("failed with underlying error: {0}")] + Generic(#[from] Error), - MissingChannelId - { chain_id: ChainId } - |e| { - format!("missing channel_id on chain {}", e.chain_id) - }, + #[error("link initialization failed during channel counterparty verification: {0}")] + Initialization(ChannelError), - Signer - { chain_id: ChainId } - [ Error ] - |e| { - format!("could not retrieve signer from src chain {}", e.chain_id) - }, + #[error("failed to construct packet proofs for chain {0} with error: {1}")] + PacketProofsConstructor(ChainId, Error), - DecrementHeight - { height: Height } - [ Ics02Error ] - |e| { - format!("Cannot clear packets @height {}, because this height cannot be decremented", e.height) - }, + #[error("failed during query to chain id {0} with underlying error: {1}")] + QueryError(ChainId, Error), - UnexpectedEvent - { event: IbcEvent } - |e| { - format!("unexpected query tx response: {}", e.event) - }, + #[error("connection error: {0}:")] + ConnectionError(#[from] ConnectionError), - 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("channel error: {0}:")] + ChannelError(#[from] ChannelError), - 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("failed during a client operation: {0}:")] + ClientError(ForeignClientError), - CounterpartyChannelNotFound - { - channel_id: ChannelId, - } - |e| { - format!("counterparty channel id not found for {}", - e.channel_id) - }, + #[error("packet error: {0}:")] + PacketError(#[from] PacketError), - NoConnectionHop - { - channel_id: ChannelId, - chain_id: ChainId, - } - |e| { - format!("channel {} on chain {} has no connection hops", - e.channel_id, e.chain_id) - }, + #[error("clearing of old packets failed")] + OldPacketClearingFailed, - } + #[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 de1c90ebb1..cca77dfe18 100644 --- a/relayer/src/link/relay_path.rs +++ b/relayer/src/link/relay_path.rs @@ -7,6 +7,7 @@ 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}, @@ -30,11 +31,10 @@ use ibc_proto::ibc::core::channel::v1::{ }; use crate::chain::handle::ChainHandle; -use crate::channel::error::ChannelError; -use crate::channel::Channel; +use crate::channel::{Channel, ChannelError}; use crate::event::monitor::EventBatch; use crate::foreign_client::{ForeignClient, ForeignClientError}; -use crate::link::error::{self, LinkError}; +use crate::link::error::LinkError; use crate::link::operational_data::{OperationalData, OperationalDataTarget, TransitMessage}; use crate::link::relay_summary::RelaySummary; @@ -100,15 +100,21 @@ impl RelayPath { } pub fn src_channel_id(&self) -> Result<&ChannelId, LinkError> { - self.channel - .src_channel_id() - .ok_or_else(|| LinkError::missing_channel_id(self.src_chain().id())) + self.channel.src_channel_id().ok_or_else(|| { + LinkError::Failed(format!( + "channel_id on source chain '{}' is 'None'", + self.src_chain().id() + )) + }) } pub fn dst_channel_id(&self) -> Result<&ChannelId, LinkError> { - self.channel - .dst_channel_id() - .ok_or_else(|| LinkError::missing_channel_id(self.dst_chain().id())) + self.channel.dst_channel_id().ok_or_else(|| { + LinkError::Failed(format!( + "channel_id on destination chain '{}' is 'None'", + self.dst_chain().id() + )) + }) } pub fn channel(&self) -> &Channel { @@ -116,33 +122,43 @@ impl RelayPath { } fn src_channel(&self, height: Height) -> Result { - self.src_chain() + Ok(self + .src_chain() .query_channel(self.src_port_id(), self.src_channel_id()?, height) - .map_err(|e| LinkError::channel(ChannelError::query(self.src_chain().id(), e))) + .map_err(|e| ChannelError::QueryError(self.src_chain().id(), e))?) } fn dst_channel(&self, height: Height) -> Result { - self.dst_chain() + Ok(self + .dst_chain() .query_channel(self.dst_port_id(), self.dst_channel_id()?, height) - .map_err(|e| LinkError::channel(ChannelError::query(self.src_chain().id(), e))) + .map_err(|e| ChannelError::QueryError(self.src_chain().id(), e))?) } fn src_signer(&self) -> Result { - self.src_chain() - .get_signer() - .map_err(|e| LinkError::signer(self.src_chain().id(), e)) + 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 + )) + }) } fn dst_signer(&self) -> Result { - self.dst_chain() - .get_signer() - .map_err(|e| LinkError::signer(self.dst_chain().id(), e)) + 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 + )) + }) } pub fn dst_latest_height(&self) -> Result { self.dst_chain() .query_latest_height() - .map_err(|e| LinkError::query(self.dst_chain().id(), e)) + .map_err(|e| LinkError::QueryError(self.dst_chain().id(), e)) } fn unordered_channel(&self) -> bool { @@ -157,14 +173,14 @@ impl RelayPath { let client = self.restore_dst_client(); client .build_update_client(height) - .map_err(LinkError::client) + .map_err(LinkError::ClientError) } 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::client) + .map_err(LinkError::ClientError) } fn build_chan_close_confirm_from_event(&self, event: &IbcEvent) -> Result { @@ -172,7 +188,7 @@ impl RelayPath { let proofs = self .src_chain() .build_channel_proofs(self.src_port_id(), src_channel_id, event.height()) - .map_err(|e| LinkError::channel(ChannelError::channel_proof(e)))?; + .map_err(|e| ChannelError::Failed(format!("failed to build channel proofs: {}", e)))?; // Build the domain type message let new_msg = MsgChannelCloseConfirm { @@ -243,7 +259,8 @@ impl RelayPath { return Ok(()); } } - Err(LinkError::old_packet_clearing_failed()) + + Err(LinkError::OldPacketClearingFailed) } /// Clears any packets that were sent before `height`, either if the `clear_packets` flag @@ -258,9 +275,17 @@ impl RelayPath { // Clearing may still happen: upon new blocks, when `force = true`. self.clear_packets = false; - let clear_height = height - .map(|h| h.decrement().map_err(|e| LinkError::decrement_height(h, e))) - .transpose()?; + 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 + }; info!(height = ?clear_height, "[{}] clearing pending packets", self); @@ -455,9 +480,9 @@ impl RelayPath { return Ok(summary); } - Err(LinkError(error::LinkErrorDetail::Send(e), _)) => { + Err(LinkError::SendError(ev)) => { // This error means we could retry - error!("[{}] error {}", self, e.event); + error!("[{}] error {}", self, ev); if i + 1 == MAX_RETRIES { error!( "[{}] {}/{} retries exhausted. giving up", @@ -580,7 +605,7 @@ impl RelayPath { let msgs = odata.assemble_msgs(self)?; - let tx_events = target.send_msgs(msgs).map_err(LinkError::relayer)?; + let tx_events = target.send_msgs(msgs)?; info!("[{}] result {}\n", self, PrettyEvents(&tx_events)); let ev = tx_events @@ -589,21 +614,20 @@ impl RelayPath { .find(|event| matches!(event, IbcEvent::ChainError(_))); match ev { - Some(ev) => Err(LinkError::send(ev)), + Some(ev) => Err(LinkError::SendError(Box::new(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()], - }) - .map_err(LinkError::relayer)?; + 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()], + })?; Ok(unreceived_packet.is_empty()) } @@ -611,16 +635,13 @@ 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(), - ) - .map_err(LinkError::relayer)?; + let (bytes, _) = self.src_chain().build_packet_proofs( + PacketMsgType::Recv, + self.src_port_id(), + self.src_channel_id()?, + packet.sequence, + Height::zero(), + )?; Ok(bytes.is_empty()) } @@ -635,14 +656,13 @@ 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()], - }) - .map_err(LinkError::relayer)?; + 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()], + })?; Ok(unreceived_ack.is_empty()) } @@ -681,10 +701,7 @@ impl RelayPath { i + 1, MAX_RETRIES, ); - let dst_tx_events = self - .dst_chain() - .send_msgs(dst_update) - .map_err(LinkError::relayer)?; + let dst_tx_events = self.dst_chain().send_msgs(dst_update)?; info!("[{}] result {}\n", self, PrettyEvents(&dst_tx_events)); dst_err_ev = dst_tx_events @@ -696,9 +713,12 @@ impl RelayPath { } } - Err(LinkError::client(ForeignClientError::chain_error_event( - self.dst_chain().id(), - dst_err_ev.unwrap(), + Err(LinkError::ClientError(ForeignClientError::ClientUpdate( + format!( + "Failed to update client on destination {} with err: {}", + self.dst_chain().id(), + dst_err_ev.unwrap() + ), ))) } @@ -722,10 +742,7 @@ impl RelayPath { dst_chain_height, ); - let src_tx_events = self - .src_chain() - .send_msgs(src_update) - .map_err(LinkError::relayer)?; + let src_tx_events = self.src_chain().send_msgs(src_update)?; info!("[{}] result {}\n", self, PrettyEvents(&src_tx_events)); src_err_ev = src_tx_events @@ -737,9 +754,12 @@ impl RelayPath { } } - Err(LinkError::client(ForeignClientError::chain_error_event( - self.src_chain().id(), - src_err_ev.unwrap(), + Err(LinkError::ClientError(ForeignClientError::ClientUpdate( + format!( + "Failed to update client on source {} with err: {}", + self.src_chain().id(), + src_err_ev.unwrap() + ), ))) } @@ -759,10 +779,8 @@ 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) - .map_err(LinkError::relayer)?; + let (packet_commitments, src_response_height) = + self.src_chain().query_packet_commitments(pc_request)?; let query_height = opt_query_height.unwrap_or(src_response_height); @@ -780,8 +798,7 @@ impl RelayPath { let sequences: Vec = self .dst_chain() - .query_unreceived_packets(request) - .map_err(LinkError::relayer)? + .query_unreceived_packets(request)? .into_iter() .map(From::from) .collect(); @@ -816,22 +833,16 @@ impl RelayPath { height: query_height, }); - events_result = self - .src_chain() - .query_txs(query) - .map_err(LinkError::relayer)?; + events_result = self.src_chain().query_txs(query)?; let mut packet_sequences = vec![]; for event in events_result.iter() { - 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())), + 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; } } info!( @@ -864,7 +875,7 @@ impl RelayPath { let (acks_on_source, src_response_height) = self .src_chain() .query_packet_acknowledgements(pc_request) - .map_err(|e| LinkError::query(self.src_chain().id(), e))?; + .map_err(|e| LinkError::QueryError(self.src_chain().id(), e))?; let query_height = opt_query_height.unwrap_or(src_response_height); @@ -884,7 +895,7 @@ impl RelayPath { let sequences: Vec = self .dst_chain() .query_unreceived_acknowledgement(request) - .map_err(|e| LinkError::query(self.dst_chain().id(), e))? + .map_err(|e| LinkError::QueryError(self.dst_chain().id(), e))? .into_iter() .map(From::from) .collect(); @@ -921,21 +932,16 @@ impl RelayPath { sequences, height: query_height, })) - .map_err(|e| LinkError::query(self.src_chain().id(), e))?; + .map_err(|e| LinkError::QueryError(self.src_chain().id(), e))?; let mut packet_sequences = vec![]; for event in events_result.iter() { - 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())); - } + 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; } } info!("[{}] found unprocessed WriteAcknowledgement events for {:?} (first 10 shown here; total={})", self, packet_sequences, events_result.len()); @@ -1004,7 +1010,7 @@ impl RelayPath { packet.sequence, height, ) - .map_err(|e| LinkError::packet_proofs_constructor(self.src_chain().id(), e))?; + .map_err(|e| LinkError::PacketProofsConstructor(self.src_chain().id(), e))?; let msg = MsgRecvPacket::new(packet.clone(), proofs.clone(), self.dst_signer()?); @@ -1033,7 +1039,7 @@ impl RelayPath { packet.sequence, event.height, ) - .map_err(|e| LinkError::packet_proofs_constructor(self.src_chain().id(), e))?; + .map_err(|e| LinkError::PacketProofsConstructor(self.src_chain().id(), e))?; let msg = MsgAcknowledgement::new( packet, @@ -1066,7 +1072,7 @@ impl RelayPath { port_id: self.dst_port_id().to_string(), channel_id: dst_channel_id.to_string(), }) - .map_err(|e| LinkError::query(self.dst_chain().id(), e))?; + .map_err(|e| ChannelError::QueryError(self.dst_chain().id(), e))?; (PacketMsgType::TimeoutOrdered, next_seq) } else { (PacketMsgType::TimeoutUnordered, packet.sequence) @@ -1081,7 +1087,7 @@ impl RelayPath { next_sequence_received, height, ) - .map_err(|e| LinkError::packet_proofs_constructor(self.dst_chain().id(), e))?; + .map_err(|e| LinkError::PacketProofsConstructor(self.dst_chain().id(), e))?; let msg = MsgTimeout::new( packet.clone(), @@ -1114,7 +1120,7 @@ impl RelayPath { packet.sequence, height, ) - .map_err(|e| LinkError::packet_proofs_constructor(self.dst_chain().id(), e))?; + .map_err(|e| LinkError::PacketProofsConstructor(self.dst_chain().id(), e))?; let msg = MsgTimeoutOnClose::new( packet.clone(), diff --git a/relayer/src/object.rs b/relayer/src/object.rs index 0be81eec4a..6a7f0c366e 100644 --- a/relayer/src/object.rs +++ b/relayer/src/object.rs @@ -1,4 +1,4 @@ -use flex_error::define_error; +use anomaly::BoxError; use serde::{Deserialize, Serialize}; use ibc::{ @@ -18,8 +18,6 @@ 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)] @@ -148,42 +146,6 @@ 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`]. @@ -283,16 +245,15 @@ 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()) - .map_err(ObjectError::relayer)?; - + ) -> Result { + let client_state = dst_chain.query_client_state(e.client_id(), Height::zero())?; if client_state.refresh_period().is_none() { - return Err(ObjectError::refresh_not_required( - e.client_id().clone(), - dst_chain.id(), - )); + return Err(format!( + "client '{}' on chain {} does not require refresh", + e.client_id(), + dst_chain.id() + ) + .into()); } let src_chain_id = client_state.chain_id(); @@ -309,20 +270,19 @@ 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(|| ObjectError::missing_channel_id(e.clone()))?; - - let client = channel_connection_client(chain, e.port_id(), channel_id) - .map_err(ObjectError::supervisor)? - .client; + .ok_or_else(|| format!("channel_id missing in channel open event '{:?}'", e))?; + let client = channel_connection_client(chain, e.port_id(), channel_id)?.client; if client.client_state.refresh_period().is_none() { - return Err(ObjectError::refresh_not_required( + return Err(format!( + "client '{}' on chain {} does not require refresh", client.client_id, - chain.id(), - )); + chain.id() + ) + .into()); } Ok(Client { @@ -337,11 +297,13 @@ 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(|| ObjectError::missing_connection_id(e.clone()))?; + ) -> Result { + let connection_id = e.connection_id.as_ref().ok_or_else(|| { + format!( + "connection_id missing from connection handshake event '{:?}'", + e + ) + })?; let dst_chain_id = counterparty_chain_from_connection(src_chain, connection_id) .map_err(ObjectError::supervisor)?; @@ -358,10 +320,10 @@ 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(|| ObjectError::missing_channel_id(attributes.clone()))?; + .ok_or_else(|| format!("channel_id missing in event attributes'{:?}'", attributes))?; let dst_chain_id = counterparty_chain_from_channel(src_chain, channel_id, attributes.port_id()) @@ -380,10 +342,10 @@ 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(|| ObjectError::missing_channel_id(attributes.clone()))?; + .ok_or_else(|| format!("channel_id missing in event attributes'{:?}'", attributes))?; let dst_chain_id = counterparty_chain_from_channel(src_chain, channel_id, attributes.port_id()) @@ -399,16 +361,12 @@ 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, @@ -423,13 +381,12 @@ 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, @@ -444,13 +401,12 @@ 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, diff --git a/relayer/src/registry.rs b/relayer/src/registry.rs index b980ec4377..f168a0d3d7 100644 --- a/relayer/src/registry.rs +++ b/relayer/src/registry.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, sync::Arc}; -use flex_error::define_error; +use anomaly::BoxError; use tokio::runtime::Runtime as TokioRuntime; use tracing::{trace, warn}; @@ -11,7 +11,6 @@ use ibc::ics24_host::identifier::ChainId; use crate::{ chain::{handle::ChainHandle, runtime::ChainRuntime, CosmosSdkChain}, config::Config, - error::Error as RelayerError, supervisor::RwArc, }; @@ -24,24 +23,6 @@ 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 { @@ -66,7 +47,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, SpawnError> { + pub fn get_or_spawn(&mut self, chain_id: &ChainId) -> Result, BoxError> { self.spawn(chain_id)?; let handle = self @@ -81,7 +62,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); @@ -108,16 +89,15 @@ pub fn spawn_chain_runtime( config: &RwArc, chain_id: &ChainId, rt: Arc, -) -> Result, SpawnError> { +) -> Result, BoxError> { let chain_config = config .read() .expect("poisoned lock") .find_chain(chain_id) .cloned() - .ok_or_else(|| SpawnError::missing_chain(chain_id.clone()))?; + .ok_or_else(|| format!("missing chain for id ({}) in configuration file", chain_id))?; - let handle = - ChainRuntime::::spawn(chain_config, rt).map_err(SpawnError::relayer)?; + let handle = ChainRuntime::::spawn(chain_config, rt)?; Ok(handle) } diff --git a/relayer/src/sdk_error.rs b/relayer/src/sdk_error.rs deleted file mode 100644 index 74009ef1c4..0000000000 --- a/relayer/src/sdk_error.rs +++ /dev/null @@ -1,168 +0,0 @@ -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 cabe9077c2..d9818da5f6 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, ErrorDetail as EventErrorDetail, EventBatch}, + event::monitor::{Error as EventError, EventBatch, UnwrapOrClone}, object::Object, registry::Registry, telemetry::Telemetry, @@ -28,11 +28,11 @@ use crate::{ }; pub mod client_state_filter; -pub mod error; +mod error; use client_state_filter::{FilterPolicy, Permission}; -pub use error::{Error, ErrorDetail}; +pub use error::Error; 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.clone()); + let mut collected = CollectedEvents::new(batch.height, batch.chain_id); let handshake_enabled = self .config @@ -188,20 +188,16 @@ 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.clone()); + collected.new_block = Some(event); } 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.clone()); + collected.per_object.entry(object).or_default().push(event); } } } @@ -217,11 +213,7 @@ 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.clone()); + collected.per_object.entry(object).or_default().push(event); } } IbcEvent::OpenInitChannel(..) | IbcEvent::OpenTryChannel(..) => { @@ -234,11 +226,7 @@ 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.clone()); + collected.per_object.entry(object).or_default().push(event); } } IbcEvent::OpenAckChannel(ref open_ack) => { @@ -272,7 +260,7 @@ impl Supervisor { .per_object .entry(channel_object) .or_default() - .push(event.clone()); + .push(event); } } } @@ -299,38 +287,22 @@ 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.clone()); + collected.per_object.entry(object).or_default().push(event); } } IbcEvent::TimeoutPacket(ref packet) => { if let Ok(object) = Object::for_timeout_packet(packet, src_chain) { - collected - .per_object - .entry(object) - .or_default() - .push(event.clone()); + collected.per_object.entry(object).or_default().push(event); } } IbcEvent::WriteAcknowledgement(ref packet) => { if let Ok(object) = Object::for_write_ack(packet, src_chain) { - collected - .per_object - .entry(object) - .or_default() - .push(event.clone()); + collected.per_object.entry(object).or_default().push(event); } } 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.clone()); + collected.per_object.entry(object).or_default().push(event); } } _ => (), @@ -358,7 +330,7 @@ impl Supervisor { } /// Run the supervisor event loop. - pub fn run(mut self) -> Result<(), Error> { + pub fn run(mut self) -> Result<(), BoxError> { self.spawn_workers(SpawnMode::Startup); let mut subscriptions = self.init_subscriptions()?; @@ -380,8 +352,8 @@ impl Supervisor { Ok(subs) => { subscriptions = subs; } - Err(Error(ErrorDetail::NoChainsAvailable(_), _)) => (), - Err(e) => return Err(e), + Err(Error::NoChainsAvailable) => (), + Err(e) => return Err(e.into()), } } } @@ -420,7 +392,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::no_chains_available()); + return Err(Error::NoChainsAvailable); } Ok(subscriptions) @@ -562,25 +534,17 @@ impl Supervisor { fn handle_batch(&mut self, chain: Box, batch: ArcBatch) { let chain_id = 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(EventError(EventErrorDetail::SubscriptionCancelled(_), _)) => { + 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"); - - 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) + self.clear_pending_packets(&chain_id) } + Err(e) => Err(e.into()), + }; + + if let Err(e) = result { + error!("[{}] error during batch processing: {}", chain_id, e); } } @@ -588,8 +552,8 @@ impl Supervisor { fn process_batch( &mut self, src_chain: Box, - batch: &EventBatch, - ) -> Result<(), Error> { + batch: EventBatch, + ) -> Result<(), BoxError> { assert_eq!(src_chain.id(), batch.chain_id); let height = batch.height; @@ -612,41 +576,30 @@ impl Supervisor { continue; } - 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 src = self.registry.get_or_spawn(object.src_chain_id())?; + let dst = self.registry.get_or_spawn(object.dst_chain_id())?; 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()) - .map_err(Error::worker)? + worker.send_events(height, events, chain_id.clone())? } // 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) - .map_err(Error::worker)? + worker.send_new_block(height, new_block)?; } } Ok(()) } - fn clear_pending_packets(&mut self, chain_id: &ChainId) -> Result<(), Error> { + fn clear_pending_packets(&mut self, chain_id: &ChainId) -> Result<(), BoxError> { for worker in self.workers.workers_for_chain(chain_id) { - worker.clear_pending_packets().map_err(Error::worker)?; + worker.clear_pending_packets()?; } Ok(()) diff --git a/relayer/src/supervisor/client_state_filter.rs b/relayer/src/supervisor/client_state_filter.rs index 610f2a11fd..8514688a5a 100644 --- a/relayer/src/supervisor/client_state_filter.rs +++ b/relayer/src/supervisor/client_state_filter.rs @@ -1,18 +1,17 @@ use std::collections::HashMap; -use flex_error::define_error; +use anomaly::BoxError; 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::Error as ChannelError; +use ibc::ics04_channel::error::Kind; 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, SpawnError}; +use crate::registry::Registry; #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Permission { @@ -37,23 +36,6 @@ 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)] @@ -80,7 +62,7 @@ impl FilterPolicy { client_state: &AnyClientState, connection: &ConnectionEnd, connection_id: &ConnectionId, - ) -> Result { + ) -> Result { let identifier = CacheKey::Connection(chain_id.clone(), connection_id.clone()); trace!( @@ -96,9 +78,7 @@ 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) - .map_err(FilterError::spawn)?; + let counterparty_chain = registry.get_or_spawn(&counterparty_chain_id)?; let counterparty_client_id = connection.counterparty().client_id(); let counterparty_client_state = counterparty_chain .query_client_state(counterparty_client_id, Height::zero()) @@ -186,7 +166,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!( @@ -200,9 +180,7 @@ impl FilterPolicy { return Ok(*p); } - let chain = registry - .get_or_spawn(&obj.dst_chain_id) - .map_err(FilterError::spawn)?; + let chain = registry.get_or_spawn(&obj.dst_chain_id)?; trace!( "[client filter] deciding if to relay on {:?} hosted chain {}", @@ -210,10 +188,7 @@ impl FilterPolicy { obj.dst_chain_id ); - let client_state = chain - .query_client_state(&obj.dst_client_id, Height::zero()) - .map_err(FilterError::relayer)?; - + let client_state = chain.query_client_state(&obj.dst_client_id, Height::zero())?; Ok(self.control_client(&obj.dst_chain_id, &obj.dst_client_id, &client_state)) } @@ -221,7 +196,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()); @@ -236,10 +211,7 @@ impl FilterPolicy { return Ok(*p); } - let src_chain = registry - .get_or_spawn(&obj.src_chain_id) - .map_err(FilterError::spawn)?; - + let src_chain = registry.get_or_spawn(&obj.src_chain_id)?; trace!( "[client filter] deciding if to relay on {:?} hosted on chain {}", obj, @@ -269,7 +241,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!( @@ -292,10 +264,7 @@ impl FilterPolicy { .map_err(FilterError::relayer)?; let conn_id = channel_end.connection_hops.first().ok_or_else(|| { - FilterError::channel(ChannelError::invalid_connection_hops_length( - 1, - channel_end.connection_hops().len(), - )) + Kind::InvalidConnectionHopsLength(1, channel_end.connection_hops().len()) })?; let connection_end = src_chain @@ -330,7 +299,7 @@ impl FilterPolicy { &mut self, registry: &mut Registry, obj: &object::Channel, - ) -> Result { + ) -> Result { self.control_channel( registry, &obj.src_chain_id, @@ -343,7 +312,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 14fe540693..e2d73192a9 100644 --- a/relayer/src/supervisor/error.rs +++ b/relayer/src/supervisor/error.rs @@ -1,70 +1,28 @@ -use flex_error::define_error; +use thiserror::Error; use ibc::ics03_connection::connection::Counterparty; use ibc::ics24_host::identifier::{ChainId, ChannelId, ConnectionId, PortId}; -use crate::error::Error as RelayerError; -use crate::registry::SpawnError; -use crate::worker::WorkerError; +#[derive(Clone, Debug, Error, PartialEq, Eq)] +pub enum Error { + #[error("port/channel {0}/{1} on chain {1} is not initialized")] + ChannelUninitialized(PortId, ChannelId, ChainId), -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("channel {0} on chain {1} has a connection with uninitialized counterparty {:2}")] + ChannelConnectionUninitialized(ChannelId, ChainId, Counterparty), - 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("connection {0} (underlying channel {1}) on chain {2} is not open")] + ConnectionNotOpen(ConnectionId, 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("channel {0} on chain {1} has no connection hops specified")] + MissingConnectionHops(ChannelId, ChainId), - 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("query failed with error: {0}")] + QueryFailed(String), - Relayer - [ RelayerError ] - |_| { "relayer error" }, + #[error("supervisor was not able to connect to any chains")] + NoChainsAvailable, - 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" }, - } + #[error("failed to spawn chain runtime: {0}")] + FailedToSpawnChainRuntime(String), } diff --git a/relayer/src/supervisor/spawn.rs b/relayer/src/supervisor/spawn.rs index 79112ceae5..eede205df8 100644 --- a/relayer/src/supervisor/spawn.rs +++ b/relayer/src/supervisor/spawn.rs @@ -1,3 +1,4 @@ +use anomaly::BoxError; use itertools::Itertools; use tracing::{debug, error, warn}; @@ -23,7 +24,6 @@ use crate::{ object::{Channel, Client, Connection, Object, Packet}, registry::Registry, supervisor::client_state_filter::{FilterPolicy, Permission}, - supervisor::error::Error as SupervisorError, worker::WorkerMap, }; @@ -383,13 +383,15 @@ 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()) - .map_err(Error::spawn)?; + .get_or_spawn(&client.client_state.chain_id())?; - connection_state_on_destination(connection, counterparty_chain.as_ref()) + Ok(connection_state_on_destination( + connection, + counterparty_chain.as_ref(), + )?) } fn spawn_connection_workers( @@ -397,7 +399,7 @@ impl<'a> SpawnContext<'a> { chain: Box, client: IdentifiedAnyClientState, connection: IdentifiedConnectionEnd, - ) -> Result<(), Error> { + ) -> Result<(), BoxError> { let handshake_enabled = self .config .read() @@ -406,8 +408,7 @@ impl<'a> SpawnContext<'a> { let counterparty_chain = self .registry - .get_or_spawn(&client.client_state.chain_id()) - .map_err(Error::spawn)?; + .get_or_spawn(&client.client_state.chain_id())?; let conn_state_src = connection.connection_end.state; let conn_state_dst = @@ -474,7 +475,7 @@ impl<'a> SpawnContext<'a> { let counterparty_chain = self .registry .get_or_spawn(&client.client_state.chain_id()) - .map_err(SupervisorError::spawn)?; + .map_err(|e| Error::FailedToSpawnChainRuntime(e.to_string()))?; 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 f5f360b64c..aa3329fa5b 100644 --- a/relayer/src/transfer.rs +++ b/relayer/src/transfer.rs @@ -1,10 +1,12 @@ use std::time::Duration; -use flex_error::{define_error, DetailOnly}; +use thiserror::Error; +use tracing::error; + 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, TimestampOverflowError}; +use ibc::timestamp::Timestamp; use ibc::tx_msg::Msg; use ibc::Height; @@ -12,42 +14,21 @@ use crate::chain::handle::ChainHandle; use crate::config::ChainConfig; use crate::error::Error; -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(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, } #[derive(Clone, Debug)] @@ -73,14 +54,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::timestamp_overflow)? + (Timestamp::now() + opts.timeout_seconds).map_err(|_| PacketError::TimestampOverflow)? }; let timeout_height = if opts.timeout_height_offset == 0 { @@ -88,7 +69,7 @@ pub fn build_and_send_transfer_messages( } else { packet_dst_chain .query_latest_height() - .map_err(PacketError::relayer)? + .map_err(|_| PacketError::Failed("Height error".to_string()))? .add(opts.timeout_height_offset) }; @@ -110,7 +91,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 @@ -121,7 +102,7 @@ pub fn build_and_send_transfer_messages( None => Ok(events), Some(err) => { if let IbcEvent::ChainError(err) = err { - Err(PacketError::tx_response(err.clone())) + Err(PacketError::Failed(err.to_string())) } else { panic!( "internal error, expected IBCEvent::ChainError, got {:?}", diff --git a/relayer/src/upgrade_chain.rs b/relayer/src/upgrade_chain.rs index f99cb6831e..ee6428a7e4 100644 --- a/relayer/src/upgrade_chain.rs +++ b/relayer/src/upgrade_chain.rs @@ -1,40 +1,32 @@ use std::time::Duration; -use flex_error::define_error; +use prost_types::Any; +use thiserror::Error; +use tracing::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; -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) - }, +#[derive(Debug, Error)] +pub enum UpgradeChainError { + #[error("failed with underlying cause: {0}")] + Failed(String), - } + #[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)] @@ -90,7 +82,9 @@ pub fn build_and_send_upgrade_chain_message( }; // build the msg submit proposal - let proposer = dst_chain.get_signer().map_err(UpgradeChainError::key)?; + let proposer = dst_chain + .get_signer() + .map_err(UpgradeChainError::KeyError)?; let coins = ibc_proto::cosmos::base::v1beta1::Coin { denom: "stake".to_string(), @@ -112,7 +106,7 @@ pub fn build_and_send_upgrade_chain_message( let events = dst_chain .send_msgs(vec![any_msg]) - .map_err(|e| UpgradeChainError::submit(dst_chain.id().clone(), e))?; + .map_err(|e| UpgradeChainError::SubmitError(dst_chain.id().clone(), e))?; // Check if the chain rejected the transaction let result = events.iter().find_map(|event| match event { @@ -122,6 +116,6 @@ pub fn build_and_send_upgrade_chain_message( match result { None => Ok(events), - Some(reason) => Err(UpgradeChainError::tx_response(reason)), + Some(reason) => Err(UpgradeChainError::Failed(reason)), } } diff --git a/relayer/src/util.rs b/relayer/src/util.rs index dc9e41a85e..be2367c749 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::try_recv_multiple; +pub use recv_multiple::{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 74dc0988a6..f0992dedf5 100644 --- a/relayer/src/util/recv_multiple.rs +++ b/relayer/src/util/recv_multiple.rs @@ -1,3 +1,4 @@ +use anomaly::BoxError; use crossbeam_channel::{Receiver, Select}; pub fn try_recv_multiple(rs: &[(K, Receiver)]) -> Option<(&K, T)> { @@ -19,3 +20,23 @@ 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 e7fbe2d6f4..1726d3b0f8 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::Error; +use crate::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> { + pub fn get(&self, db: &sled::Db) -> Result, error::Error> { self.fetch(db, &()) } - pub fn set(&self, db: &sled::Db, value: &V) -> Result<(), Error> { + pub fn set(&self, db: &sled::Db, value: &V) -> Result<(), error::Error> { self.insert(db, &(), value) } } @@ -52,16 +52,19 @@ where prefix_bytes } - pub fn fetch(&self, db: &sled::Db, key: &K) -> Result, Error> { - let key_bytes = serde_cbor::to_vec(&key).map_err(Error::cbor)?; + 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))?; let prefixed_key_bytes = self.prefixed_key(key_bytes); - let value_bytes = db.get(prefixed_key_bytes).map_err(Error::store)?; + let value_bytes = db + .get(prefixed_key_bytes) + .map_err(|e| error::Kind::Store.context(e))?; match value_bytes { Some(bytes) => { - let value = serde_cbor::from_slice(&bytes).map_err(Error::cbor)?; + let value = + serde_cbor::from_slice(&bytes).map_err(|e| error::Kind::Store.context(e))?; Ok(value) } None => Ok(None), @@ -79,16 +82,16 @@ where // Ok(exists) // } - 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)?; + 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))?; let prefixed_key_bytes = self.prefixed_key(key_bytes); - let value_bytes = serde_cbor::to_vec(&value).map_err(Error::cbor)?; + let value_bytes = serde_cbor::to_vec(&value).map_err(|e| error::Kind::Store.context(e))?; db.insert(prefixed_key_bytes, value_bytes) .map(|_| ()) - .map_err(Error::store)?; + .map_err(|e| error::Kind::Store.context(e))?; Ok(()) } diff --git a/relayer/src/worker.rs b/relayer/src/worker.rs index 07d565b0e9..faf467f674 100644 --- a/relayer/src/worker.rs +++ b/relayer/src/worker.rs @@ -8,9 +8,6 @@ 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 e271c34f21..5403099c37 100644 --- a/relayer/src/worker/channel.rs +++ b/relayer/src/worker/channel.rs @@ -1,5 +1,6 @@ use std::{thread, time::Duration}; +use anomaly::BoxError; use crossbeam_channel::Receiver; use tracing::{debug, info, warn}; @@ -10,7 +11,6 @@ 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<(), RunError> { + pub(crate) fn run(self) -> Result<(), BoxError> { let a_chain = self.chains.a.clone(); let b_chain = self.chains.b.clone(); @@ -66,8 +66,7 @@ impl ChannelWorker { a_chain.clone(), b_chain.clone(), event.clone(), - ) - .map_err(RunError::channel)?; + )?; retry_with_index( retry_strategy::worker_default_strategy(), @@ -90,15 +89,14 @@ impl ChannelWorker { "Channel worker starts processing block event at {:#?}", current_height ); - let height = current_height.decrement().map_err(RunError::ics02)?; + let height = current_height.decrement()?; 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 2cfc000225..2ea171c62a 100644 --- a/relayer/src/worker/client.rs +++ b/relayer/src/worker/client.rs @@ -1,5 +1,6 @@ use std::{thread, time::Duration}; +use anomaly::BoxError; use crossbeam_channel::Receiver; use tracing::{debug, info, trace, warn}; @@ -7,13 +8,12 @@ use ibc::{events::IbcEvent, ics02_client::events::UpdateClient}; use crate::{ chain::handle::ChainHandlePair, - foreign_client::{ForeignClient, ForeignClientErrorDetail, MisbehaviourResults}, + foreign_client::{ForeignClient, ForeignClientError, 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<(), RunError> { + pub fn run(self) -> Result<(), BoxError> { let mut client = ForeignClient::restore( self.client.dst_client_id.clone(), self.chains.b.clone(), @@ -70,14 +70,12 @@ impl ClientWorker { ) }; } - Err(e) => { - if let ForeignClientErrorDetail::ExpiredOrFrozen(_) = e.detail() { - warn!("failed to refresh client '{}': {}", client, e); + Err(e @ ForeignClientError::ExpiredOrFrozen(..)) => { + 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 ddf5dc170c..8464541670 100644 --- a/relayer/src/worker/connection.rs +++ b/relayer/src/worker/connection.rs @@ -1,5 +1,6 @@ use std::{thread, time::Duration}; +use anomaly::BoxError; use crossbeam_channel::Receiver; use tracing::{debug, info, warn}; @@ -10,7 +11,6 @@ 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<(), RunError> { + pub(crate) fn run(self) -> Result<(), BoxError> { let a_chain = self.chains.a.clone(); let b_chain = self.chains.b.clone(); @@ -67,8 +67,7 @@ impl ConnectionWorker { a_chain.clone(), b_chain.clone(), event.clone(), - ) - .map_err(RunError::connection)?; + )?; retry_with_index( retry_strategy::worker_default_strategy(), @@ -93,7 +92,7 @@ impl ConnectionWorker { current_height ); - let height = current_height.decrement().map_err(RunError::ics02)?; + let height = current_height.decrement()?; let (mut handshake_connection, state) = RelayConnection::restore_from_state( @@ -101,8 +100,7 @@ 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 deleted file mode 100644 index 3aefeda5f8..0000000000 --- a/relayer/src/worker/error.rs +++ /dev/null @@ -1,50 +0,0 @@ -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 3780a782df..2d18420134 100644 --- a/relayer/src/worker/handle.rs +++ b/relayer/src/worker/handle.rs @@ -3,6 +3,7 @@ use std::{ thread::{self, JoinHandle}, }; +use anomaly::BoxError; use crossbeam_channel::Sender; use tracing::trace; @@ -12,7 +13,6 @@ 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,35 +53,33 @@ impl WorkerHandle { height: Height, events: Vec, chain_id: ChainId, - ) -> Result<(), WorkerError> { + ) -> Result<(), BoxError> { let batch = EventBatch { chain_id, height, events, }; - self.tx - .send(WorkerCmd::IbcEvents { batch }) - .map_err(WorkerError::send) + self.tx.send(WorkerCmd::IbcEvents { batch })?; + 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) + /// 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(()) } /// Instruct the worker to clear pending packets. - pub fn clear_pending_packets(&self) -> Result<(), WorkerError> { - self.tx - .send(WorkerCmd::ClearPendingPackets) - .map_err(WorkerError::send) + pub fn clear_pending_packets(&self) -> Result<(), BoxError> { + self.tx.send(WorkerCmd::ClearPendingPackets)?; + Ok(()) } /// Shutdown the worker. - pub fn shutdown(&self) -> Result<(), WorkerError> { - self.tx.send(WorkerCmd::Shutdown).map_err(WorkerError::send) + pub fn shutdown(&self) -> Result<(), BoxError> { + self.tx.send(WorkerCmd::Shutdown)?; + Ok(()) } /// Wait for the worker thread to finish. diff --git a/relayer/src/worker/packet.rs b/relayer/src/worker/packet.rs index 3b65a9a834..83f77e2b36 100644 --- a/relayer/src/worker/packet.rs +++ b/relayer/src/worker/packet.rs @@ -1,5 +1,6 @@ use std::time::Duration; +use anomaly::BoxError; use crossbeam_channel::Receiver; use tracing::{error, info, warn}; @@ -13,7 +14,6 @@ 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<(), RunError> { + pub fn run(self) -> Result<(), BoxError> { let mut link = Link::new_from_opts( self.chains.a.clone(), self.chains.b.clone(), @@ -56,13 +56,10 @@ 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 is_closed { + if link.is_closed()? { warn!("channel is closed, exiting"); return Ok(()); } @@ -92,7 +89,7 @@ impl PacketWorker { } Err(retries) => { - return Err(RunError::retry(retries)); + return Err(format!("Packet worker failed after {} retries", retries).into()); } } }