From 8ddc68804c9bbafb6f9f925cb1536592e97c7d61 Mon Sep 17 00:00:00 2001 From: Adi Seredinschi Date: Thu, 3 Dec 2020 16:59:37 +0100 Subject: [PATCH] Local chain implementation & basic tests (#422) * Attemptin integration at the level of Chain trait * Removed rpc client from trait Chain * Removed ChainConfig from trait Chain * Minor fixes as follow-up from #364 * Impl generic runtime instantiation. * Migrated CLIs to use generic runtime spawn. * Added mock light client. First test (incomplete impl). * Almost ready to send_tx (mock has to be mutable). * Added chain executor. * Mutable chain in send_tx, simplified spawn.After review w/ Anca & Romain * impl CosmosSDKChain cleanup * Adapted mock context, ICS18 & 26 to correct abstractions. * Cleaned up Msg trait, added const TYPE_URL. * Basic light client impl to complete create client test. * Removed clippy allow exception * Updated changelog * Revert "Updated changelog" This reverts commit 43bd00876c29769b1a555eb4f109b8702b42c486. In anticipation of merging master into this dev branch.. * Redid the changelog * After Anca's comments --- CHANGELOG.md | 7 + modules/Cargo.toml | 4 +- modules/src/ics02_client/client_def.rs | 5 +- modules/src/ics02_client/height.rs | 3 + .../src/ics02_client/msgs/create_client.rs | 8 +- .../src/ics02_client/msgs/update_client.rs | 14 +- .../ics03_connection/msgs/conn_open_ack.rs | 15 +- .../msgs/conn_open_confirm.rs | 15 +- .../ics03_connection/msgs/conn_open_init.rs | 16 +- .../ics03_connection/msgs/conn_open_try.rs | 15 +- .../src/ics04_channel/msgs/acknowledgement.rs | 7 - .../ics04_channel/msgs/chan_close_confirm.rs | 7 - .../src/ics04_channel/msgs/chan_close_init.rs | 7 - .../src/ics04_channel/msgs/chan_open_ack.rs | 10 +- .../ics04_channel/msgs/chan_open_confirm.rs | 9 +- .../src/ics04_channel/msgs/chan_open_init.rs | 9 +- .../src/ics04_channel/msgs/chan_open_try.rs | 9 +- modules/src/ics04_channel/msgs/recv_packet.rs | 7 - modules/src/ics04_channel/msgs/timeout.rs | 7 - modules/src/ics18_relayer/context.rs | 13 +- modules/src/ics18_relayer/utils.rs | 7 +- modules/src/ics26_routing/context.rs | 8 +- modules/src/ics26_routing/error.rs | 6 + modules/src/ics26_routing/handler.rs | 44 ++- modules/src/lib.rs | 4 +- modules/src/mock/context.rs | 26 +- modules/src/tx_msg.rs | 3 +- relayer-cli/src/commands/query/channel.rs | 2 +- relayer-cli/src/commands/query/client.rs | 6 +- relayer-cli/src/commands/query/connection.rs | 2 +- relayer-cli/src/commands/tx/channel.rs | 7 +- relayer-cli/src/commands/tx/client.rs | 9 +- relayer-cli/src/commands/tx/connection.rs | 7 +- relayer-cli/src/commands/v0.rs | 5 +- relayer-cli/tests/integration.rs | 2 +- relayer/Cargo.toml | 2 + relayer/src/chain.rs | 58 ++-- relayer/src/chain/cosmos.rs | 253 +++++++++++------- relayer/src/chain/mock.rs | 214 +++++++++++++++ relayer/src/chain/runtime.rs | 90 +++---- relayer/src/foreign_client.rs | 49 ++++ relayer/src/keys/add.rs | 4 +- relayer/src/keys/list.rs | 2 +- relayer/src/keys/restore.rs | 4 +- relayer/src/light_client.rs | 3 + relayer/src/light_client/mock.rs | 47 ++++ relayer/src/light_client/tendermint.rs | 2 - 47 files changed, 697 insertions(+), 356 deletions(-) create mode 100644 relayer/src/chain/mock.rs create mode 100644 relayer/src/light_client/mock.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index f79db22df0..4d1b08d98d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased Changes +### FEATURES + +### IMPROVEMENTS + +- Mock chain (implementing IBC handlers) and integration against CLI ([#158]) + +[#158]: https://github.com/informalsystems/ibc-rs/issues/158 ## v0.0.5 *December 2, 2020* diff --git a/modules/Cargo.toml b/modules/Cargo.toml index 58baeefdaa..053f4db639 100644 --- a/modules/Cargo.toml +++ b/modules/Cargo.toml @@ -51,10 +51,10 @@ version = "=0.17.0-rc3" version = "=0.17.0-rc3" [dependencies.tendermint-testgen] -version = "0.17.0-rc2" +version = "0.17.0-rc3" optional = true [dev-dependencies] tokio = { version = "0.2", features = ["macros"] } subtle-encoding = { version = "0.5" } -tendermint-testgen = { version = "0.17.0-rc2" } # Needed for generating (synthetic) light blocks. +tendermint-testgen = { version = "0.17.0-rc3" } # Needed for generating (synthetic) light blocks. diff --git a/modules/src/ics02_client/client_def.rs b/modules/src/ics02_client/client_def.rs index dc615a2c63..98b9ddd3e0 100644 --- a/modules/src/ics02_client/client_def.rs +++ b/modules/src/ics02_client/client_def.rs @@ -9,7 +9,6 @@ use crate::ics02_client::error::{Error, Kind}; use crate::ics02_client::header::Header; use crate::ics02_client::state::{ClientState, ConsensusState}; use crate::ics03_connection::connection::ConnectionEnd; -use crate::ics07_tendermint as tendermint; use crate::ics07_tendermint::client_def::TendermintClient; use crate::ics07_tendermint::client_state::ClientState as TendermintClientState; use crate::ics07_tendermint::consensus_state::ConsensusState as TendermintConsensusState; @@ -93,7 +92,7 @@ pub trait ClientDef: Clone { #[derive(Clone, Debug, PartialEq)] // TODO: Add Eq bound once possible #[allow(clippy::large_enum_variant)] pub enum AnyHeader { - Tendermint(tendermint::header::Header), + Tendermint(TendermintHeader), #[cfg(any(test, feature = "mocks"))] Mock(MockHeader), @@ -258,7 +257,7 @@ impl ClientState for AnyClientState { #[derive(Clone, Debug, PartialEq, Eq)] pub enum AnyConsensusState { - Tendermint(crate::ics07_tendermint::consensus_state::ConsensusState), + Tendermint(TendermintConsensusState), #[cfg(any(test, feature = "mocks"))] Mock(MockConsensusState), diff --git a/modules/src/ics02_client/height.rs b/modules/src/ics02_client/height.rs index 8ac0df86a3..a4e5291f17 100644 --- a/modules/src/ics02_client/height.rs +++ b/modules/src/ics02_client/height.rs @@ -7,7 +7,10 @@ use ibc_proto::ibc::core::client::v1::Height as RawHeight; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct Height { + /// Previously known as "epoch", and will be renamed to "revision" soon pub version_number: u64, + + /// The height of a block pub version_height: u64, } diff --git a/modules/src/ics02_client/msgs/create_client.rs b/modules/src/ics02_client/msgs/create_client.rs index 365e30ed73..5051c3dba8 100644 --- a/modules/src/ics02_client/msgs/create_client.rs +++ b/modules/src/ics02_client/msgs/create_client.rs @@ -19,7 +19,7 @@ use crate::ics02_client::error::{Error, Kind}; use crate::ics24_host::identifier::ClientId; use crate::tx_msg::Msg; -const TYPE_MSG_CREATE_CLIENT: &str = "create_client"; +pub const TYPE_URL: &str = "/ibc.core.client.v1.MsgCreateClient"; /// A type of message that triggers the creation of a new on-chain (IBC) client. #[derive(Clone, Debug, PartialEq, Eq)] @@ -69,17 +69,13 @@ impl Msg for MsgCreateAnyClient { crate::keys::ROUTER_KEY.to_string() } - fn get_type(&self) -> String { - TYPE_MSG_CREATE_CLIENT.to_string() - } - fn validate_basic(&self) -> Result<(), Self::ValidationError> { // Nothing to validate since all fields are validated on creation. Ok(()) } fn type_url(&self) -> String { - "/ibc.core.client.v1.MsgCreateClient".to_string() + TYPE_URL.to_string() } fn get_signers(&self) -> Vec { diff --git a/modules/src/ics02_client/msgs/update_client.rs b/modules/src/ics02_client/msgs/update_client.rs index dc2e043246..f0269b0b31 100644 --- a/modules/src/ics02_client/msgs/update_client.rs +++ b/modules/src/ics02_client/msgs/update_client.rs @@ -17,7 +17,7 @@ use crate::ics02_client::error::{Error, Kind}; use crate::ics24_host::identifier::ClientId; use crate::tx_msg::Msg; -const TYPE_MSG_UPDATE_CLIENT: &str = "update_client"; +pub const TYPE_URL: &str = "/ibc.core.client.v1.MsgUpdateClient"; /// A type of message that triggers the update of an on-chain (IBC) client with new headers. #[derive(Clone, Debug, PartialEq)] // TODO: Add Eq bound when possible @@ -44,21 +44,17 @@ impl Msg for MsgUpdateAnyClient { crate::keys::ROUTER_KEY.to_string() } - fn get_type(&self) -> String { - TYPE_MSG_UPDATE_CLIENT.to_string() - } - fn validate_basic(&self) -> Result<(), Self::ValidationError> { // Nothing to validate since all fields are validated on creation. Ok(()) } - fn get_signers(&self) -> Vec { - vec![self.signer] + fn type_url(&self) -> String { + TYPE_URL.to_string() } - fn type_url(&self) -> String { - "/ibc.core.client.v1.MsgUpdateClient".to_string() + fn get_signers(&self) -> Vec { + vec![self.signer] } } diff --git a/modules/src/ics03_connection/msgs/conn_open_ack.rs b/modules/src/ics03_connection/msgs/conn_open_ack.rs index 02186b98de..9cf7d33e49 100644 --- a/modules/src/ics03_connection/msgs/conn_open_ack.rs +++ b/modules/src/ics03_connection/msgs/conn_open_ack.rs @@ -16,8 +16,7 @@ use crate::proofs::{ConsensusProof, Proofs}; use crate::tx_msg::Msg; use crate::Height; -/// Message type for the `MsgConnectionOpenAck` message. -pub const TYPE_MSG_CONNECTION_OPEN_ACK: &str = "connection_open_ack"; +pub const TYPE_URL: &str = "/ibc.core.connection.v1.MsgConnectionOpenAck"; /// Message definition `MsgConnectionOpenAck` (i.e., `ConnOpenAck` datagram). #[derive(Clone, Debug, PartialEq, Eq)] @@ -73,20 +72,16 @@ impl Msg for MsgConnectionOpenAck { crate::keys::ROUTER_KEY.to_string() } - fn get_type(&self) -> String { - TYPE_MSG_CONNECTION_OPEN_ACK.to_string() - } - fn validate_basic(&self) -> Result<(), Self::ValidationError> { Ok(()) } - fn get_signers(&self) -> Vec { - vec![self.signer] + fn type_url(&self) -> String { + TYPE_URL.to_string() } - fn type_url(&self) -> String { - "/ibc.core.connection.v1.MsgConnectionOpenAck".to_string() + fn get_signers(&self) -> Vec { + vec![self.signer] } } diff --git a/modules/src/ics03_connection/msgs/conn_open_confirm.rs b/modules/src/ics03_connection/msgs/conn_open_confirm.rs index ddd71b0ec0..a36a55d97a 100644 --- a/modules/src/ics03_connection/msgs/conn_open_confirm.rs +++ b/modules/src/ics03_connection/msgs/conn_open_confirm.rs @@ -10,8 +10,7 @@ use crate::ics03_connection::error::{Error, Kind}; use crate::ics24_host::identifier::ConnectionId; use crate::{proofs::Proofs, tx_msg::Msg}; -/// Message type for the `MsgConnectionOpenConfirm` message. -pub const TYPE_MSG_CONNECTION_OPEN_CONFIRM: &str = "connection_open_confirm"; +pub const TYPE_URL: &str = "/ibc.core.connection.v1.MsgConnectionOpenConfirm"; /// /// Message definition for `MsgConnectionOpenConfirm` (i.e., `ConnOpenConfirm` datagram). @@ -42,20 +41,16 @@ impl Msg for MsgConnectionOpenConfirm { crate::keys::ROUTER_KEY.to_string() } - fn get_type(&self) -> String { - TYPE_MSG_CONNECTION_OPEN_CONFIRM.to_string() - } - fn validate_basic(&self) -> Result<(), Error> { Ok(()) } - fn get_signers(&self) -> Vec { - vec![self.signer] + fn type_url(&self) -> String { + TYPE_URL.to_string() } - fn type_url(&self) -> String { - "/ibc.core.connection.v1.MsgConnectionOpenConfirm".to_string() + fn get_signers(&self) -> Vec { + vec![self.signer] } } diff --git a/modules/src/ics03_connection/msgs/conn_open_init.rs b/modules/src/ics03_connection/msgs/conn_open_init.rs index 7731e2b1d0..72a1dd701f 100644 --- a/modules/src/ics03_connection/msgs/conn_open_init.rs +++ b/modules/src/ics03_connection/msgs/conn_open_init.rs @@ -12,8 +12,8 @@ use crate::ics03_connection::version::validate_version; use crate::ics24_host::identifier::{ClientId, ConnectionId}; use crate::tx_msg::Msg; -/// Message type for the `MsgConnectionOpenInit` message. -pub const TYPE_MSG_CONNECTION_OPEN_INIT: &str = "connection_open_init"; +pub const TYPE_URL: &str = "/ibc.core.connection.v1.MsgConnectionOpenInit"; + /// /// Message definition `MsgConnectionOpenInit` (i.e., the `ConnOpenInit` datagram). /// @@ -55,10 +55,6 @@ impl Msg for MsgConnectionOpenInit { crate::keys::ROUTER_KEY.to_string() } - fn get_type(&self) -> String { - TYPE_MSG_CONNECTION_OPEN_INIT.to_string() - } - fn validate_basic(&self) -> Result<(), Self::ValidationError> { // All the validation is performed on creation self.counterparty @@ -66,12 +62,12 @@ impl Msg for MsgConnectionOpenInit { .map_err(|e| Kind::InvalidCounterparty.context(e).into()) } - fn get_signers(&self) -> Vec { - vec![self.signer] + fn type_url(&self) -> String { + TYPE_URL.to_string() } - fn type_url(&self) -> String { - "/ibc.core.connection.v1.MsgConnectionOpenInit".to_string() + fn get_signers(&self) -> Vec { + vec![self.signer] } } diff --git a/modules/src/ics03_connection/msgs/conn_open_try.rs b/modules/src/ics03_connection/msgs/conn_open_try.rs index cbf610d81e..7b6e5d0677 100644 --- a/modules/src/ics03_connection/msgs/conn_open_try.rs +++ b/modules/src/ics03_connection/msgs/conn_open_try.rs @@ -17,8 +17,7 @@ use crate::proofs::{ConsensusProof, Proofs}; use crate::tx_msg::Msg; use crate::Height; -/// Message type for the `MsgConnectionOpenTry` message. -pub const TYPE_MSG_CONNECTION_OPEN_TRY: &str = "connection_open_try"; +pub const TYPE_URL: &str = "/ibc.core.connection.v1.MsgConnectionOpenTry"; /// /// Message definition `MsgConnectionOpenTry` (i.e., `ConnOpenTry` datagram). @@ -88,22 +87,18 @@ impl Msg for MsgConnectionOpenTry { crate::keys::ROUTER_KEY.to_string() } - fn get_type(&self) -> String { - TYPE_MSG_CONNECTION_OPEN_TRY.to_string() - } - fn validate_basic(&self) -> Result<(), Self::ValidationError> { self.counterparty .validate_basic() .map_err(|e| Kind::InvalidCounterparty.context(e).into()) } - fn get_signers(&self) -> Vec { - vec![self.signer] + fn type_url(&self) -> String { + TYPE_URL.to_string() } - fn type_url(&self) -> String { - "/ibc.core.connection.v1.MsgConnectionOpenTry".to_string() + fn get_signers(&self) -> Vec { + vec![self.signer] } } diff --git a/modules/src/ics04_channel/msgs/acknowledgement.rs b/modules/src/ics04_channel/msgs/acknowledgement.rs index 2a3b2e04d6..463a64a7ee 100644 --- a/modules/src/ics04_channel/msgs/acknowledgement.rs +++ b/modules/src/ics04_channel/msgs/acknowledgement.rs @@ -11,9 +11,6 @@ use crate::ics04_channel::packet::Packet; use crate::ics23_commitment::commitment::CommitmentProof; use crate::{proofs::Proofs, tx_msg::Msg, Height}; -/// Message type for the `MsgAcknowledgement` message. -const TYPE_MSG_ACKNOWLEDGEMENT: &str = "ics04/opaque"; - /// /// Message definition for packet acknowledgements. /// @@ -56,10 +53,6 @@ impl Msg for MsgAcknowledgement { crate::keys::ROUTER_KEY.to_string() } - fn get_type(&self) -> String { - TYPE_MSG_ACKNOWLEDGEMENT.to_string() - } - fn validate_basic(&self) -> Result<(), Self::ValidationError> { // Nothing to validate // All the validation is performed on creation diff --git a/modules/src/ics04_channel/msgs/chan_close_confirm.rs b/modules/src/ics04_channel/msgs/chan_close_confirm.rs index 252bc42286..caede9a93a 100644 --- a/modules/src/ics04_channel/msgs/chan_close_confirm.rs +++ b/modules/src/ics04_channel/msgs/chan_close_confirm.rs @@ -10,9 +10,6 @@ use tendermint_proto::Protobuf; use std::convert::{TryFrom, TryInto}; -/// Message type for the `MsgChannelCloseConfirm` message. -const TYPE_MSG_CHANNEL_CLOSE_CONFIRM: &str = "channel_close_confirm"; - /// /// Message definition for the second step in the channel close handshake (the `ChanCloseConfirm` /// datagram). @@ -56,10 +53,6 @@ impl Msg for MsgChannelCloseConfirm { crate::keys::ROUTER_KEY.to_string() } - fn get_type(&self) -> String { - TYPE_MSG_CHANNEL_CLOSE_CONFIRM.to_string() - } - fn validate_basic(&self) -> Result<(), Self::ValidationError> { // Nothing to validate // All the validation is performed on creation diff --git a/modules/src/ics04_channel/msgs/chan_close_init.rs b/modules/src/ics04_channel/msgs/chan_close_init.rs index 7e5d70585c..7aea9fd749 100644 --- a/modules/src/ics04_channel/msgs/chan_close_init.rs +++ b/modules/src/ics04_channel/msgs/chan_close_init.rs @@ -9,9 +9,6 @@ use tendermint_proto::Protobuf; use std::convert::TryFrom; -/// Message type for the `MsgChannelCloseInit` message. -const TYPE_MSG_CHANNEL_CLOSE_INIT: &str = "channel_close_init"; - /// /// Message definition for the first step in the channel close handshake (`ChanCloseInit` datagram). /// @@ -49,10 +46,6 @@ impl Msg for MsgChannelCloseInit { crate::keys::ROUTER_KEY.to_string() } - fn get_type(&self) -> String { - TYPE_MSG_CHANNEL_CLOSE_INIT.to_string() - } - fn validate_basic(&self) -> Result<(), Self::ValidationError> { // Nothing to validate // All the validation is performed on creation diff --git a/modules/src/ics04_channel/msgs/chan_open_ack.rs b/modules/src/ics04_channel/msgs/chan_open_ack.rs index d255cdb392..c6f29a2b15 100644 --- a/modules/src/ics04_channel/msgs/chan_open_ack.rs +++ b/modules/src/ics04_channel/msgs/chan_open_ack.rs @@ -11,8 +11,7 @@ use tendermint_proto::Protobuf; use std::convert::{TryFrom, TryInto}; -/// Message type for the `MsgChannelOpenAck` message. -const TYPE_MSG_CHANNEL_OPEN_ACK: &str = "channel_open_ack"; +pub const TYPE_URL: &str = "/ibc.core.channel.v1.MsgChannelOpenAck"; /// /// Message definition for the third step in the channel open handshake (`ChanOpenAck` datagram). @@ -62,17 +61,14 @@ impl Msg for MsgChannelOpenAck { crate::keys::ROUTER_KEY.to_string() } - fn get_type(&self) -> String { - TYPE_MSG_CHANNEL_OPEN_ACK.to_string() - } - fn validate_basic(&self) -> Result<(), Self::ValidationError> { // Nothing to validate // All the validation is performed on creation Ok(()) } + fn type_url(&self) -> String { - "/ibc.core.channel.v1.MsgChannelOpenAck".to_string() + TYPE_URL.to_string() } fn get_signers(&self) -> Vec { diff --git a/modules/src/ics04_channel/msgs/chan_open_confirm.rs b/modules/src/ics04_channel/msgs/chan_open_confirm.rs index 370ed84cbb..a202b6176c 100644 --- a/modules/src/ics04_channel/msgs/chan_open_confirm.rs +++ b/modules/src/ics04_channel/msgs/chan_open_confirm.rs @@ -10,8 +10,7 @@ use tendermint_proto::Protobuf; use std::convert::{TryFrom, TryInto}; -/// Message type for the `MsgChannelOpenConfirm` message. -const TYPE_MSG_CHANNEL_OPEN_CONFIRM: &str = "channel_open_confirm"; +pub const TYPE_URL: &str = "/ibc.core.channel.v1.MsgChannelOpenConfirm"; /// /// Message definition for the fourth step in the channel open handshake (`ChanOpenConfirm` @@ -56,10 +55,6 @@ impl Msg for MsgChannelOpenConfirm { crate::keys::ROUTER_KEY.to_string() } - fn get_type(&self) -> String { - TYPE_MSG_CHANNEL_OPEN_CONFIRM.to_string() - } - fn validate_basic(&self) -> Result<(), Self::ValidationError> { // Nothing to validate // All the validation is performed on creation @@ -67,7 +62,7 @@ impl Msg for MsgChannelOpenConfirm { } fn type_url(&self) -> String { - "/ibc.core.channel.v1.MsgChannelOpenConfirm".to_string() + TYPE_URL.to_string() } fn get_signers(&self) -> Vec { diff --git a/modules/src/ics04_channel/msgs/chan_open_init.rs b/modules/src/ics04_channel/msgs/chan_open_init.rs index 993f989f91..8f55172476 100644 --- a/modules/src/ics04_channel/msgs/chan_open_init.rs +++ b/modules/src/ics04_channel/msgs/chan_open_init.rs @@ -10,8 +10,7 @@ use tendermint_proto::Protobuf; use std::convert::{TryFrom, TryInto}; -/// Message type for the `MsgChannelOpenInit` message. -const TYPE_MSG_CHANNEL_OPEN_INIT: &str = "channel_open_init"; +pub const TYPE_URL: &str = "/ibc.core.channel.v1.MsgChannelOpenInit"; /// /// Message definition for the first step in the channel open handshake (`ChanOpenInit` datagram). @@ -31,16 +30,12 @@ impl Msg for MsgChannelOpenInit { crate::keys::ROUTER_KEY.to_string() } - fn get_type(&self) -> String { - TYPE_MSG_CHANNEL_OPEN_INIT.to_string() - } - fn validate_basic(&self) -> Result<(), Self::ValidationError> { self.channel.validate_basic() } fn type_url(&self) -> String { - "/ibc.core.channel.v1.MsgChannelOpenInit".to_string() + TYPE_URL.to_string() } fn get_signers(&self) -> Vec { diff --git a/modules/src/ics04_channel/msgs/chan_open_try.rs b/modules/src/ics04_channel/msgs/chan_open_try.rs index 4f591c0c53..f426cd8e46 100644 --- a/modules/src/ics04_channel/msgs/chan_open_try.rs +++ b/modules/src/ics04_channel/msgs/chan_open_try.rs @@ -11,8 +11,7 @@ use tendermint_proto::Protobuf; use std::convert::{TryFrom, TryInto}; use std::str::FromStr; -/// Message type for the `MsgChannelOpenTry` message. -const TYPE_MSG_CHANNEL_OPEN_TRY: &str = "channel_open_try"; +pub const TYPE_URL: &str = "/ibc.core.channel.v1.MsgChannelOpenTry"; /// /// Message definition for the second step in the channel open handshake (`ChanOpenTry` datagram). @@ -35,16 +34,12 @@ impl Msg for MsgChannelOpenTry { crate::keys::ROUTER_KEY.to_string() } - fn get_type(&self) -> String { - TYPE_MSG_CHANNEL_OPEN_TRY.to_string() - } - fn validate_basic(&self) -> Result<(), Self::ValidationError> { self.channel.validate_basic() } fn type_url(&self) -> String { - "/ibc.core.channel.v1.MsgChannelOpenTry".to_string() + TYPE_URL.to_string() } fn get_signers(&self) -> Vec { diff --git a/modules/src/ics04_channel/msgs/recv_packet.rs b/modules/src/ics04_channel/msgs/recv_packet.rs index 0dd4b28469..47bbc7b13c 100644 --- a/modules/src/ics04_channel/msgs/recv_packet.rs +++ b/modules/src/ics04_channel/msgs/recv_packet.rs @@ -11,9 +11,6 @@ use crate::ics04_channel::packet::Packet; use crate::ics23_commitment::commitment::CommitmentProof; use crate::{proofs::Proofs, tx_msg::Msg, Height}; -/// Message type for `MsgPacket`. -const TYPE_MSG_PACKET: &str = "ics04/opaque"; - /// /// Message definition for the "packet receiving" datagram. /// @@ -55,10 +52,6 @@ impl Msg for MsgRecvPacket { crate::keys::ROUTER_KEY.to_string() } - fn get_type(&self) -> String { - TYPE_MSG_PACKET.to_string() - } - fn validate_basic(&self) -> Result<(), Self::ValidationError> { // Nothing to validate // All the validation is performed on creation diff --git a/modules/src/ics04_channel/msgs/timeout.rs b/modules/src/ics04_channel/msgs/timeout.rs index 521ceb67e7..60743cb68f 100644 --- a/modules/src/ics04_channel/msgs/timeout.rs +++ b/modules/src/ics04_channel/msgs/timeout.rs @@ -11,9 +11,6 @@ use crate::ics04_channel::packet::{Packet, Sequence}; use crate::ics23_commitment::commitment::CommitmentProof; use crate::{proofs::Proofs, tx_msg::Msg, Height}; -/// Message type for the `MsgTimeout` message. -const TYPE_MSG_TIMEOUT: &str = "ics04/timeout"; - /// /// Message definition for packet timeout domain type. /// @@ -52,10 +49,6 @@ impl Msg for MsgTimeout { crate::keys::ROUTER_KEY.to_string() } - fn get_type(&self) -> String { - TYPE_MSG_TIMEOUT.to_string() - } - fn validate_basic(&self) -> Result<(), Self::ValidationError> { // Nothing to validate // All the validation is performed on creation diff --git a/modules/src/ics18_relayer/context.rs b/modules/src/ics18_relayer/context.rs index dfa7751c7e..3cfff8fcf0 100644 --- a/modules/src/ics18_relayer/context.rs +++ b/modules/src/ics18_relayer/context.rs @@ -1,15 +1,16 @@ +use prost_types::Any; +use tendermint::account::Id as AccountId; + use crate::ics02_client::client_def::{AnyClientState, AnyHeader}; use crate::ics18_relayer::error::Error; use crate::ics24_host::identifier::ClientId; -use crate::ics26_routing::msgs::ICS26Envelope; use crate::Height; -use tendermint::account::Id as AccountId; - /// Trait capturing all dependencies (i.e., the context) which algorithms in ICS18 require to /// relay packets between chains. This trait comprises the dependencies towards a single chain. /// Most of the functions in this represent wrappers over the ABCI interface. -/// TODO -- eventually this trait should mirror the `Chain` trait. +/// This trait mimics the `Chain` trait, but at a lower level of abstraction (no networking, header +/// types, light client, RPC client, etc.) pub trait ICS18Context { /// Returns the latest height of the chain. fn query_latest_height(&self) -> Height; @@ -22,8 +23,8 @@ pub trait ICS18Context { fn query_latest_header(&self) -> Option; /// Interface that the relayer uses to submit a datagram to this chain. - /// Wraps around the `/broadcast_tx_async` ABCI endpoint. - fn send(&mut self, msg: ICS26Envelope) -> Result<(), Error>; + /// One can think of this as wrapping around the `/broadcast_tx_commit` ABCI endpoint. + fn send(&mut self, msgs: Vec) -> Result<(), Error>; /// Temporary solution. Similar to `CosmosSDKChain::key_and_signer()` but simpler. fn signer(&self) -> AccountId; diff --git a/modules/src/ics18_relayer/utils.rs b/modules/src/ics18_relayer/utils.rs index c1d918f861..0d616d25f9 100644 --- a/modules/src/ics18_relayer/utils.rs +++ b/modules/src/ics18_relayer/utils.rs @@ -128,8 +128,9 @@ mod tests { ); let client_msg_b = client_msg_b_res.unwrap(); - // - send the message to B - let dispatch_res_b = ctx_b.send(ICS26Envelope::ICS2Msg(client_msg_b)); + // - send the message to B. We bypass ICS18 interface and call directly into + // MockContext `recv` method (to avoid additional serialization steps). + let dispatch_res_b = ctx_b.deliver(ICS26Envelope::ICS2Msg(client_msg_b)); let validation_res = ctx_b.validate(); assert!( validation_res.is_ok(), @@ -173,7 +174,7 @@ mod tests { let client_msg_a = client_msg_a_res.unwrap(); // - send the message to A - let dispatch_res_a = ctx_a.send(ICS26Envelope::ICS2Msg(client_msg_a)); + let dispatch_res_a = ctx_a.deliver(ICS26Envelope::ICS2Msg(client_msg_a)); let validation_res = ctx_a.validate(); assert!( validation_res.is_ok(), diff --git a/modules/src/ics26_routing/context.rs b/modules/src/ics26_routing/context.rs index f920d4794c..05320706b1 100644 --- a/modules/src/ics26_routing/context.rs +++ b/modules/src/ics26_routing/context.rs @@ -2,5 +2,9 @@ use crate::ics02_client::context::{ClientKeeper, ClientReader}; use crate::ics03_connection::context::{ConnectionKeeper, ConnectionReader}; /// This trait captures all the functional dependencies (i.e., context) which the ICS26 module -/// requires to be able to dispatch messages to their corresponding ICS handler. -pub trait ICS26Context: ClientReader + ClientKeeper + ConnectionReader + ConnectionKeeper {} +/// requires to be able to dispatch and process IBC messages. In other words, this is the +/// representation of a chain from the perspective of the IBC module of that chain. +pub trait ICS26Context: + ClientReader + ClientKeeper + ConnectionReader + ConnectionKeeper + Clone +{ +} diff --git a/modules/src/ics26_routing/error.rs b/modules/src/ics26_routing/error.rs index 07b2f27cf6..02109780a3 100644 --- a/modules/src/ics26_routing/error.rs +++ b/modules/src/ics26_routing/error.rs @@ -10,6 +10,12 @@ pub enum Kind { #[error("error raised by the keeper functionality in message handler")] KeeperRaisedError, + + #[error("unknown type URL {0}")] + UnknownMessageTypeURL(String), + + #[error("the message is malformed and cannot be decoded")] + MalformedMessageBytes, } impl Kind { diff --git a/modules/src/ics26_routing/handler.rs b/modules/src/ics26_routing/handler.rs index 34afc07bd5..be6af0fdaf 100644 --- a/modules/src/ics26_routing/handler.rs +++ b/modules/src/ics26_routing/handler.rs @@ -1,21 +1,51 @@ +use prost_types::Any; +use tendermint_proto::Protobuf; + use crate::handler::HandlerOutput; use crate::ics02_client::handler::dispatch as ics2_msg_dispatcher; +use crate::ics02_client::msgs::create_client; +use crate::ics02_client::msgs::update_client; +use crate::ics02_client::msgs::ClientMsg; use crate::ics03_connection::handler::dispatch as ics3_msg_dispatcher; use crate::ics26_routing::context::ICS26Context; use crate::ics26_routing::error::{Error, Kind}; use crate::ics26_routing::msgs::ICS26Envelope; use crate::ics26_routing::msgs::ICS26Envelope::{ICS2Msg, ICS3Msg}; -use ibc_proto::cosmos::tx::v1beta1::Tx; -// TODO: Implement this (the tx type is probably wrong also). Rough sketch: -// 1. deserialize & validate each message in the tx -// 2. invoke dispatch(ctx, ms) -// 3. if all message in the tx pass through correctly, then apply the side-effects to the context -pub fn deliver_tx(_ctx: &mut Ctx, _tx: Tx) -> Result<(), Error> +/// Mimics the DeliverTx ABCI interface, but a slightly lower level. No need for authentication +/// info or signature checks here. +/// https://github.com/cosmos/cosmos-sdk/tree/master/docs/basics +pub fn deliver(ctx: &mut Ctx, messages: Vec) -> Result<(), Error> where Ctx: ICS26Context, { - unimplemented!() + // Create a clone, which will store each intermediary stage of applying txs. + let mut ctx_interim = ctx.clone(); + + for any_msg in messages { + // Decode the proto message into a domain message, creating an ICS26 envelope. + let envelope = match any_msg.type_url.as_str() { + // ICS2 messages + create_client::TYPE_URL => { + // Pop out the message and then wrap it in the corresponding type + let domain_msg = create_client::MsgCreateAnyClient::decode_vec(&*any_msg.value) + .map_err(|e| Kind::MalformedMessageBytes.context(e))?; + Ok(ICS2Msg(ClientMsg::CreateClient(domain_msg))) + } + update_client::TYPE_URL => { + let domain_msg = update_client::MsgUpdateAnyClient::decode_vec(&*any_msg.value) + .map_err(|e| Kind::MalformedMessageBytes.context(e))?; + Ok(ICS2Msg(ClientMsg::UpdateClient(domain_msg))) + } + // TODO: ICS3 messages + _ => Err(Kind::UnknownMessageTypeURL(any_msg.type_url)), + }?; + dispatch(&mut ctx_interim, envelope)?; + } + + // No error has surfaced, so we now apply the changes permanently to the original context. + *ctx = ctx_interim; + Ok(()) } /// Top-level ICS dispatch function. Routes incoming IBC messages to their corresponding module. diff --git a/modules/src/lib.rs b/modules/src/lib.rs index f3f6e5acc2..9d219cabe3 100644 --- a/modules/src/lib.rs +++ b/modules/src/lib.rs @@ -45,7 +45,7 @@ pub type Height = crate::ics02_client::height::Height; mod test; #[cfg(any(test, feature = "mocks"))] -mod test_utils; +pub mod test_utils; #[cfg(any(test, feature = "mocks"))] -mod mock; // Context mock, the underlying host chain, and client types: for testing all handlers. +pub mod mock; // Context mock, the underlying host chain, and client types: for testing all handlers. diff --git a/modules/src/mock/context.rs b/modules/src/mock/context.rs index 87391b371c..c760f3e928 100644 --- a/modules/src/mock/context.rs +++ b/modules/src/mock/context.rs @@ -1,7 +1,12 @@ //! Implementation of a global context mock. Used in testing handlers of all IBC modules. -// TODO: remove this clippy exception (some code is not covered in `mocks` feature). -#![allow(dead_code)] +use std::cmp::min; +use std::collections::HashMap; +use std::error::Error; +use std::str::FromStr; + +use prost_types::Any; +use tendermint::account::Id; use crate::ics02_client::client_def::{AnyClientState, AnyConsensusState, AnyHeader}; use crate::ics02_client::client_type::ClientType; @@ -16,19 +21,13 @@ use crate::ics18_relayer::error::{Error as ICS18Error, Kind as ICS18ErrorKind}; use crate::ics23_commitment::commitment::CommitmentPrefix; use crate::ics24_host::identifier::{ChainId, ClientId, ConnectionId}; use crate::ics26_routing::context::ICS26Context; -use crate::ics26_routing::handler::dispatch; +use crate::ics26_routing::handler::{deliver, dispatch}; use crate::ics26_routing::msgs::ICS26Envelope; use crate::mock::client_state::{MockClientRecord, MockClientState, MockConsensusState}; use crate::mock::header::MockHeader; use crate::mock::host::{HostBlock, HostType}; use crate::Height; -use std::cmp::min; -use std::collections::HashMap; -use std::error::Error; -use std::str::FromStr; -use tendermint::account::Id; - /// A context implementing the dependencies necessary for testing any IBC module. #[derive(Clone, Debug)] pub struct MockContext { @@ -225,8 +224,9 @@ impl MockContext { } /// A datagram passes from the relayer to the IBC module (on host chain). + /// Alternative method to `ICS18Context::send` that does not exercise any serialization. /// Used in testing the ICS18 algorithms, hence this may return a ICS18Error. - fn recv(&mut self, msg: ICS26Envelope) -> Result<(), ICS18Error> { + pub fn deliver(&mut self, msg: ICS26Envelope) -> Result<(), ICS18Error> { dispatch(self, msg).map_err(|e| ICS18ErrorKind::TransactionFailed.context(e))?; // Create a new block. self.advance_host_chain_height(); @@ -415,8 +415,10 @@ impl ICS18Context for MockContext { block_ref.cloned().map(Into::into) } - fn send(&mut self, msg: ICS26Envelope) -> Result<(), ICS18Error> { - self.recv(msg) + fn send(&mut self, msgs: Vec) -> Result<(), ICS18Error> { + deliver(self, msgs).map_err(|e| ICS18ErrorKind::TransactionFailed.context(e))?; // Forward call to ICS26 delivery method + self.advance_host_chain_height(); // Advance chain height + Ok(()) } fn signer(&self) -> Id { diff --git a/modules/src/tx_msg.rs b/modules/src/tx_msg.rs index 192f7cd1a5..4a70dd7568 100644 --- a/modules/src/tx_msg.rs +++ b/modules/src/tx_msg.rs @@ -7,8 +7,6 @@ pub trait Msg: Clone { // TODO -- clarify what is this function supposed to do & its connection to ICS26 routing mod. fn route(&self) -> String; - fn get_type(&self) -> String; - fn validate_basic(&self) -> Result<(), Self::ValidationError>; fn get_sign_bytes + prost::Message>(&self) -> Vec { @@ -18,6 +16,7 @@ pub trait Msg: Clone { buf } + /// Unique type identifier for this message, to support encoding to/from `prost_types::Any`. fn type_url(&self) -> String { unimplemented!() } diff --git a/relayer-cli/src/commands/query/channel.rs b/relayer-cli/src/commands/query/channel.rs index 1fb18eaa8e..bf02e5d16b 100644 --- a/relayer-cli/src/commands/query/channel.rs +++ b/relayer-cli/src/commands/query/channel.rs @@ -100,7 +100,7 @@ impl Runnable for QueryChannelEndCmd { let rt = Arc::new(Mutex::new(TokioRuntime::new().unwrap())); - let chain = CosmosSDKChain::from_config(chain_config, rt).unwrap(); + let chain = CosmosSDKChain::bootstrap(chain_config, rt).unwrap(); let height = ibc::Height::new(chain.id().version(), opts.height); let res: Result = chain .query( diff --git a/relayer-cli/src/commands/query/client.rs b/relayer-cli/src/commands/query/client.rs index aa960744b6..0eaff5c1f9 100644 --- a/relayer-cli/src/commands/query/client.rs +++ b/relayer-cli/src/commands/query/client.rs @@ -79,7 +79,7 @@ impl Runnable for QueryClientStateCmd { status_info!("Options", "{:?}", opts); let rt = Arc::new(Mutex::new(TokioRuntime::new().unwrap())); - let chain = CosmosSDKChain::from_config(chain_config, rt).unwrap(); + let chain = CosmosSDKChain::bootstrap(chain_config, rt).unwrap(); let height = ibc::Height::new(chain.id().version(), opts.height); let res: Result = chain @@ -172,7 +172,7 @@ impl Runnable for QueryClientConsensusCmd { status_info!("Options", "{:?}", opts); let rt = Arc::new(Mutex::new(TokioRuntime::new().unwrap())); - let chain = CosmosSDKChain::from_config(chain_config, rt).unwrap(); + let chain = CosmosSDKChain::bootstrap(chain_config, rt).unwrap(); let height = ibc::Height::new(chain.id().version(), opts.height); let res: Result = chain @@ -286,7 +286,7 @@ impl Runnable for QueryClientConnectionsCmd { status_info!("Options", "{:?}", opts); let rt = Arc::new(Mutex::new(TokioRuntime::new().unwrap())); - let chain = CosmosSDKChain::from_config(chain_config, rt).unwrap(); + let chain = CosmosSDKChain::bootstrap(chain_config, rt).unwrap(); let height = ibc::Height::new(chain.id().version(), opts.height); let res: Result = chain diff --git a/relayer-cli/src/commands/query/connection.rs b/relayer-cli/src/commands/query/connection.rs index bd65c844ee..db330286bd 100644 --- a/relayer-cli/src/commands/query/connection.rs +++ b/relayer-cli/src/commands/query/connection.rs @@ -83,7 +83,7 @@ impl Runnable for QueryConnectionEndCmd { status_info!("Options", "{:?}", opts); let rt = Arc::new(Mutex::new(TokioRuntime::new().unwrap())); - let chain = CosmosSDKChain::from_config(chain_config, rt).unwrap(); + let chain = CosmosSDKChain::bootstrap(chain_config, rt).unwrap(); let height = ibc::Height::new(chain.id().version(), opts.height); // TODO - any value in querying with proof from the CLI? diff --git a/relayer-cli/src/commands/tx/channel.rs b/relayer-cli/src/commands/tx/channel.rs index 4b4d3844af..eed31d1440 100644 --- a/relayer-cli/src/commands/tx/channel.rs +++ b/relayer-cli/src/commands/tx/channel.rs @@ -11,6 +11,7 @@ use relayer::channel::{ }; use relayer::chain::runtime::ChainRuntime; +use relayer::chain::CosmosSDKChain; use relayer::channel::{ChannelConfig, ChannelConfigSide}; macro_rules! chan_open_cmd { @@ -86,8 +87,10 @@ macro_rules! chan_open_cmd { status_info!("Message ", "{}: {:#?}", $dbg_string, opts); - let (src_chain, _) = ChainRuntime::spawn(src_chain_config.clone()).unwrap(); - let (dst_chain, _) = ChainRuntime::spawn(dst_chain_config.clone()).unwrap(); + let (src_chain, _) = + ChainRuntime::::spawn(src_chain_config.clone()).unwrap(); + let (dst_chain, _) = + ChainRuntime::::spawn(dst_chain_config.clone()).unwrap(); let res: Result = $func(dst_chain, src_chain, &opts).map_err(|e| Kind::Tx.context(e).into()); diff --git a/relayer-cli/src/commands/tx/client.rs b/relayer-cli/src/commands/tx/client.rs index 872d7950ca..4e838fea2e 100644 --- a/relayer-cli/src/commands/tx/client.rs +++ b/relayer-cli/src/commands/tx/client.rs @@ -6,6 +6,7 @@ use crate::application::app_config; use crate::error::{Error, Kind}; use crate::prelude::*; use relayer::chain::runtime::ChainRuntime; +use relayer::chain::CosmosSDKChain; use relayer::config::ChainConfig; use relayer::foreign_client::{ build_create_client_and_send, build_update_client_and_send, ForeignClientConfig, @@ -48,8 +49,8 @@ impl Runnable for TxCreateClientCmd { opts.chain_id() ); - let (src_chain, _) = ChainRuntime::spawn(src_chain_config).unwrap(); - let (dst_chain, _) = ChainRuntime::spawn(dst_chain_config).unwrap(); + let (src_chain, _) = ChainRuntime::::spawn(src_chain_config).unwrap(); + let (dst_chain, _) = ChainRuntime::::spawn(dst_chain_config).unwrap(); let res: Result = build_create_client_and_send(dst_chain, src_chain, &opts) .map_err(|e| Kind::Tx.context(e).into()); @@ -97,8 +98,8 @@ impl Runnable for TxUpdateClientCmd { opts.chain_id() ); - let (src_chain, _) = ChainRuntime::spawn(src_chain_config).unwrap(); - let (dst_chain, _) = ChainRuntime::spawn(dst_chain_config).unwrap(); + let (src_chain, _) = ChainRuntime::::spawn(src_chain_config).unwrap(); + let (dst_chain, _) = ChainRuntime::::spawn(dst_chain_config).unwrap(); let res: Result = build_update_client_and_send(dst_chain, src_chain, &opts) .map_err(|e| Kind::Tx.context(e).into()); diff --git a/relayer-cli/src/commands/tx/connection.rs b/relayer-cli/src/commands/tx/connection.rs index 42c9b24d87..ce9043380f 100644 --- a/relayer-cli/src/commands/tx/connection.rs +++ b/relayer-cli/src/commands/tx/connection.rs @@ -11,6 +11,7 @@ use relayer::connection::{ use crate::error::{Error, Kind}; use relayer::chain::runtime::ChainRuntime; +use relayer::chain::CosmosSDKChain; use relayer::connection::{ConnectionConfig, ConnectionSideConfig}; macro_rules! conn_open_cmd { @@ -75,8 +76,10 @@ macro_rules! conn_open_cmd { status_info!("Message ", "{}: {:#?}", $dbg_string, opts); - let (src_chain, _) = ChainRuntime::spawn(src_chain_config.clone()).unwrap(); - let (dst_chain, _) = ChainRuntime::spawn(dst_chain_config.clone()).unwrap(); + let (src_chain, _) = + ChainRuntime::::spawn(src_chain_config.clone()).unwrap(); + let (dst_chain, _) = + ChainRuntime::::spawn(dst_chain_config.clone()).unwrap(); let res: Result = $func(dst_chain, src_chain, &opts).map_err(|e| Kind::Tx.context(e).into()); diff --git a/relayer-cli/src/commands/v0.rs b/relayer-cli/src/commands/v0.rs index 728aa9a14e..451020b136 100644 --- a/relayer-cli/src/commands/v0.rs +++ b/relayer-cli/src/commands/v0.rs @@ -11,6 +11,7 @@ use relayer::relay::channel_relay; use crate::config::Config; use crate::prelude::*; +use relayer::chain::CosmosSDKChain; #[derive(Command, Debug, Options)] pub struct V0Cmd {} @@ -57,8 +58,8 @@ pub fn v0_task(config: &Config) -> Result<(), BoxError> { .find(|c| c.id == connection.dst().chain_id().clone()) .ok_or("Configuration for source chain not found")?; - let (src_chain_handle, _) = ChainRuntime::spawn(src_chain_config)?; - let (dst_chain_handle, _) = ChainRuntime::spawn(dst_chain_config)?; + let (src_chain_handle, _) = ChainRuntime::::spawn(src_chain_config)?; + let (dst_chain_handle, _) = ChainRuntime::::spawn(dst_chain_config)?; Ok(channel_relay( src_chain_handle, diff --git a/relayer-cli/tests/integration.rs b/relayer-cli/tests/integration.rs index 9c35ee4b82..f08c6a50e0 100644 --- a/relayer-cli/tests/integration.rs +++ b/relayer-cli/tests/integration.rs @@ -46,7 +46,7 @@ fn simd_config() -> Config { /// Chain created for the informaldev/simd DockerHub image running on localhost. fn simd_chain() -> CosmosSDKChain { let rt = Arc::new(Mutex::new(tokio::runtime::Runtime::new().unwrap())); - CosmosSDKChain::from_config(simd_config().chains[0].clone(), rt).unwrap() + CosmosSDKChain::bootstrap(simd_config().chains[0].clone(), rt).unwrap() } /// Query connection ID. Requires the informaldev/simd Docker image running on localhost. diff --git a/relayer/Cargo.toml b/relayer/Cargo.toml index 60f0030985..6781a05bb5 100644 --- a/relayer/Cargo.toml +++ b/relayer/Cargo.toml @@ -54,3 +54,5 @@ version = "=0.17.0-rc3" [dev-dependencies] serial_test = "0.5.0" +ibc = { path = "../modules", features = [ "mocks" ] } +tendermint-testgen = { version = "0.17.0-rc3" } \ No newline at end of file diff --git a/relayer/src/chain.rs b/relayer/src/chain.rs index 27116b9302..c204b7f51c 100644 --- a/relayer/src/chain.rs +++ b/relayer/src/chain.rs @@ -4,6 +4,16 @@ pub use cosmos::CosmosSDKChain; pub mod handle; pub mod runtime; +#[cfg(test)] +pub mod mock; + +use crossbeam_channel as channel; +use std::{ + sync::{Arc, Mutex}, + thread, +}; +use tokio::runtime::Runtime as TokioRuntime; + use prost_types::Any; use tendermint_proto::Protobuf; @@ -12,8 +22,6 @@ use tendermint_proto::Protobuf; use tendermint::account::Id as AccountId; use tendermint::block::Height; -use tendermint_rpc::Client as RpcClient; - use ibc::ics02_client::header::Header; use ibc::ics02_client::state::{ClientState, ConsensusState}; @@ -33,7 +41,9 @@ use ibc::Height as ICSHeight; use crate::config::ChainConfig; use crate::connection::ConnectionMsgType; use crate::error::{Error, Kind}; +use crate::event::monitor::EventBatch; use crate::keyring::store::{KeyEntry, KeyRing}; +use crate::light_client::LightClient; /// Generic query response type /// TODO - will slowly move to GRPC protobuf specs for queries @@ -45,7 +55,7 @@ pub struct QueryResponse { } /// Defines a blockchain as understood by the relayer -pub trait Chain { +pub trait Chain: Sized { /// Type of light blocks for this chain type LightBlock: Send + Sync; @@ -58,29 +68,38 @@ pub trait Chain { /// Type of the client state for this chain type ClientState: ClientState; - /// Type of RPC requester (wrapper around low-level RPC client) for this chain - type RpcClient: RpcClient + Send + Sync; + /// Constructs the chain + fn bootstrap(config: ChainConfig, rt: Arc>) -> Result; - /// Returns the chain's identifier - fn id(&self) -> &ChainId { - &self.config().id - } + #[allow(clippy::type_complexity)] + /// Initializes and returns the light client (if any) associated with this chain. + fn init_light_client( + &self, + ) -> Result<(Box>, Option>), Error>; - /// Returns the chain's configuration - fn config(&self) -> &ChainConfig; + /// Initializes and returns the event monitor (if any) associated with this chain. + fn init_event_monitor( + &self, + rt: Arc>, + ) -> Result< + ( + channel::Receiver, + Option>, + ), + Error, + >; + + /// Returns the chain's identifier + fn id(&self) -> &ChainId; /// Returns the chain's keybase fn keybase(&self) -> &KeyRing; - /// Get a low-level RPC client for this chain - /// TODO - Should this be part of the Chain trait? - fn rpc_client(&self) -> &Self::RpcClient; - /// Perform a generic ICS `query`, and return the corresponding response data. fn query(&self, data: Path, height: ICSHeight, prove: bool) -> Result; /// Send a transaction with `msgs` to chain. - fn send_tx(&self, proto_msgs: Vec) -> Result; + fn send_tx(&mut self, proto_msgs: Vec) -> Result; fn get_signer(&mut self) -> Result; @@ -111,12 +130,7 @@ pub trait Chain { height: ICSHeight, ) -> Result; - fn query_commitment_prefix(&self) -> Result { - // TODO - do a real chain query - Ok(CommitmentPrefix::from( - self.config().store_prefix.as_bytes().to_vec(), - )) - } + fn query_commitment_prefix(&self) -> Result; fn query_compatible_versions(&self) -> Result, Error> { // TODO - do a real chain query diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index d80dafef52..93abd04cd2 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -1,12 +1,16 @@ -use std::convert::TryFrom; -use std::future::Future; -use std::str::FromStr; -use std::sync::{Arc, Mutex}; -use std::time::Duration; +use std::{ + convert::TryFrom, + future::Future, + str::FromStr, + sync::{Arc, Mutex}, + thread, + time::Duration, +}; use anomaly::fail; use bitcoin::hashes::hex::ToHex; +use crossbeam_channel as channel; use prost::Message; use prost_types::Any; use tokio::runtime::Runtime as TokioRuntime; @@ -35,6 +39,7 @@ use ibc::ics07_tendermint::consensus_state::ConsensusState as TMConsensusState; use ibc::ics07_tendermint::consensus_state::ConsensusState; use ibc::ics07_tendermint::header::Header as TMHeader; +use ibc::ics23_commitment::commitment::CommitmentPrefix; use ibc::ics23_commitment::merkle::MerkleProof; use ibc::ics24_host::identifier::{ChainId, ClientId}; use ibc::ics24_host::Path::ClientConsensusState as ClientConsensusPath; @@ -48,7 +53,10 @@ use super::Chain; use crate::chain::QueryResponse; use crate::config::ChainConfig; use crate::error::{Error, Kind}; +use crate::event::monitor::{EventBatch, EventMonitor}; use crate::keyring::store::{KeyEntry, KeyRing, KeyRingOperations, StoreBackend}; +use crate::light_client::tendermint::LightClient as TMLightClient; +use crate::light_client::LightClient; // Support for GRPC use ibc_proto::cosmos::auth::v1beta1::{BaseAccount, QueryAccountRequest}; @@ -62,26 +70,6 @@ pub struct CosmosSDKChain { } impl CosmosSDKChain { - pub fn from_config(config: ChainConfig, rt: Arc>) -> Result { - let primary = config - .primary() - .ok_or_else(|| Kind::LightClient.context("no primary peer specified"))?; - - let rpc_client = - HttpClient::new(primary.address.clone()).map_err(|e| Kind::Rpc.context(e))?; - - // Initialize key store and load key - let key_store = KeyRing::init(StoreBackend::Test, config.clone()) - .map_err(|e| Kind::KeyBase.context(e))?; - - Ok(Self { - rt, - config, - keybase: key_store, - rpc_client, - }) - } - /// The unbonding period of this chain pub fn unbonding_period(&self) -> Result { // TODO - generalize this @@ -111,6 +99,14 @@ impl CosmosSDKChain { Ok(Duration::from_secs(res.seconds as u64)) } + fn rpc_client(&self) -> &HttpClient { + &self.rpc_client + } + + pub fn config(&self) -> &ChainConfig { + &self.config + } + /// Query the consensus parameters via an RPC query /// Specific to the SDK and used only for Tendermint client create pub fn query_consensus_params(&self) -> Result { @@ -127,18 +123,64 @@ impl CosmosSDKChain { } impl Chain for CosmosSDKChain { - type Header = TMHeader; type LightBlock = TMLightBlock; - type RpcClient = HttpClient; + type Header = TMHeader; type ConsensusState = ConsensusState; type ClientState = ClientState; - fn config(&self) -> &ChainConfig { - &self.config + fn bootstrap(config: ChainConfig, rt: Arc>) -> Result { + let rpc_client = + HttpClient::new(config.rpc_addr.clone()).map_err(|e| Kind::Rpc.context(e))?; + + // Initialize key store and load key + let key_store = KeyRing::init(StoreBackend::Test, config.clone()) + .map_err(|e| Kind::KeyBase.context(e))?; + + Ok(Self { + rt, + config, + keybase: key_store, + rpc_client, + }) } - fn rpc_client(&self) -> &HttpClient { - &self.rpc_client + // TODO use a simpler approach to create the light client + #[allow(clippy::type_complexity)] + fn init_light_client( + &self, + ) -> Result<(Box>, Option>), Error> { + let (lc, supervisor) = TMLightClient::from_config(&self.config, true)?; + + let supervisor_thread = thread::spawn(move || supervisor.run().unwrap()); + + Ok((Box::new(lc), Some(supervisor_thread))) + } + + fn init_event_monitor( + &self, + rt: Arc>, + ) -> Result< + ( + channel::Receiver, + Option>, + ), + Error, + > { + let (mut event_monitor, event_receiver) = + EventMonitor::new(self.config.id.clone(), self.config.rpc_addr.clone(), rt)?; + + event_monitor.subscribe().unwrap(); + let monitor_thread = thread::spawn(move || event_monitor.run()); + + Ok((event_receiver, Some(monitor_thread))) + } + + fn id(&self) -> &ChainId { + &self.config().id + } + + fn keybase(&self) -> &KeyRing { + &self.keybase } fn query(&self, data: Path, height: ICSHeight, prove: bool) -> Result { @@ -164,7 +206,7 @@ impl Chain for CosmosSDKChain { /// Send a transaction that includes the specified messages /// TODO - split the messages in multiple Tx-es such that they don't exceed some max size - fn send_tx(&self, proto_msgs: Vec) -> Result { + fn send_tx(&mut self, proto_msgs: Vec) -> Result { let key = self .keybase() .get_key() @@ -256,6 +298,74 @@ impl Chain for CosmosSDKChain { Ok(response) } + /// Get the account for the signer + fn get_signer(&mut self) -> Result { + // Get the key from key seed file + let key = self + .keybase() + .get_key() + .map_err(|e| Kind::KeyBase.context(e))?; + + let signer: AccountId = + AccountId::from_str(&key.address.to_hex()).map_err(|e| Kind::KeyBase.context(e))?; + + Ok(signer) + } + + /// Get the signing key + fn get_key(&mut self) -> Result { + // Get the key from key seed file + let key = self + .keybase() + .get_key() + .map_err(|e| Kind::KeyBase.context(e))?; + + Ok(key) + } + + fn build_client_state(&self, height: ICSHeight) -> Result { + // Build the client state. + let client_state = ibc::ics07_tendermint::client_state::ClientState::new( + self.id().to_string(), + self.config.trust_threshold, + self.config.trusting_period, + self.unbonding_period()?, + Duration::from_millis(3000), // TODO - get it from src config when avail + height, + ICSHeight::zero(), + self.query_consensus_params()?, + "upgrade/upgradedClient".to_string(), + false, + false, + ) + .map_err(|e| Kind::BuildClientStateFailure.context(e))?; + + Ok(client_state) + } + + fn build_consensus_state( + &self, + light_block: Self::LightBlock, + ) -> Result { + Ok(TMConsensusState::from(light_block.signed_header.header)) + } + + fn build_header( + &self, + trusted_light_block: Self::LightBlock, + target_light_block: Self::LightBlock, + ) -> Result { + let trusted_height = + ICSHeight::new(self.id().version(), trusted_light_block.height().into()); + + Ok(TMHeader { + trusted_height, + signed_header: target_light_block.signed_header.clone(), + validator_set: target_light_block.validators, + trusted_validator_set: trusted_light_block.validators, + }) + } + /// Query the latest height the chain is at via a RPC query fn query_latest_height(&self) -> Result { let status = self @@ -293,6 +403,13 @@ impl Chain for CosmosSDKChain { Ok(client_state) } + fn query_commitment_prefix(&self) -> Result { + // TODO - do a real chain query + Ok(CommitmentPrefix::from( + self.config().store_prefix.as_bytes().to_vec(), + )) + } + fn proven_client_state( &self, client_id: &ClientId, @@ -337,78 +454,6 @@ impl Chain for CosmosSDKChain { Ok((consensus_state, res.proof)) } - - fn build_client_state(&self, height: ICSHeight) -> Result { - // Build the client state. - let client_state = ibc::ics07_tendermint::client_state::ClientState::new( - self.id().to_string(), - self.config.trust_threshold, - self.config.trusting_period, - self.unbonding_period()?, - Duration::from_millis(3000), // TODO - get it from src config when avail - height, - ICSHeight::zero(), - self.query_consensus_params()?, - "upgrade/upgradedClient".to_string(), - false, - false, - ) - .map_err(|e| Kind::BuildClientStateFailure.context(e))?; - - Ok(client_state) - } - - fn build_consensus_state( - &self, - light_block: Self::LightBlock, - ) -> Result { - Ok(TMConsensusState::from(light_block.signed_header.header)) - } - - fn build_header( - &self, - trusted_light_block: Self::LightBlock, - target_light_block: Self::LightBlock, - ) -> Result { - let trusted_height = - ICSHeight::new(self.id().version(), trusted_light_block.height().into()); - - Ok(TMHeader { - trusted_height, - signed_header: target_light_block.signed_header.clone(), - validator_set: target_light_block.validators, - trusted_validator_set: trusted_light_block.validators, - }) - } - - fn keybase(&self) -> &KeyRing { - &self.keybase - } - - /// Get the account for the signer - fn get_signer(&mut self) -> Result { - // Get the key from key seed file - let key = self - .keybase() - .get_key() - .map_err(|e| Kind::KeyBase.context(e))?; - - let signer: AccountId = - AccountId::from_str(&key.address.to_hex()).map_err(|e| Kind::KeyBase.context(e))?; - - Ok(signer) - } - - /// Get the signing key - fn get_key(&mut self) -> Result { - // Get the key from key seed file - let key = self - .keybase() - .get_key() - .map_err(|e| Kind::KeyBase.context(e))?; - - Ok(key) - } } /// Perform a generic `abci_query`, and return the corresponding deserialized response data. diff --git a/relayer/src/chain/mock.rs b/relayer/src/chain/mock.rs new file mode 100644 index 0000000000..b665675cda --- /dev/null +++ b/relayer/src/chain/mock.rs @@ -0,0 +1,214 @@ +use std::ops::Add; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use crossbeam_channel as channel; +use prost_types::Any; +use tendermint::account::Id; +use tendermint_testgen::light_block::TMLightBlock; +use tokio::runtime::Runtime; + +use ibc::downcast; +use ibc::ics02_client::client_def::AnyClientState; +use ibc::ics07_tendermint::client_state::ClientState as TendermintClientState; +use ibc::ics07_tendermint::consensus_state::ConsensusState as TendermintConsensusState; +use ibc::ics07_tendermint::header::Header as TendermintHeader; +use ibc::ics18_relayer::context::ICS18Context; +use ibc::ics23_commitment::commitment::CommitmentPrefix; +use ibc::ics23_commitment::merkle::MerkleProof; +use ibc::ics24_host::identifier::{ChainId, ClientId}; +use ibc::ics24_host::Path; +use ibc::mock::context::MockContext; +use ibc::mock::host::HostType; +use ibc::test_utils::{default_consensus_params, get_dummy_account_id}; +use ibc::Height; + +use crate::chain::{Chain, QueryResponse}; +use crate::config::ChainConfig; +use crate::error::{Error, Kind}; +use crate::event::monitor::EventBatch; +use crate::keyring::store::{KeyEntry, KeyRing}; +use crate::light_client::{mock::LightClient as MockLightClient, LightClient}; +use std::thread; + +/// The representation of a mocked chain as the relayer sees it. +/// The relayer runtime and the light client will engage with the MockChain to query/send tx; the +/// primary interface for doing so is captured by `ICS18Context` which this struct can access via +/// the `context` field. +pub struct MockChain { + config: ChainConfig, + context: MockContext, +} + +impl Chain for MockChain { + type LightBlock = TMLightBlock; + type Header = TendermintHeader; + type ConsensusState = TendermintConsensusState; + type ClientState = TendermintClientState; + + fn bootstrap(config: ChainConfig, _rt: Arc>) -> Result { + Ok(MockChain { + config: config.clone(), + context: MockContext::new( + config.id.clone(), + HostType::SyntheticTendermint, + 50, + Height::new(config.id.version(), 20), + ), + }) + } + + #[allow(clippy::type_complexity)] + fn init_light_client( + &self, + ) -> Result<(Box>, Option>), Error> { + let light_client = MockLightClient::new(self); + + Ok((Box::new(light_client), None)) + } + + fn init_event_monitor( + &self, + _rt: Arc>, + ) -> Result< + ( + channel::Receiver, + Option>, + ), + Error, + > { + let (_, rx) = channel::unbounded(); + Ok((rx, None)) + } + + fn id(&self) -> &ChainId { + &self.config.id + } + + fn keybase(&self) -> &KeyRing { + unimplemented!() + } + + fn query(&self, _data: Path, _height: Height, _prove: bool) -> Result { + unimplemented!() + } + + fn send_tx(&mut self, proto_msgs: Vec) -> Result { + // Use the ICS18Context interface to submit the set of messages. + self.context + .send(proto_msgs) + .map(|_| "OK".to_string()) // TODO: establish success return codes. + .map_err(|e| Kind::Rpc.context(e).into()) + } + + fn get_signer(&mut self) -> Result { + Ok(get_dummy_account_id()) + } + + fn get_key(&mut self) -> Result { + unimplemented!() + } + + fn build_client_state(&self, height: Height) -> Result { + let client_state = Self::ClientState::new( + self.id().to_string(), + self.config.trust_threshold, + self.config.trusting_period, + self.config.trusting_period.add(Duration::from_secs(1000)), + Duration::from_millis(3000), + height, + Height::zero(), + default_consensus_params(), + "upgrade/upgradedClient".to_string(), + false, + false, + ) + .map_err(|e| Kind::BuildClientStateFailure.context(e))?; + + Ok(client_state) + } + + fn build_consensus_state( + &self, + light_block: Self::LightBlock, + ) -> Result { + Ok(Self::ConsensusState::from(light_block.signed_header.header)) + } + + fn build_header( + &self, + _trusted_light_block: Self::LightBlock, + _target_light_block: Self::LightBlock, + ) -> Result { + unimplemented!() + } + + fn query_latest_height(&self) -> Result { + Ok(self.context.query_latest_height()) + } + + fn query_client_state( + &self, + client_id: &ClientId, + _height: Height, + ) -> Result { + // TODO: unclear what are the scenarios where we need to take height into account. + let any_state = self + .context + .query_client_full_state(client_id) + .ok_or(Kind::EmptyResponseValue)?; + let client_state = downcast!(any_state => AnyClientState::Tendermint) + .ok_or_else(|| Kind::Query.context("unexpected client state type"))?; + Ok(client_state) + } + + fn query_commitment_prefix(&self) -> Result { + unimplemented!() + } + + fn proven_client_state( + &self, + _client_id: &ClientId, + _height: Height, + ) -> Result<(Self::ClientState, MerkleProof), Error> { + unimplemented!() + } + + fn proven_client_consensus( + &self, + _client_id: &ClientId, + _consensus_height: Height, + _height: Height, + ) -> Result<(Self::ConsensusState, MerkleProof), Error> { + unimplemented!() + } +} + +// For integration tests with the modules +#[cfg(test)] +pub mod test_utils { + use std::str::FromStr; + use std::time::Duration; + + use ibc::ics24_host::identifier::ChainId; + + use crate::config::ChainConfig; + + /// Returns a very minimal chain configuration, to be used in initializing `MockChain`s. + pub fn get_basic_chain_config(id: &str) -> ChainConfig { + ChainConfig { + id: ChainId::from_str(id).unwrap(), + rpc_addr: "35.192.61.41:26656".parse().unwrap(), + grpc_addr: "".to_string(), + account_prefix: "".to_string(), + key_name: "".to_string(), + store_prefix: "".to_string(), + client_ids: vec![], + gas: 0, + clock_drift: Duration::from_secs(5), + trusting_period: Duration::from_secs(14 * 24 * 60 * 60), // 14 days + trust_threshold: Default::default(), + peers: None, + } + } +} diff --git a/relayer/src/chain/runtime.rs b/relayer/src/chain/runtime.rs index 0be8a925d4..b426d499ac 100644 --- a/relayer/src/chain/runtime.rs +++ b/relayer/src/chain/runtime.rs @@ -27,29 +27,24 @@ use ibc::{ // FIXME: the handle should not depend on tendermint-specific types use tendermint::account::Id as AccountId; -// use crate::foreign_client::ForeignClient; - use crate::{ config::ChainConfig, connection::ConnectionMsgType, error::{Error, Kind}, - event::{ - bus::EventBus, - monitor::{EventBatch, EventMonitor}, - }, + event::{bus::EventBus, monitor::EventBatch}, keyring::store::KeyEntry, - light_client::{tendermint::LightClient as TMLightClient, LightClient}, + light_client::LightClient, }; use super::{ handle::{ChainHandle, HandleInput, ProdChainHandle, ReplyTo, Subscription}, - Chain, CosmosSDKChain, QueryResponse, + Chain, QueryResponse, }; pub struct Threads { - pub light_client: thread::JoinHandle<()>, + pub light_client: Option>, pub chain_runtime: thread::JoinHandle<()>, - pub event_monitor: thread::JoinHandle<()>, + pub event_monitor: Option>, } pub struct ChainRuntime { @@ -61,49 +56,27 @@ pub struct ChainRuntime { light_client: Box>, #[allow(dead_code)] - rt: Arc>, + rt: Arc>, // Making this future-proof, so we keep the runtime around. } -impl ChainRuntime { - pub fn cosmos_sdk( - config: ChainConfig, - light_client: TMLightClient, - event_receiver: channel::Receiver, - rt: Arc>, - ) -> Result { - let chain = CosmosSDKChain::from_config(config, rt.clone())?; - Ok(Self::new(chain, light_client, event_receiver, rt)) - } - - // TODO: Make this work for a generic Chain +impl ChainRuntime { + /// Spawns a new runtime for a specific Chain implementation. pub fn spawn(config: ChainConfig) -> Result<(impl ChainHandle, Threads), Error> { let rt = Arc::new(Mutex::new( TokioRuntime::new().map_err(|e| Kind::Io.context(e))?, )); - // Initialize the light clients - let (light_client, supervisor) = TMLightClient::from_config(&config, true)?; + // Similar to `from_config`. + let chain = C::bootstrap(config, rt.clone())?; - // Spawn the light clients - let light_client_thread = thread::spawn(move || supervisor.run().unwrap()); + // Start the light client + let (light_client_handler, light_client_thread) = chain.init_light_client()?; - let (mut event_monitor, event_receiver) = - EventMonitor::new(config.id.clone(), config.rpc_addr.clone(), rt.clone())?; - - // FIXME: Only connect/subscribe on demand + deal with error - event_monitor.subscribe().unwrap(); - - // Spawn the event monitor - let event_monitor_thread = thread::spawn(move || event_monitor.run()); - - // Initialize the source and destination chain runtimes - let chain_runtime = Self::cosmos_sdk(config, light_client, event_receiver, rt)?; - - // Get a handle to the runtime - let handle = chain_runtime.handle(); + // Start the event monitor + let (event_receiver, event_monitor_thread) = chain.init_event_monitor(rt.clone())?; - // Spawn the runtime - let runtime_thread = thread::spawn(move || chain_runtime.run().unwrap()); + // Instantiate & spawn the runtime + let (handle, runtime_thread) = Self::init(chain, light_client_handler, event_receiver, rt); let threads = Threads { light_client: light_client_thread, @@ -113,12 +86,29 @@ impl ChainRuntime { Ok((handle, threads)) } -} -impl ChainRuntime { - pub fn new( + /// Initializes a runtime for a given chain, and spawns the associated thread + fn init( + chain: C, + light_client: Box>, + event_receiver: channel::Receiver, + rt: Arc>, + ) -> (impl ChainHandle, thread::JoinHandle<()>) { + let chain_runtime = Self::new(chain, light_client, event_receiver, rt); + + // Get a handle to the runtime + let handle = chain_runtime.handle(); + + // Spawn the runtime & return + let thread = thread::spawn(move || chain_runtime.run().unwrap()); + + (handle, thread) + } + + /// Basic constructor + fn new( chain: C, - light_client: impl LightClient + 'static, + light_client: Box>, event_receiver: channel::Receiver, rt: Arc>, ) -> Self { @@ -131,7 +121,7 @@ impl ChainRuntime { receiver, event_bus: EventBus::new(), event_receiver, - light_client: Box::new(light_client), + light_client, } } @@ -142,7 +132,7 @@ impl ChainRuntime { ProdChainHandle::new(chain_id, sender) } - pub fn run(mut self) -> Result<(), Error> { + fn run(mut self) -> Result<(), Error> { loop { channel::select! { recv(self.event_receiver) -> event_batch => { @@ -301,7 +291,7 @@ impl ChainRuntime { } fn send_tx( - &self, + &mut self, proto_msgs: Vec, reply_to: ReplyTo, ) -> Result<(), Error> { diff --git a/relayer/src/foreign_client.rs b/relayer/src/foreign_client.rs index 726577a070..cfc7285f61 100644 --- a/relayer/src/foreign_client.rs +++ b/relayer/src/foreign_client.rs @@ -211,3 +211,52 @@ pub fn build_update_client_and_send( Ok(dst_chain.send_tx(new_msgs)?) } + +#[cfg(test)] +mod test { + use std::str::FromStr; + + use ibc::ics24_host::identifier::ClientId; + + use crate::chain::mock::test_utils::get_basic_chain_config; + use crate::chain::mock::MockChain; + use crate::chain::runtime::ChainRuntime; + use crate::foreign_client::{ + build_create_client_and_send, build_update_client_and_send, ForeignClientConfig, + }; + + #[test] + fn test_build_create_client_and_send() { + let client_id = ClientId::from_str("client_on_a_forb").unwrap(); + let a_cfg = get_basic_chain_config("chain_a"); + let b_cfg = get_basic_chain_config("chain_b"); + let opts = ForeignClientConfig::new(&a_cfg.id, &client_id); + + let (a_chain, _) = ChainRuntime::::spawn(a_cfg).unwrap(); + let (b_chain, _) = ChainRuntime::::spawn(b_cfg).unwrap(); + + let res = build_create_client_and_send(a_chain, b_chain, &opts); + assert!( + res.is_ok(), + "build_create_client_and_send failed with error {:?}", + res + ); + } + + #[test] + fn test_build_update_client_and_send() { + let client_id = ClientId::from_str("client_on_a_forb").unwrap(); + let a_cfg = get_basic_chain_config("chain_a"); + let b_cfg = get_basic_chain_config("chain_b"); + let opts = ForeignClientConfig::new(&a_cfg.id, &client_id); + + let (a_chain, _) = ChainRuntime::::spawn(a_cfg).unwrap(); + let (b_chain, _) = ChainRuntime::::spawn(b_cfg).unwrap(); + + let res = build_update_client_and_send(a_chain, b_chain, &opts); + assert!( + res.is_err(), + "build_update_client_and_send was supposed to fail (no client existed)" + ); + } +} diff --git a/relayer/src/keys/add.rs b/relayer/src/keys/add.rs index 647621da19..4fdcbd0bf2 100644 --- a/relayer/src/keys/add.rs +++ b/relayer/src/keys/add.rs @@ -20,7 +20,7 @@ pub fn add_key(opts: KeysAddOptions) -> Result { let rt = TokioRuntime::new().unwrap(); // Get the destination chain - let chain = CosmosSDKChain::from_config(opts.clone().chain_config, Arc::new(Mutex::new(rt)))?; + let chain = CosmosSDKChain::bootstrap(opts.clone().chain_config, Arc::new(Mutex::new(rt)))?; let key_contents = fs::read_to_string(&opts.file) .map_err(|_| Kind::KeyBase.context("error reading the key file"))?; @@ -38,7 +38,7 @@ pub fn add_key(opts: KeysAddOptions) -> Result { "Added key {} ({}) on {} chain", opts.name.as_str(), k.account.as_str(), - chain.config().id.clone() + chain.id().clone() )) } Err(e) => Err(Kind::KeyBase.context(e).into()), diff --git a/relayer/src/keys/list.rs b/relayer/src/keys/list.rs index 7a4da84b89..9ac78835c2 100644 --- a/relayer/src/keys/list.rs +++ b/relayer/src/keys/list.rs @@ -16,7 +16,7 @@ pub fn list_keys(opts: KeysListOptions) -> Result { let rt = TokioRuntime::new().unwrap(); // Get the destination chain - let chain = CosmosSDKChain::from_config(opts.chain_config, Arc::new(Mutex::new(rt)))?; + let chain = CosmosSDKChain::bootstrap(opts.chain_config, Arc::new(Mutex::new(rt)))?; let key_entry = chain.keybase().get_key(); diff --git a/relayer/src/keys/restore.rs b/relayer/src/keys/restore.rs index c24d9d4b1d..1c7fe5e352 100644 --- a/relayer/src/keys/restore.rs +++ b/relayer/src/keys/restore.rs @@ -1,4 +1,4 @@ -use crate::chain::{handle::ChainHandle, runtime::ChainRuntime}; +use crate::chain::{handle::ChainHandle, runtime::ChainRuntime, CosmosSDKChain}; use crate::config::ChainConfig; use crate::error; use crate::error::Error; @@ -12,7 +12,7 @@ pub struct KeysRestoreOptions { pub fn restore_key(opts: KeysRestoreOptions) -> Result, Error> { // Get the destination chain - let (chain, _) = ChainRuntime::spawn(opts.chain_config)?; + let (chain, _) = ChainRuntime::::spawn(opts.chain_config)?; let address = chain .get_key() diff --git a/relayer/src/light_client.rs b/relayer/src/light_client.rs index 7d28f45b43..c9e3bc6d10 100644 --- a/relayer/src/light_client.rs +++ b/relayer/src/light_client.rs @@ -3,6 +3,9 @@ use crate::error; pub mod tendermint; +#[cfg(test)] +pub mod mock; + /// Defines a light block from the point of view of the relayer. pub trait LightBlock: Send + Sync { fn signed_header(&self) -> &C::Header; diff --git a/relayer/src/light_client/mock.rs b/relayer/src/light_client/mock.rs new file mode 100644 index 0000000000..1f716ad2aa --- /dev/null +++ b/relayer/src/light_client/mock.rs @@ -0,0 +1,47 @@ +use crate::chain::mock::MockChain; +use crate::chain::Chain; +use crate::error::Error; +use ibc::ics24_host::identifier::ChainId; +use ibc::mock::host::HostBlock; +use ibc::Height; + +/// A light client serving a mock chain. +pub struct LightClient { + chain_id: ChainId, +} + +impl LightClient { + pub fn new(chain: &MockChain) -> LightClient { + LightClient { + chain_id: chain.id().clone(), + } + } + + /// Returns a LightBlock at the requested height `h`. + fn light_block(&self, h: Height) -> ::LightBlock { + HostBlock::generate_tm_block(self.chain_id.clone(), h.version_height) + } +} + +#[allow(unused_variables)] +impl super::LightClient for LightClient { + fn latest_trusted(&self) -> Result::LightBlock>, Error> { + unimplemented!() + } + + fn verify_to_latest(&self) -> Result<::LightBlock, Error> { + unimplemented!() + } + + fn verify_to_target(&self, height: Height) -> Result<::LightBlock, Error> { + Ok(self.light_block(height)) + } + + fn get_minimal_set( + &self, + latest_client_state_height: Height, + target_height: Height, + ) -> Result, Error> { + unimplemented!() + } +} diff --git a/relayer/src/light_client/tendermint.rs b/relayer/src/light_client/tendermint.rs index e62ec8afab..73cbf49d7f 100644 --- a/relayer/src/light_client/tendermint.rs +++ b/relayer/src/light_client/tendermint.rs @@ -100,8 +100,6 @@ fn build_instance( } fn build_supervisor(config: &ChainConfig, reset: bool) -> Result { - let _id = config.id.clone(); - let options = light_client::Options { trust_threshold: config.trust_threshold, trusting_period: config.trusting_period,