diff --git a/CHANGELOG.md b/CHANGELOG.md index 4741a901e0..eccbacc942 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,11 +13,13 @@ - [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 ## v0.0.5 *December 2, 2020* diff --git a/relayer-cli/src/commands/tx/client.rs b/relayer-cli/src/commands/tx/client.rs index 35acaeec96..0e324434cf 100644 --- a/relayer-cli/src/commands/tx/client.rs +++ b/relayer-cli/src/commands/tx/client.rs @@ -8,9 +8,7 @@ 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, -}; +use relayer::foreign_client::{build_update_client_and_send, ForeignClient, ForeignClientConfig}; #[derive(Clone, Command, Debug, Options)] pub struct TxCreateClientCmd { @@ -52,13 +50,11 @@ impl Runnable for TxCreateClientCmd { let (src_chain, _) = ChainRuntime::::spawn(src_chain_config).unwrap(); let (dst_chain, _) = ChainRuntime::::spawn(dst_chain_config).unwrap(); - let res: Result, Error> = - build_create_client_and_send(&dst_chain, &src_chain, &opts) - .map_err(|e| Kind::Tx.context(e).into()); + let res = ForeignClient::new(dst_chain, src_chain, opts).map_err(|e| Kind::Tx.context(e)); match res { Ok(receipt) => status_ok!("Success", "client created: {:?}", receipt), - Err(e) => status_err!("client create failed: {}", e), + Err(e) => status_err!("client create failed: {:?}", e), } } } @@ -103,7 +99,7 @@ impl Runnable for TxUpdateClientCmd { let (dst_chain, _) = ChainRuntime::::spawn(dst_chain_config).unwrap(); let res: Result, Error> = - build_update_client_and_send(&dst_chain, &src_chain, &opts) + build_update_client_and_send(dst_chain, src_chain, &opts) .map_err(|e| Kind::Tx.context(e).into()); match res { diff --git a/relayer-cli/src/commands/tx/packet.rs b/relayer-cli/src/commands/tx/packet.rs index 7d8634cb77..cb09180ac1 100644 --- a/relayer-cli/src/commands/tx/packet.rs +++ b/relayer-cli/src/commands/tx/packet.rs @@ -72,7 +72,7 @@ impl Runnable for TxRawPacketRecvCmd { ChainRuntime::::spawn(opts.dst_chain_config.clone()).unwrap(); let res: Result, Error> = - build_and_send_recv_packet_messages(&dst_chain, &src_chain, &opts) + build_and_send_recv_packet_messages(dst_chain, src_chain, &opts) .map_err(|e| Kind::Tx.context(e).into()); match res { diff --git a/relayer/Cargo.toml b/relayer/Cargo.toml index ed726a0b4e..94516e168a 100644 --- a/relayer/Cargo.toml +++ b/relayer/Cargo.toml @@ -39,6 +39,7 @@ itertools = "0.9.0" dyn-clonable = "0.9.0" tonic = "0.3.1" dirs-next = "2.0.0" +dyn-clone = "1.0.3" [dependencies.tendermint] version = "=0.17.0-rc3" diff --git a/relayer/src/chain/handle.rs b/relayer/src/chain/handle.rs index d97b4fa59b..f0948a6d35 100644 --- a/relayer/src/chain/handle.rs +++ b/relayer/src/chain/handle.rs @@ -2,6 +2,8 @@ use std::sync::Arc; use crossbeam_channel as channel; +use dyn_clone::DynClone; + use ibc_proto::ibc::core::channel::v1::{ PacketAckCommitment, QueryPacketCommitmentsRequest, QueryUnreceivedPacketsRequest, }; @@ -32,6 +34,7 @@ use crate::{error::Error, event::monitor::EventBatch}; mod prod; pub use prod::ProdChainHandle; +use std::fmt::Debug; pub type Subscription = channel::Receiver>; @@ -42,9 +45,9 @@ pub fn reply_channel() -> (ReplyTo, Reply) { channel::bounded(1) } -/// Inputs that a Handle may send to a Runtime. +/// Requests that a `ChainHandle` may send to a `ChainRuntime`. #[derive(Clone, Debug)] -pub enum HandleInput { +pub enum ChainRequest { Terminate { reply_to: ReplyTo<()>, }, @@ -201,7 +204,10 @@ pub enum HandleInput { }, } -pub trait ChainHandle: Clone + Send + Sync { +// Make `clone` accessible to a ChainHandle object +dyn_clone::clone_trait_object!(ChainHandle); + +pub trait ChainHandle: DynClone + Send + Sync + Debug { fn id(&self) -> ChainId; fn query(&self, path: Path, height: Height, prove: bool) -> Result; diff --git a/relayer/src/chain/handle/prod.rs b/relayer/src/chain/handle/prod.rs index c069d2b33f..0f96536116 100644 --- a/relayer/src/chain/handle/prod.rs +++ b/relayer/src/chain/handle/prod.rs @@ -23,7 +23,7 @@ use ibc::{ // FIXME: the handle should not depend on tendermint-specific types use tendermint::account::Id as AccountId; -use super::{reply_channel, ChainHandle, HandleInput, ReplyTo, Subscription}; +use super::{reply_channel, ChainHandle, ChainRequest, ReplyTo, Subscription}; use crate::{ chain::QueryResponse, @@ -34,24 +34,30 @@ use crate::{ #[derive(Debug, Clone)] pub struct ProdChainHandle { + /// Chain identifier chain_id: ChainId, - sender: channel::Sender, + + /// The handle's channel for sending requests to the runtime + runtime_sender: channel::Sender, } impl ProdChainHandle { - pub fn new(chain_id: ChainId, sender: channel::Sender) -> Self { - Self { chain_id, sender } + pub fn new(chain_id: ChainId, sender: channel::Sender) -> Self { + Self { + chain_id, + runtime_sender: sender, + } } fn send(&self, f: F) -> Result where - F: FnOnce(ReplyTo) -> HandleInput, + F: FnOnce(ReplyTo) -> ChainRequest, O: Debug, { let (sender, receiver) = reply_channel(); let input = f(sender); - self.sender + self.runtime_sender .send(input) .map_err(|e| Kind::Channel.context(e))?; @@ -64,17 +70,13 @@ impl ChainHandle for ProdChainHandle { self.chain_id.clone() } - fn subscribe(&self, _chain_id: ChainId) -> Result { - self.send(|reply_to| HandleInput::Subscribe { reply_to }) - } - fn query( &self, path: ibc::ics24_host::Path, height: Height, prove: bool, ) -> Result { - self.send(|reply_to| HandleInput::Query { + self.send(|reply_to| ChainRequest::Query { path, height, prove, @@ -82,8 +84,12 @@ impl ChainHandle for ProdChainHandle { }) } + fn subscribe(&self, _chain_id: ChainId) -> Result { + self.send(|reply_to| ChainRequest::Subscribe { reply_to }) + } + fn send_msgs(&self, proto_msgs: Vec) -> Result, Error> { - self.send(|reply_to| HandleInput::SendMsgs { + self.send(|reply_to| ChainRequest::SendMsgs { proto_msgs, reply_to, }) @@ -94,19 +100,19 @@ impl ChainHandle for ProdChainHandle { // } fn get_minimal_set(&self, from: Height, to: Height) -> Result, Error> { - self.send(|reply_to| HandleInput::GetMinimalSet { from, to, reply_to }) + self.send(|reply_to| ChainRequest::GetMinimalSet { from, to, reply_to }) } fn get_signer(&self) -> Result { - self.send(|reply_to| HandleInput::Signer { reply_to }) + self.send(|reply_to| ChainRequest::Signer { reply_to }) } fn get_key(&self) -> Result { - self.send(|reply_to| HandleInput::Key { reply_to }) + self.send(|reply_to| ChainRequest::Key { reply_to }) } fn module_version(&self, port_id: &PortId) -> Result { - self.send(|reply_to| HandleInput::ModuleVersion { + self.send(|reply_to| ChainRequest::ModuleVersion { port_id: port_id.clone(), reply_to, }) @@ -124,7 +130,7 @@ impl ChainHandle for ProdChainHandle { // } fn query_latest_height(&self) -> Result { - self.send(|reply_to| HandleInput::QueryLatestHeight { reply_to }) + self.send(|reply_to| ChainRequest::QueryLatestHeight { reply_to }) } fn query_client_state( @@ -132,7 +138,7 @@ impl ChainHandle for ProdChainHandle { client_id: &ClientId, height: Height, ) -> Result { - self.send(|reply_to| HandleInput::QueryClientState { + self.send(|reply_to| ChainRequest::QueryClientState { client_id: client_id.clone(), height, reply_to, @@ -147,11 +153,11 @@ impl ChainHandle for ProdChainHandle { // ) -> Result; fn query_commitment_prefix(&self) -> Result { - self.send(|reply_to| HandleInput::QueryCommitmentPrefix { reply_to }) + self.send(|reply_to| ChainRequest::QueryCommitmentPrefix { reply_to }) } fn query_compatible_versions(&self) -> Result, Error> { - self.send(|reply_to| HandleInput::QueryCompatibleVersions { reply_to }) + self.send(|reply_to| ChainRequest::QueryCompatibleVersions { reply_to }) } fn query_connection( @@ -159,7 +165,7 @@ impl ChainHandle for ProdChainHandle { connection_id: &ConnectionId, height: Height, ) -> Result { - self.send(|reply_to| HandleInput::QueryConnection { + self.send(|reply_to| ChainRequest::QueryConnection { connection_id: connection_id.clone(), height, reply_to, @@ -172,7 +178,7 @@ impl ChainHandle for ProdChainHandle { channel_id: &ChannelId, height: Height, ) -> Result { - self.send(|reply_to| HandleInput::QueryChannel { + self.send(|reply_to| ChainRequest::QueryChannel { port_id: port_id.clone(), channel_id: channel_id.clone(), height, @@ -185,7 +191,7 @@ impl ChainHandle for ProdChainHandle { client_id: &ClientId, height: Height, ) -> Result<(AnyClientState, MerkleProof), Error> { - self.send(|reply_to| HandleInput::ProvenClientState { + self.send(|reply_to| ChainRequest::ProvenClientState { client_id: client_id.clone(), height, reply_to, @@ -197,7 +203,7 @@ impl ChainHandle for ProdChainHandle { connection_id: &ConnectionId, height: Height, ) -> Result<(ConnectionEnd, MerkleProof), Error> { - self.send(|reply_to| HandleInput::ProvenConnection { + self.send(|reply_to| ChainRequest::ProvenConnection { connection_id: connection_id.clone(), height, reply_to, @@ -210,7 +216,7 @@ impl ChainHandle for ProdChainHandle { consensus_height: Height, height: Height, ) -> Result<(AnyConsensusState, MerkleProof), Error> { - self.send(|reply_to| HandleInput::ProvenClientConsensus { + self.send(|reply_to| ChainRequest::ProvenClientConsensus { client_id: client_id.clone(), consensus_height, height, @@ -223,7 +229,7 @@ impl ChainHandle for ProdChainHandle { trusted_height: Height, target_height: Height, ) -> Result { - self.send(|reply_to| HandleInput::BuildHeader { + self.send(|reply_to| ChainRequest::BuildHeader { trusted_height, target_height, reply_to, @@ -231,11 +237,11 @@ impl ChainHandle for ProdChainHandle { } fn build_client_state(&self, height: Height) -> Result { - self.send(|reply_to| HandleInput::BuildClientState { height, reply_to }) + self.send(|reply_to| ChainRequest::BuildClientState { height, reply_to }) } fn build_consensus_state(&self, height: Height) -> Result { - self.send(|reply_to| HandleInput::BuildConsensusState { height, reply_to }) + self.send(|reply_to| ChainRequest::BuildConsensusState { height, reply_to }) } fn build_connection_proofs_and_client_state( @@ -246,7 +252,7 @@ impl ChainHandle for ProdChainHandle { height: Height, ) -> Result<(Option, Proofs), Error> { self.send( - |reply_to| HandleInput::BuildConnectionProofsAndClientState { + |reply_to| ChainRequest::BuildConnectionProofsAndClientState { message_type, connection_id: connection_id.clone(), client_id: client_id.clone(), @@ -262,7 +268,7 @@ impl ChainHandle for ProdChainHandle { channel_id: &ChannelId, height: Height, ) -> Result { - self.send(|reply_to| HandleInput::BuildChannelProofs { + self.send(|reply_to| ChainRequest::BuildChannelProofs { port_id: port_id.clone(), channel_id: channel_id.clone(), height, @@ -277,7 +283,7 @@ impl ChainHandle for ProdChainHandle { sequence: u64, height: Height, ) -> Result<(Vec, MerkleProof), Error> { - self.send(|reply_to| HandleInput::ProvenPacketCommitment { + self.send(|reply_to| ChainRequest::ProvenPacketCommitment { port_id: port_id.clone(), channel_id: channel_id.clone(), sequence, @@ -290,17 +296,17 @@ impl ChainHandle for ProdChainHandle { &self, request: QueryPacketCommitmentsRequest, ) -> Result<(Vec, Height), Error> { - self.send(|reply_to| HandleInput::QueryPacketCommitments { request, reply_to }) + self.send(|reply_to| ChainRequest::QueryPacketCommitments { request, reply_to }) } fn query_unreceived_packets( &self, request: QueryUnreceivedPacketsRequest, ) -> Result, Error> { - self.send(|reply_to| HandleInput::QueryUnreceivedPackets { request, reply_to }) + self.send(|reply_to| ChainRequest::QueryUnreceivedPackets { request, reply_to }) } fn query_txs(&self, request: QueryPacketEventDataRequest) -> Result, Error> { - self.send(|reply_to| HandleInput::QueryPacketEventData { request, reply_to }) + self.send(|reply_to| ChainRequest::QueryPacketEventData { request, reply_to }) } } diff --git a/relayer/src/chain/runtime.rs b/relayer/src/chain/runtime.rs index 56bd388dc7..a66da54352 100644 --- a/relayer/src/chain/runtime.rs +++ b/relayer/src/chain/runtime.rs @@ -33,7 +33,7 @@ use ibc::{ use tendermint::account::Id as AccountId; use super::{ - handle::{ChainHandle, HandleInput, ProdChainHandle, ReplyTo, Subscription}, + handle::{ChainHandle, ChainRequest, ProdChainHandle, ReplyTo, Subscription}, Chain, QueryResponse, }; @@ -53,11 +53,24 @@ pub struct Threads { } pub struct ChainRuntime { + /// The specific chain this runtime runs against chain: C, - sender: channel::Sender, - receiver: channel::Receiver, + + /// The sender side of a channel to this runtime. Any `ChainHandle` can use this to send + /// chain requests to this runtime + request_sender: channel::Sender, + + /// The receiving side of a channel to this runtime. The runtime consumes chain requests coming + /// in through this channel. + request_receiver: channel::Receiver, + + /// An event bus, for broadcasting events that this runtime receives (via `event_receiver`) to subscribers event_bus: EventBus>, + + /// Receiver channel from the event bus event_receiver: channel::Receiver, + + /// A handle to the light client light_client: Box>, #[allow(dead_code)] @@ -66,7 +79,7 @@ pub struct ChainRuntime { impl ChainRuntime { /// Spawns a new runtime for a specific Chain implementation. - pub fn spawn(config: ChainConfig) -> Result<(impl ChainHandle, Threads), Error> { + pub fn spawn(config: ChainConfig) -> Result<(Box, Threads), Error> { let rt = Arc::new(Mutex::new( TokioRuntime::new().map_err(|e| Kind::Io.context(e))?, )); @@ -98,7 +111,7 @@ impl ChainRuntime { light_client: Box>, event_receiver: channel::Receiver, rt: Arc>, - ) -> (impl ChainHandle, thread::JoinHandle<()>) { + ) -> (Box, thread::JoinHandle<()>) { let chain_runtime = Self::new(chain, light_client, event_receiver, rt); // Get a handle to the runtime @@ -117,24 +130,24 @@ impl ChainRuntime { event_receiver: channel::Receiver, rt: Arc>, ) -> Self { - let (sender, receiver) = channel::unbounded::(); + let (request_sender, request_receiver) = channel::unbounded::(); Self { rt, chain, - sender, - receiver, + request_sender, + request_receiver, event_bus: EventBus::new(), event_receiver, light_client, } } - pub fn handle(&self) -> impl ChainHandle { + pub fn handle(&self) -> Box { let chain_id = self.chain.id().clone(); - let sender = self.sender.clone(); + let sender = self.request_sender.clone(); - ProdChainHandle::new(chain_id, sender) + Box::new(ProdChainHandle::new(chain_id, sender)) } fn run(mut self) -> Result<(), Error> { @@ -149,117 +162,117 @@ impl ChainRuntime { // TODO: Handle error } }, - recv(self.receiver) -> event => { + recv(self.request_receiver) -> event => { match event { - Ok(HandleInput::Terminate { reply_to }) => { + Ok(ChainRequest::Terminate { reply_to }) => { reply_to.send(Ok(())).map_err(|_| Kind::Channel)?; break; } - Ok(HandleInput::Subscribe { reply_to }) => { + Ok(ChainRequest::Subscribe { reply_to }) => { self.subscribe(reply_to)? }, - Ok(HandleInput::Query { path, height, prove, reply_to, }) => { + Ok(ChainRequest::Query { path, height, prove, reply_to, }) => { self.query(path, height, prove, reply_to)? }, - Ok(HandleInput::SendMsgs { proto_msgs, reply_to }) => { + Ok(ChainRequest::SendMsgs { proto_msgs, reply_to }) => { self.send_msgs(proto_msgs, reply_to)? }, - Ok(HandleInput::GetMinimalSet { from, to, reply_to }) => { + Ok(ChainRequest::GetMinimalSet { from, to, reply_to }) => { self.get_minimal_set(from, to, reply_to)? } - // Ok(HandleInput::GetHeader { height, reply_to }) => { + // Ok(ChainRequest::GetHeader { height, reply_to }) => { // self.get_header(height, reply_to)? // } // - // Ok(HandleInput::Submit { transaction, reply_to, }) => { + // Ok(ChainRequest::Submit { transaction, reply_to, }) => { // self.submit(transaction, reply_to)? // }, // - // Ok(HandleInput::CreatePacket { event, reply_to }) => { + // Ok(ChainRequest::CreatePacket { event, reply_to }) => { // self.create_packet(event, reply_to)? // } - Ok(HandleInput::Signer { reply_to }) => { + Ok(ChainRequest::Signer { reply_to }) => { self.get_signer(reply_to)? } - Ok(HandleInput::Key { reply_to }) => { + Ok(ChainRequest::Key { reply_to }) => { self.get_key(reply_to)? } - Ok(HandleInput::ModuleVersion { port_id, reply_to }) => { + Ok(ChainRequest::ModuleVersion { port_id, reply_to }) => { self.module_version(port_id, reply_to)? } - Ok(HandleInput::BuildHeader { trusted_height, target_height, reply_to }) => { + Ok(ChainRequest::BuildHeader { trusted_height, target_height, reply_to }) => { self.build_header(trusted_height, target_height, reply_to)? } - Ok(HandleInput::BuildClientState { height, reply_to }) => { + Ok(ChainRequest::BuildClientState { height, reply_to }) => { self.build_client_state(height, reply_to)? } - Ok(HandleInput::BuildConsensusState { height, reply_to }) => { + Ok(ChainRequest::BuildConsensusState { height, reply_to }) => { self.build_consensus_state(height, reply_to)? } - Ok(HandleInput::BuildConnectionProofsAndClientState { message_type, connection_id, client_id, height, reply_to }) => { + Ok(ChainRequest::BuildConnectionProofsAndClientState { message_type, connection_id, client_id, height, reply_to }) => { self.build_connection_proofs_and_client_state(message_type, connection_id, client_id, height, reply_to)? }, - Ok(HandleInput::BuildChannelProofs { port_id, channel_id, height, reply_to }) => { + Ok(ChainRequest::BuildChannelProofs { port_id, channel_id, height, reply_to }) => { self.build_channel_proofs(port_id, channel_id, height, reply_to)? }, - Ok(HandleInput::QueryLatestHeight { reply_to }) => { + Ok(ChainRequest::QueryLatestHeight { reply_to }) => { self.query_latest_height(reply_to)? } - Ok(HandleInput::QueryClientState { client_id, height, reply_to }) => { + Ok(ChainRequest::QueryClientState { client_id, height, reply_to }) => { self.query_client_state(client_id, height, reply_to)? }, - Ok(HandleInput::QueryCommitmentPrefix { reply_to }) => { + Ok(ChainRequest::QueryCommitmentPrefix { reply_to }) => { self.query_commitment_prefix(reply_to)? }, - Ok(HandleInput::QueryCompatibleVersions { reply_to }) => { + Ok(ChainRequest::QueryCompatibleVersions { reply_to }) => { self.query_compatible_versions(reply_to)? }, - Ok(HandleInput::QueryConnection { connection_id, height, reply_to }) => { + Ok(ChainRequest::QueryConnection { connection_id, height, reply_to }) => { self.query_connection(connection_id, height, reply_to)? }, - Ok(HandleInput::QueryChannel { port_id, channel_id, height, reply_to }) => { + Ok(ChainRequest::QueryChannel { port_id, channel_id, height, reply_to }) => { self.query_channel(port_id, channel_id, height, reply_to)? }, - Ok(HandleInput::ProvenClientState { client_id, height, reply_to }) => { + Ok(ChainRequest::ProvenClientState { client_id, height, reply_to }) => { self.proven_client_state(client_id, height, reply_to)? }, - Ok(HandleInput::ProvenConnection { connection_id, height, reply_to }) => { + Ok(ChainRequest::ProvenConnection { connection_id, height, reply_to }) => { self.proven_connection(connection_id, height, reply_to)? }, - Ok(HandleInput::ProvenClientConsensus { client_id, consensus_height, height, reply_to }) => { + Ok(ChainRequest::ProvenClientConsensus { client_id, consensus_height, height, reply_to }) => { self.proven_client_consensus(client_id, consensus_height, height, reply_to)? }, - Ok(HandleInput::ProvenPacketCommitment { port_id, channel_id, sequence, height, reply_to }) => { + Ok(ChainRequest::ProvenPacketCommitment { port_id, channel_id, sequence, height, reply_to }) => { self.proven_packet_commitment(port_id, channel_id, sequence, height, reply_to)? }, - Ok(HandleInput::QueryPacketCommitments { request, reply_to }) => { + Ok(ChainRequest::QueryPacketCommitments { request, reply_to }) => { self.query_packet_commitments(request, reply_to)? }, - Ok(HandleInput::QueryUnreceivedPackets { request, reply_to }) => { + Ok(ChainRequest::QueryUnreceivedPackets { request, reply_to }) => { self.query_unreceived_packets(request, reply_to)? }, - Ok(HandleInput::QueryPacketEventData { request, reply_to }) => { + Ok(ChainRequest::QueryPacketEventData { request, reply_to }) => { self.query_txs(request, reply_to)? }, diff --git a/relayer/src/channel.rs b/relayer/src/channel.rs index 5dfab960dc..4d59dfcdb9 100644 --- a/relayer/src/channel.rs +++ b/relayer/src/channel.rs @@ -115,6 +115,7 @@ impl ChannelConfig { #[derive(Clone, Debug)] pub struct Channel { pub config: ChannelConfig, + connection: Connection, } impl ChannelConfig { @@ -164,7 +165,7 @@ impl ChannelConfig { // temp fix for queries fn get_channel( - chain: impl ChainHandle, + chain: Box, port: &PortId, id: &ChannelId, ) -> Result, ChannelError> { @@ -181,18 +182,31 @@ fn get_channel( } impl Channel { - pub fn new( - a_chain: impl ChainHandle, - b_chain: impl ChainHandle, - _connection: Connection, - config: ChannelConfig, - ) -> Result { + /// Creates a new channel on top of the existing connection. If the channel is not already + /// set-up on both sides of the connection, this functions also fulfils the channel handshake. + pub fn new(connection: Connection, config: ChannelConfig) -> Result { + let channel = Channel { config, connection }; + channel.handshake()?; + + Ok(channel) + } + + /// Returns the underlying connection of this channel + pub fn connection(&self) -> Connection { + self.connection.clone() + } + + /// Executes the channel handshake protocol (ICS004) + fn handshake(&self) -> Result<(), ChannelError> { let done = '\u{1F973}'; - let flipped = config.flipped(); + let flipped = self.config.flipped(); let mut counter = 0; + let a_chain = self.connection.chain_a(); + let b_chain = self.connection.chain_b(); + while counter < MAX_ITER { counter += 1; let now = SystemTime::now(); @@ -200,16 +214,16 @@ impl Channel { // Continue loop if query error let a_channel = get_channel( a_chain.clone(), - &config.a_end().port_id, - &config.a_end().channel_id, + &self.config.a_end().port_id, + &self.config.a_end().channel_id, ); if a_channel.is_err() { continue; } let b_channel = get_channel( b_chain.clone(), - &config.b_end().port_id, - &config.b_end().channel_id, + &self.config.b_end().port_id, + &self.config.b_end().channel_id, ); if b_channel.is_err() { continue; @@ -219,24 +233,24 @@ impl Channel { (None, None) => { // Init to src match build_chan_init_and_send(a_chain.clone(), b_chain.clone(), &flipped) { - Err(e) => info!("{:?} Failed ChanInit {:?}", e, config.a_end()), - Ok(_) => info!("{} ChanInit {:?}", done, config.a_end()), + Err(e) => info!("{:?} Failed ChanInit {:?}", e, self.config.a_end()), + Ok(_) => info!("{} ChanInit {:?}", done, self.config.a_end()), } } (Some(a_channel), None) => { // Try to dest assert!(a_channel.state_matches(&State::Init)); - match build_chan_try_and_send(b_chain.clone(), a_chain.clone(), &config) { - Err(e) => info!("{:?} Failed ChanTry {:?}", e, config.b_end()), - Ok(_) => info!("{} ChanTry {:?}", done, config.b_end()), + match build_chan_try_and_send(b_chain.clone(), a_chain.clone(), &self.config) { + Err(e) => info!("{:?} Failed ChanTry {:?}", e, self.config.b_end()), + Ok(_) => info!("{} ChanTry {:?}", done, self.config.b_end()), } } (None, Some(b_channel)) => { // Try to src assert!(b_channel.state_matches(&State::Init)); match build_chan_try_and_send(a_chain.clone(), b_chain.clone(), &flipped) { - Err(e) => info!("{:?} Failed ChanTry {:?}", e, config.a_end()), - Ok(_) => info!("{} ChanTry {:?}", done, config.a_end()), + Err(e) => info!("{:?} Failed ChanTry {:?}", e, self.config.a_end()), + Ok(_) => info!("{} ChanTry {:?}", done, self.config.a_end()), } } (Some(a_channel), Some(b_channel)) => { @@ -244,18 +258,24 @@ impl Channel { (&State::Init, &State::Init) => { // Try to dest // Try to dest - match build_chan_try_and_send(b_chain.clone(), a_chain.clone(), &config) - { - Err(e) => info!("{:?} Failed ChanTry {:?}", e, config.b_end()), - Ok(_) => info!("{} ChanTry {:?}", done, config.b_end()), + match build_chan_try_and_send( + b_chain.clone(), + a_chain.clone(), + &self.config, + ) { + Err(e) => info!("{:?} Failed ChanTry {:?}", e, self.config.b_end()), + Ok(_) => info!("{} ChanTry {:?}", done, self.config.b_end()), } } (&State::TryOpen, &State::Init) => { // Ack to dest - match build_chan_ack_and_send(b_chain.clone(), a_chain.clone(), &config) - { - Err(e) => info!("{:?} Failed ChanAck {:?}", e, config.b_end()), - Ok(_) => info!("{} ChanAck {:?}", done, config.b_end()), + match build_chan_ack_and_send( + b_chain.clone(), + a_chain.clone(), + &self.config, + ) { + Err(e) => info!("{:?} Failed ChanAck {:?}", e, self.config.b_end()), + Ok(_) => info!("{} ChanAck {:?}", done, self.config.b_end()), } } (&State::Init, &State::TryOpen) | (&State::TryOpen, &State::TryOpen) => { @@ -265,8 +285,8 @@ impl Channel { b_chain.clone(), &flipped, ) { - Err(e) => info!("{:?} Failed ChanAck {:?}", e, config.a_end()), - Ok(_) => info!("{} ChanAck {:?}", done, config.a_end()), + Err(e) => info!("{:?} Failed ChanAck {:?}", e, self.config.a_end()), + Ok(_) => info!("{} ChanAck {:?}", done, self.config.a_end()), } } (&State::Open, &State::TryOpen) => { @@ -274,12 +294,12 @@ impl Channel { match build_chan_confirm_and_send( b_chain.clone(), a_chain.clone(), - &config, + &self.config, ) { Err(e) => { - info!("{:?} Failed ChanConfirm {:?}", e, config.b_end()) + info!("{:?} Failed ChanConfirm {:?}", e, self.config.b_end()) } - Ok(_) => info!("{} ChanConfirm {:?}", done, config.b_end()), + Ok(_) => info!("{} ChanConfirm {:?}", done, self.config.b_end()), } } (&State::TryOpen, &State::Open) => { @@ -296,9 +316,9 @@ impl Channel { (&State::Open, &State::Open) => { info!( "{} {} {} Channel handshake finished for {:#?}", - done, done, done, config + done, done, done, self.config ); - return Ok(Channel { config }); + return Ok(()); } _ => {} // TODO channel close } @@ -323,8 +343,8 @@ pub enum ChannelMsgType { } pub fn build_chan_init( - dst_chain: impl ChainHandle, - _src_chain: impl ChainHandle, + dst_chain: Box, + _src_chain: Box, opts: &ChannelConfig, ) -> Result, Error> { // Check that the destination chain will accept the message, i.e. it does not have the channel @@ -372,8 +392,8 @@ pub fn build_chan_init( } pub fn build_chan_init_and_send( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, + dst_chain: Box, + src_chain: Box, opts: &ChannelConfig, ) -> Result, Error> { let dst_msgs = build_chan_init(dst_chain.clone(), src_chain, &opts)?; @@ -412,8 +432,8 @@ fn check_destination_channel_state( /// built from the message type (`msg_type`) and options (`opts`). /// If the expected and the destination channels are compatible, it returns the expected channel fn validated_expected_channel( - dst_chain: impl ChainHandle, - _src_chain: impl ChainHandle, + dst_chain: Box, + _src_chain: Box, msg_type: ChannelMsgType, opts: &ChannelConfig, ) -> Result { @@ -473,8 +493,8 @@ fn validated_expected_channel( } pub fn build_chan_try( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, + dst_chain: Box, + src_chain: Box, opts: &ChannelConfig, ) -> Result, Error> { // Check that the destination chain will accept the message, i.e. it does not have the channel @@ -514,8 +534,8 @@ pub fn build_chan_try( // Build message to update client on destination let mut msgs = build_update_client( - &dst_chain, - &src_chain, + dst_chain.clone(), + src_chain.clone(), &dest_connection.client_id(), ics_target_height, )?; @@ -561,8 +581,8 @@ pub fn build_chan_try( } pub fn build_chan_try_and_send( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, + dst_chain: Box, + src_chain: Box, opts: &ChannelConfig, ) -> Result, Error> { let dst_msgs = build_chan_try(dst_chain.clone(), src_chain, &opts)?; @@ -570,8 +590,8 @@ pub fn build_chan_try_and_send( } pub fn build_chan_ack( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, + dst_chain: Box, + src_chain: Box, opts: &ChannelConfig, ) -> Result, Error> { // Check that the destination chain will accept the message @@ -611,8 +631,8 @@ pub fn build_chan_ack( // Build message to update client on destination let mut msgs = build_update_client( - &dst_chain, - &src_chain, + dst_chain.clone(), + src_chain.clone(), &dest_connection.client_id(), ics_target_height, )?; @@ -644,8 +664,8 @@ pub fn build_chan_ack( } pub fn build_chan_ack_and_send( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, + dst_chain: Box, + src_chain: Box, opts: &ChannelConfig, ) -> Result, Error> { let dst_msgs = build_chan_ack(dst_chain.clone(), src_chain, &opts)?; @@ -653,8 +673,8 @@ pub fn build_chan_ack_and_send( } pub fn build_chan_confirm( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, + dst_chain: Box, + src_chain: Box, opts: &ChannelConfig, ) -> Result, Error> { // Check that the destination chain will accept the message @@ -694,8 +714,8 @@ pub fn build_chan_confirm( // Build message to update client on destination let mut msgs = build_update_client( - &dst_chain, - &src_chain, + dst_chain.clone(), + src_chain.clone(), &dest_connection.client_id(), ics_target_height, )?; @@ -725,8 +745,8 @@ pub fn build_chan_confirm( } pub fn build_chan_confirm_and_send( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, + dst_chain: Box, + src_chain: Box, opts: &ChannelConfig, ) -> Result, Error> { let dst_msgs = build_chan_confirm(dst_chain.clone(), src_chain, &opts)?; diff --git a/relayer/src/connection.rs b/relayer/src/connection.rs index 1e5ece5e2f..c809e16793 100644 --- a/relayer/src/connection.rs +++ b/relayer/src/connection.rs @@ -29,11 +29,16 @@ use crate::relay::MAX_ITER; pub enum ConnectionError { #[error("Failed")] Failed(String), + + #[error("constructor parameters do not match")] + ConstructorFailed(String), } #[derive(Clone, Debug)] pub struct Connection { - pub config: ConnectionConfig, + config: ConnectionConfig, + a_client: ForeignClient, + b_client: ForeignClient, } #[derive(Clone, Debug)] @@ -145,7 +150,7 @@ impl ConnectionConfig { // temp fix for queries fn get_connection( - chain: impl ChainHandle, + chain: Box, id: &ConnectionId, ) -> Result, ConnectionError> { match chain.query_connection(id, Height::zero()) { @@ -161,16 +166,56 @@ fn get_connection( } impl Connection { + /// Create a new connection, ensuring that the handshake has succeeded and the two connection + /// ends exist on each side. pub fn new( - a_chain: impl ChainHandle, - b_chain: impl ChainHandle, - _a_client: ForeignClient, - _b_client: ForeignClient, + a_client: ForeignClient, + b_client: ForeignClient, config: ConnectionConfig, ) -> Result { + // Validate that the two clients serve the same two chains + if a_client.src_chain().id().ne(&b_client.dst_chain().id()) { + return Err(ConnectionError::ConstructorFailed(format!( + "the source chain of client a ({}) does not not match the destination chain of client b ({})", + a_client.src_chain().id(), + b_client.dst_chain().id() + ))); + } else if a_client.dst_chain().id().ne(&b_client.src_chain().id()) { + return Err(ConnectionError::ConstructorFailed(format!( + "the destination chain of client a ({}) does not not match the source chain of client b ({})", + a_client.dst_chain().id(), + b_client.src_chain().id() + ))); + } + + let c = Connection { + config, + a_client, + b_client, + }; + c.handshake()?; + + Ok(c) + } + + /// Returns the "a" side of the connection. + pub fn chain_a(&self) -> Box { + self.a_client.dst_chain() + } + + /// Returns the "b" side of the connection. + pub fn chain_b(&self) -> Box { + self.b_client.dst_chain() + } + + /// Executes a connection handshake protocol (ICS 003) for this connection object + fn handshake(&self) -> Result<(), ConnectionError> { let done = '\u{1F942}'; // surprise emoji - let flipped = config.flipped(); + let a_chain = self.chain_a(); + let b_chain = self.chain_b(); + + let flipped = self.config.flipped(); let mut counter = 0; while counter < MAX_ITER { @@ -178,11 +223,11 @@ impl Connection { let now = SystemTime::now(); // Continue loop if query error - let a_connection = get_connection(a_chain.clone(), &config.a_end().connection_id); + let a_connection = get_connection(a_chain.clone(), &self.config.a_end().connection_id); if a_connection.is_err() { continue; } - let b_connection = get_connection(b_chain.clone(), &config.b_end().connection_id); + let b_connection = get_connection(b_chain.clone(), &self.config.b_end().connection_id); if b_connection.is_err() { continue; } @@ -191,40 +236,50 @@ impl Connection { (None, None) => { // Init to src match build_conn_init_and_send(a_chain.clone(), b_chain.clone(), &flipped) { - Err(e) => info!("{:?} Failed ConnInit {:?}", e, config.a_end()), - Ok(_) => info!("{} ConnInit {:?}", done, config.a_end()), + Err(e) => info!("{:?} Failed ConnInit {:?}", e, self.config.a_end()), + Ok(_) => info!("{} ConnInit {:?}", done, self.config.a_end()), } } (Some(a_connection), None) => { assert!(a_connection.state_matches(&State::Init)); - match build_conn_try_and_send(b_chain.clone(), a_chain.clone(), &config) { - Err(e) => info!("{:?} Failed ConnTry {:?}", e, config.b_end()), - Ok(_) => info!("{} ConnTry {:?}", done, config.b_end()), + match build_conn_try_and_send(b_chain.clone(), a_chain.clone(), &self.config) { + Err(e) => info!("{:?} Failed ConnTry {:?}", e, self.config.b_end()), + Ok(_) => info!("{} ConnTry {:?}", done, self.config.b_end()), } } (None, Some(b_connection)) => { assert!(b_connection.state_matches(&State::Init)); match build_conn_try_and_send(a_chain.clone(), b_chain.clone(), &flipped) { - Err(e) => info!("{:?} Failed ConnTry {:?}", e, config.a_end()), - Ok(_) => info!("{} ConnTry {:?}", done, config.a_end()), + Err(e) => info!("{:?} Failed ConnTry {:?}", e, self.config.a_end()), + Ok(_) => info!("{} ConnTry {:?}", done, self.config.a_end()), } } (Some(a_connection), Some(b_connection)) => { match (a_connection.state(), b_connection.state()) { (&State::Init, &State::Init) => { // Try to dest - match build_conn_try_and_send(b_chain.clone(), a_chain.clone(), &config) - { - Err(e) => info!("{:?} Failed ConnTry {:?}", e, config.b_end()), - Ok(_) => info!("{} ConnTry {:?}", done, config.b_end()), + match build_conn_try_and_send( + b_chain.clone(), + a_chain.clone(), + &self.config, + ) { + Err(e) => { + info!("{:?} Failed ConnTry {:?}", e, self.config.b_end()) + } + Ok(_) => info!("{} ConnTry {:?}", done, self.config.b_end()), } } (&State::TryOpen, &State::Init) => { // Ack to dest - match build_conn_ack_and_send(b_chain.clone(), a_chain.clone(), &config) - { - Err(e) => info!("{:?} Failed ConnAck {:?}", e, config.b_end()), - Ok(_) => info!("{} ConnAck {:?}", done, config.b_end()), + match build_conn_ack_and_send( + b_chain.clone(), + a_chain.clone(), + &self.config, + ) { + Err(e) => { + info!("{:?} Failed ConnAck {:?}", e, self.config.b_end()) + } + Ok(_) => info!("{} ConnAck {:?}", done, self.config.b_end()), } } (&State::Init, &State::TryOpen) | (&State::TryOpen, &State::TryOpen) => { @@ -234,8 +289,10 @@ impl Connection { b_chain.clone(), &flipped, ) { - Err(e) => info!("{:?} Failed ConnAck {:?}", e, config.a_end()), - Ok(_) => info!("{} ConnAck {:?}", done, config.a_end()), + Err(e) => { + info!("{:?} Failed ConnAck {:?}", e, self.config.a_end()) + } + Ok(_) => info!("{} ConnAck {:?}", done, self.config.a_end()), } } (&State::Open, &State::TryOpen) => { @@ -243,12 +300,14 @@ impl Connection { match build_conn_confirm_and_send( b_chain.clone(), a_chain.clone(), - &config, + &self.config, ) { Err(e) => { - info!("{:?} Failed ConnConfirm {:?}", e, config.b_end()) + info!("{:?} Failed ConnConfirm {:?}", e, self.config.b_end()) + } + Ok(_) => { + info!("{} ConnConfirm {:?}", done, self.config.b_end()) } - Ok(_) => info!("{} ConnConfirm {:?}", done, config.b_end()), } } (&State::TryOpen, &State::Open) => { @@ -258,16 +317,18 @@ impl Connection { b_chain.clone(), &flipped, ) { - Err(e) => info!("{:?} ConnConfirm {:?}", e, config.a_end()), - Ok(_) => info!("{} ConnConfirm {:?}", done, config.a_end()), + Err(e) => info!("{:?} ConnConfirm {:?}", e, self.config.a_end()), + Ok(_) => { + info!("{} ConnConfirm {:?}", done, self.config.a_end()) + } } } (&State::Open, &State::Open) => { info!( "{} {} {} Connection handshake finished for [{:#?}]", - done, done, done, config + done, done, done, self.config ); - return Ok(Connection { config }); + return Ok(()); } _ => {} } @@ -292,8 +353,8 @@ pub enum ConnectionMsgType { } pub fn build_conn_init( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, + dst_chain: Box, + src_chain: Box, opts: &ConnectionConfig, ) -> Result, Error> { // Check that the destination chain will accept the message, i.e. it does not have the connection @@ -334,8 +395,8 @@ pub fn build_conn_init( } pub fn build_conn_init_and_send( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, + dst_chain: Box, + src_chain: Box, opts: &ConnectionConfig, ) -> Result, Error> { let dst_msgs = build_conn_init(dst_chain.clone(), src_chain, opts)?; @@ -375,8 +436,8 @@ fn check_destination_connection_state( /// built from the message type (`msg_type`) and options (`opts`). /// If the expected and the destination connections are compatible, it returns the expected connection fn validated_expected_connection( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, + dst_chain: Box, + src_chain: Box, msg_type: ConnectionMsgType, opts: &ConnectionConfig, ) -> Result { @@ -435,8 +496,8 @@ fn validated_expected_connection( /// Attempts to build a MsgConnOpenTry. pub fn build_conn_try( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, + dst_chain: Box, + src_chain: Box, opts: &ConnectionConfig, ) -> Result, Error> { let dst_expected_connection = validated_expected_connection( @@ -469,8 +530,8 @@ pub fn build_conn_try( // TODO - add check if it is required let src_client_target_height = dst_chain.query_latest_height()?; let client_msgs = build_update_client( - &src_chain, - &dst_chain, + src_chain.clone(), + dst_chain.clone(), &opts.src().client_id(), src_client_target_height, )?; @@ -480,8 +541,8 @@ pub fn build_conn_try( let ics_target_height = src_chain.query_latest_height()?; let mut msgs = build_update_client( - &dst_chain, - &src_chain, + dst_chain.clone(), + src_chain.clone(), &opts.dst().client_id(), ics_target_height, )?; @@ -523,8 +584,8 @@ pub fn build_conn_try( } pub fn build_conn_try_and_send( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, + dst_chain: Box, + src_chain: Box, opts: &ConnectionConfig, ) -> Result, Error> { let dst_msgs = build_conn_try(dst_chain.clone(), src_chain, &opts)?; @@ -533,8 +594,8 @@ pub fn build_conn_try_and_send( /// Attempts to build a MsgConnOpenAck. pub fn build_conn_ack( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, + dst_chain: Box, + src_chain: Box, opts: &ConnectionConfig, ) -> Result, Error> { let _expected_dst_connection = validated_expected_connection( @@ -569,8 +630,8 @@ pub fn build_conn_ack( // TODO - add check if it is required let src_client_target_height = dst_chain.query_latest_height()?; let client_msgs = build_update_client( - &src_chain, - &dst_chain, + src_chain.clone(), + dst_chain.clone(), &opts.src().client_id(), src_client_target_height, )?; @@ -580,8 +641,8 @@ pub fn build_conn_ack( let ics_target_height = src_chain.query_latest_height()?; let mut msgs = build_update_client( - &dst_chain, - &src_chain, + dst_chain.clone(), + src_chain.clone(), &opts.dst().client_id(), ics_target_height, )?; @@ -615,8 +676,8 @@ pub fn build_conn_ack( } pub fn build_conn_ack_and_send( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, + dst_chain: Box, + src_chain: Box, opts: &ConnectionConfig, ) -> Result, Error> { let dst_msgs = build_conn_ack(dst_chain.clone(), src_chain, opts)?; @@ -625,8 +686,8 @@ pub fn build_conn_ack_and_send( /// Attempts to build a MsgConnOpenConfirm. pub fn build_conn_confirm( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, + dst_chain: Box, + src_chain: Box, opts: &ConnectionConfig, ) -> Result, Error> { let _expected_dst_connection = validated_expected_connection( @@ -660,8 +721,8 @@ pub fn build_conn_confirm( let ics_target_height = src_chain.query_latest_height()?; let mut msgs = build_update_client( - &dst_chain, - &src_chain, + dst_chain.clone(), + src_chain.clone(), &opts.dst().client_id(), ics_target_height, )?; @@ -692,8 +753,8 @@ pub fn build_conn_confirm( } pub fn build_conn_confirm_and_send( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, + dst_chain: Box, + src_chain: Box, opts: &ConnectionConfig, ) -> Result, Error> { let dst_msgs = build_conn_confirm(dst_chain.clone(), src_chain, &opts)?; diff --git a/relayer/src/foreign_client.rs b/relayer/src/foreign_client.rs index f44f7989f5..951829aa04 100644 --- a/relayer/src/foreign_client.rs +++ b/relayer/src/foreign_client.rs @@ -27,8 +27,8 @@ pub enum ForeignClientError { #[derive(Clone, Debug)] pub struct ForeignClientConfig { - /// The identifier of the chain which hosts this client - chain: ChainId, + /// The identifier of the chain which hosts this client, or destination chain + chain: ChainId, // TODO: This field seems useless, consider removing /// The client's identifier id: ClientId, @@ -53,88 +53,129 @@ impl ForeignClientConfig { #[derive(Clone, Debug)] pub struct ForeignClient { + /// The configuration of this client. config: ForeignClientConfig, + + /// A handle to the chain hosting this client, i.e., destination chain. + dst_chain: Box, + + /// A handle to the chain whose headers this client is verifying, aka the source chain. + src_chain: Box, } impl ForeignClient { - /// Creates a new foreign client. Blocks until the client is created on `dst_chain` or + /// Creates a new foreign client on `host_chain`. Blocks until the client is created, or /// an error occurs. - /// Post-condition: `dst_chain` hosts an IBC client for `src_chain`. + /// Post-condition: `host_chain` hosts an IBC client for `src_chain`. /// TODO: what are the pre-conditions for success? - /// Is it enough to have a "live" handle to each of `dst_chain` and `src_chain` chains? + /// Is it enough to have a "live" handle to each of `host_chain` and `src_chain` chains? pub fn new( - dst_chain: &impl ChainHandle, - src_chain: &impl ChainHandle, + dst_chain: Box, + src_chain: Box, config: ForeignClientConfig, ) -> Result { + // Sanity check: the configuration should be consistent with the other parameters + if config.chain_id().ne(&dst_chain.id()) { + return Err(ForeignClientError::ClientCreate(format!( + "host chain id ({}) in config does not not match host chain in argument ({})", + config.chain_id(), + dst_chain.id() + ))); + } + + let client = ForeignClient { + config, + dst_chain: dst_chain.clone(), + src_chain: src_chain.clone(), + }; + + client.create()?; + + Ok(client) + } + + fn create(&self) -> Result<(), ForeignClientError> { let done = '\u{1F36D}'; // Query the client state on source chain. - let client_state = dst_chain.query_client_state(&config.id, Height::default()); + let client_state = self + .dst_chain + .query_client_state(&self.config.id, Height::default()); + + // If an error is returned, the client does not already exist if client_state.is_err() { - build_create_client_and_send(dst_chain, src_chain, &config).map_err(|e| { + build_create_client_and_send( + self.dst_chain.clone(), + self.src_chain.clone(), + &self.config, + ) + .map_err(|e| { ForeignClientError::ClientCreate(format!("Create client failed ({:?})", e)) })?; } info!( "{} Client on {:?} is created {:?}\n", - done, config.chain, config.id + done, self.config.chain, self.config.id ); - Ok(ForeignClient { config }) + + Ok(()) } - pub fn update( - &mut self, - _src_chain: impl ChainHandle, - _dst_chain: impl ChainHandle, - src_target_height: Height, - ) -> Result { - /* - return Ok(src_target_height); - let (src_consensus_state, dst_membership_proof) = - dst_chain.consensus_state(src_chain.id(), src_target_height); - - let dst_sh = dst_chain.get_header(dst_membership_proof.height + 1); - // type verifyMembership = (root: CommitmentRoot, proof: CommitmentProof, path: CommitmentPath, value: Value) => boolean (ICS-023) - if ! verify_consensus_state_inclusion(&src_consensus_state, &dst_membership_proof, &(dst_sh.header.app_hash)) { - // Error: Destination chain provided invalid consensus_state - return Err(ForeignClientError::VerificationError()) - } + /// Returns a handle to the chain hosting this client. + pub fn dst_chain(&self) -> Box { + self.dst_chain.clone() + } - if src_chain.get_header(src_consensus_state.height).header == src_consensus_state.signed_header.header { - return Err(ForeignClientError::HeaderMismatch()) - } + /// Returns a handle to the chain whose headers this client is sourcing (the source chain). + pub fn src_chain(&self) -> Box { + self.src_chain.clone() + } - while src_consensus_state.height < src_target_height { - let src_signed_headers = src_chain.get_minimal_set(src_consensus_state.height, src_target_height); - - // if we actually want to do this we need to create a transaction - // This might fail semantically due to competing relayers - // Even if this fails, we need to continue - // XXX FIXME - dst_chain.submit(vec![create_client_update_datagram(src_signed_headers)]); - - let (src_consensus_state, dst_membership_proof) = dst_chain.consensus_state(src_chain.id(), src_target_height); - let dst_sh = dst_chain.get_header(dst_membership_proof.height + 1); - if ! verify_consensus_state_inclusion(&src_consensus_state, &dst_membership_proof, &(dst_sh.header.app_hash)) { - // Error: Destination chain provided invalid client_state - return Err(ForeignClientError::VerificationError()) - } - - if src_chain.get_header(src_consensus_state.height).header == src_consensus_state.signed_header.header { - // Error: consesus_state isn't verified by self light client - return Err(ForeignClientError::HeaderMismatch()) - } - } - */ + /// Creates a message for updating this client with the latest header of its source chain. + pub fn prepare_update(&self) -> Result, ForeignClientError> { + let target_height = self.src_chain.query_latest_height().map_err(|e| { + ForeignClientError::ClientUpdate(format!("error querying latest height {:?}", e)) + })?; + let msg = build_update_client( + self.dst_chain.clone(), + self.src_chain.clone(), + self.config.client_id(), + target_height, + ) + .map_err(|e| { + ForeignClientError::ClientUpdate(format!("build_update_client error {:?}", e)) + })?; + + Ok(msg) + } + + /// Attempts to update a client using header from the latest height of its source chain. + pub fn update(&self) -> Result<(), ForeignClientError> { + let res = build_update_client_and_send( + self.dst_chain.clone(), + self.src_chain.clone(), + &self.config, + ) + .map_err(|e| { + ForeignClientError::ClientUpdate(format!("build_create_client_and_send {:?}", e)) + })?; + + println!( + "Client id {:?} on {:?} updated with return message {:?}\n", + self.config.id, self.config.chain, res + ); - Ok(src_target_height) + Ok(()) } } +/// Lower-level interface for preparing a message to create a client. +/// +/// ## Note +/// Methods in `ForeignClient` (see `new`) should be preferred over this. pub fn build_create_client( - dst_chain: &impl ChainHandle, - src_chain: &impl ChainHandle, + dst_chain: Box, + src_chain: Box, dst_client_id: &ClientId, ) -> Result { // Verify that the client has not been created already, i.e the destination chain does not @@ -165,19 +206,27 @@ pub fn build_create_client( })?) } +/// Lower-level interface for creating a client. +/// +/// ## Note +/// Methods in `ForeignClient` (see `new`) should be preferred over this. pub fn build_create_client_and_send( - dst_chain: &impl ChainHandle, - src_chain: &impl ChainHandle, + dst_chain: Box, + src_chain: Box, opts: &ForeignClientConfig, ) -> Result, Error> { - let new_msg = build_create_client(dst_chain, src_chain, opts.client_id())?; + let new_msg = build_create_client(dst_chain.clone(), src_chain, opts.client_id())?; Ok(dst_chain.send_msgs(vec![new_msg.to_any::()])?) } +/// Lower-level interface to create the message for updating a client to height `target_height`. +/// +/// ## Note +/// Methods in `ForeignClient`, in particular `prepare_update`, should be preferred over this. pub fn build_update_client( - dst_chain: &impl ChainHandle, - src_chain: &impl ChainHandle, + dst_chain: Box, + src_chain: Box, dst_client_id: &ClientId, target_height: Height, ) -> Result, Error> { @@ -200,14 +249,18 @@ pub fn build_update_client( Ok(vec![new_msg.to_any::()]) } +/// Lower-level interface for preparing a message to update a client. +/// +/// ## Note +/// Methods in `ForeignClient` (see `update`) should be preferred over this. pub fn build_update_client_and_send( - dst_chain: &impl ChainHandle, - src_chain: &impl ChainHandle, + dst_chain: Box, + src_chain: Box, opts: &ForeignClientConfig, ) -> Result, Error> { let new_msgs = build_update_client( - dst_chain, - src_chain, + dst_chain.clone(), + src_chain.clone(), opts.client_id(), src_chain.query_latest_height()?, )?; @@ -225,7 +278,6 @@ mod test { use ibc::ics24_host::identifier::ClientId; use ibc::Height; - use crate::chain::handle::ChainHandle; use crate::chain::mock::test_utils::get_basic_chain_config; use crate::chain::mock::MockChain; use crate::chain::runtime::ChainRuntime; @@ -248,7 +300,7 @@ mod test { let (b_chain, _) = ChainRuntime::::spawn(b_cfg).unwrap(); // Create the client on chain a - let res = build_create_client_and_send(&a_chain, &b_chain, &a_opts); + let res = build_create_client_and_send(a_chain.clone(), b_chain.clone(), &a_opts); assert!( res.is_ok(), "build_create_client_and_send failed (chain a) with error {:?}", @@ -256,14 +308,14 @@ mod test { ); // Double client creation should be forbidden. - let res = build_create_client_and_send(&a_chain, &b_chain, &a_opts); + let res = build_create_client_and_send(a_chain.clone(), b_chain.clone(), &a_opts); assert!( res.is_err(), "build_create_client_and_send double client creation should have failed!", ); // Create the client on chain b - let res = build_create_client_and_send(&b_chain, &a_chain, &b_opts); + let res = build_create_client_and_send(b_chain.clone(), a_chain.clone(), &b_opts); assert!( res.is_ok(), "build_create_client_and_send failed (chain b) with error {:?}", @@ -271,7 +323,7 @@ mod test { ); // Test double creation for chain b - let res = build_create_client_and_send(&b_chain, &a_chain, &b_opts); + let res = build_create_client_and_send(b_chain, a_chain, &b_opts); assert!( res.is_err(), "build_create_client_and_send failed (chain b) with error {:?}", @@ -296,7 +348,7 @@ mod test { let (b_chain, _) = ChainRuntime::::spawn(b_cfg).unwrap(); // This action should fail because no client exists (yet) - let res = build_update_client_and_send(&a_chain, &b_chain, &a_opts); + let res = build_update_client_and_send(a_chain.clone(), b_chain.clone(), &a_opts); assert!( res.is_err(), "build_update_client_and_send was supposed to fail (no client existed)" @@ -306,7 +358,7 @@ mod test { let b_height_start = b_chain.query_latest_height().unwrap(); // Create a client on chain a - let res = build_create_client_and_send(&a_chain, &b_chain, &a_opts); + let res = build_create_client_and_send(a_chain.clone(), b_chain.clone(), &a_opts); assert!( res.is_ok(), "build_create_client_and_send failed (chain a) with error {:?}", @@ -316,7 +368,7 @@ mod test { // This should fail because the client on chain a already has the latest headers. Chain b, // the source chain for the client on a, is at the same height where it was when the client // was created, so an update should fail here. - let res = build_update_client_and_send(&a_chain, &b_chain, &a_opts); + let res = build_update_client_and_send(a_chain.clone(), b_chain.clone(), &a_opts); assert!( res.is_err(), @@ -326,7 +378,7 @@ mod test { assert_eq!(b_height_last, b_height_start); // Create a client on chain b - let res = build_create_client_and_send(&b_chain, &a_chain, &b_opts); + let res = build_create_client_and_send(b_chain.clone(), a_chain.clone(), &b_opts); assert!( res.is_ok(), "build_create_client_and_send failed (chain b) with error {:?}", @@ -341,7 +393,7 @@ mod test { // Now we can update both clients -- a ping pong, similar to ICS18 `client_update_ping_pong` for _i in 1..num_iterations { - let res = build_update_client_and_send(&a_chain, &b_chain, &a_opts); + let res = build_update_client_and_send(a_chain.clone(), b_chain.clone(), &a_opts); assert!( res.is_ok(), "build_update_client_and_send failed (chain a) with error: {:?}", @@ -355,7 +407,7 @@ mod test { ); // And also update the client on chain b. - let res = build_update_client_and_send(&b_chain, &a_chain, &b_opts); + let res = build_update_client_and_send(b_chain.clone(), a_chain.clone(), &b_opts); assert!( res.is_ok(), "build_update_client_and_send failed (chain b) with error: {:?}", @@ -384,14 +436,14 @@ mod test { let (b_chain, _) = ChainRuntime::::spawn(b_cfg).unwrap(); // Instantiate the foreign clients on the two chains. - let client_on_a = ForeignClient::new(&a_chain, &b_chain, a_opts); + let client_on_a = ForeignClient::new(a_chain.clone(), b_chain.clone(), a_opts); assert!( client_on_a.is_ok(), "Client creation (on chain a) failed with error: {:?}", client_on_a ); - let client_on_b = ForeignClient::new(&b_chain, &a_chain, b_opts); + let client_on_b = ForeignClient::new(b_chain.clone(), a_chain.clone(), b_opts); assert!( client_on_b.is_ok(), "Client creation (on chain a) failed with error: {:?}", @@ -413,4 +465,65 @@ mod test { a_client_state ); } + + /// Tests for `ForeignClient::update()`. + #[test] + fn foreign_client_update() { + let a_cfg = get_basic_chain_config("chain_a"); + let b_cfg = get_basic_chain_config("chain_b"); + let a_client_id = ClientId::from_str("client_on_a_forb").unwrap(); + let a_opts = ForeignClientConfig::new(&a_cfg.id, &a_client_id); + let b_client_id = ClientId::from_str("client_on_b_fora").unwrap(); + let b_opts = ForeignClientConfig::new(&b_cfg.id, &b_client_id); + + let (a_chain, _) = ChainRuntime::::spawn(a_cfg).unwrap(); + let (b_chain, _) = ChainRuntime::::spawn(b_cfg).unwrap(); + + // Instantiate the foreign clients on the two chains. + let client_on_a_res = ForeignClient::new(a_chain.clone(), b_chain.clone(), a_opts); + assert!( + client_on_a_res.is_ok(), + "Client creation (on chain a) failed with error: {:?}", + client_on_a_res + ); + let client_on_a = client_on_a_res.unwrap(); + + let client_on_b_res = ForeignClient::new(b_chain.clone(), a_chain.clone(), b_opts); + assert!( + client_on_b_res.is_ok(), + "Client creation (on chain a) failed with error: {:?}", + client_on_b_res + ); + let client_on_b = client_on_b_res.unwrap(); + + let num_iterations = 5; + + let mut b_height_start = b_chain.query_latest_height().unwrap(); + let mut a_height_start = a_chain.query_latest_height().unwrap(); + + // Update each client + for _i in 1..num_iterations { + let res = client_on_a.update(); + assert!(res.is_ok(), "Client update for chain a failed {:?}", res); + + // Basic check that the height of the chain advanced + let a_height_current = a_chain.query_latest_height().unwrap(); + a_height_start = a_height_start.increment(); + assert_eq!( + a_height_start, a_height_current, + "after client update, chain a did not advance" + ); + + let res = client_on_b.update(); + assert!(res.is_ok(), "Client update for chain b failed {:?}", res); + + // Basic check that the height of the chain advanced + let b_height_current = b_chain.query_latest_height().unwrap(); + b_height_start = b_height_start.increment(); + assert_eq!( + b_height_start, b_height_current, + "after client update, chain b did not advance" + ); + } + } } diff --git a/relayer/src/keys/restore.rs b/relayer/src/keys/restore.rs index 1c7fe5e352..3956fd81d3 100644 --- a/relayer/src/keys/restore.rs +++ b/relayer/src/keys/restore.rs @@ -1,4 +1,4 @@ -use crate::chain::{handle::ChainHandle, runtime::ChainRuntime, CosmosSDKChain}; +use crate::chain::{runtime::ChainRuntime, CosmosSDKChain}; use crate::config::ChainConfig; use crate::error; use crate::error::Error; diff --git a/relayer/src/link.rs b/relayer/src/link.rs index 13f20e856a..a0bf3cb641 100644 --- a/relayer/src/link.rs +++ b/relayer/src/link.rs @@ -49,15 +49,16 @@ pub struct Link { } fn send_update_client_and_msgs( - dst_chain: &impl ChainHandle, - src_chain: &impl ChainHandle, + dst_chain: Box, + src_chain: Box, msgs: &mut Vec, height: &Height, client_id: &ClientId, ) -> Result<(), Error> { if !msgs.is_empty() { let update_height = height.increment(); - let mut msgs_to_send = build_update_client(dst_chain, src_chain, client_id, update_height)?; + let mut msgs_to_send = + build_update_client(dst_chain.clone(), src_chain, client_id, update_height)?; msgs_to_send.append(msgs); info!("sending {:#?} messages", msgs_to_send.len()); let res = dst_chain.send_msgs(msgs_to_send)?; @@ -84,7 +85,7 @@ impl Link { // Determines if the event received from the chain `from_chain` is relevant and // should be processed. // Only events for a port/channel matching one of the channel ends should be processed. - fn must_process_event(&self, from_chain: &impl ChainHandle, event: &IBCEvent) -> bool { + fn must_process_event(&self, from_chain: Box, event: &IBCEvent) -> bool { match event { IBCEvent::SendPacketChannel(send_packet_ev) => { self.channel.config.a_config.chain_id().clone() == from_chain.id() @@ -104,8 +105,8 @@ impl Link { fn relay_from_events( &self, - src_chain: &impl ChainHandle, - dst_chain: &impl ChainHandle, + src_chain: Box, + dst_chain: Box, src_subscription: &Subscription, ) -> Result<(), LinkError> { let mut prev_height = Height::zero(); @@ -115,10 +116,10 @@ impl Link { // Send a multi message transaction with these, prepending the client update for batch in src_subscription.try_iter().collect::>().iter() { for event in batch.events.iter() { - if !self.must_process_event(src_chain, event) { + if !self.must_process_event(src_chain.clone(), event) { continue; } - let packet_msg = handle_packet_event(dst_chain, src_chain, event)?; + let packet_msg = handle_packet_event(dst_chain.clone(), src_chain.clone(), event)?; // TODO add ICS height to IBC event let event_height = Height { @@ -130,8 +131,8 @@ impl Link { } if event_height > prev_height { send_update_client_and_msgs( - dst_chain, - src_chain, + dst_chain.clone(), + src_chain.clone(), &mut prev_msgs, &prev_height, self.client_of_chain(dst_chain.id()).unwrap(), @@ -145,7 +146,7 @@ impl Link { } Ok(send_update_client_and_msgs( - dst_chain, + dst_chain.clone(), src_chain, &mut prev_msgs, &prev_height, @@ -153,25 +154,24 @@ impl Link { )?) } - pub fn run( - &self, - a_chain: impl ChainHandle, - b_chain: impl ChainHandle, - ) -> Result<(), LinkError> { + pub fn run(&self) -> Result<(), LinkError> { info!("relaying packets for link {:#?}", self.channel.config); + let a_chain = self.channel.connection().chain_a(); + let b_chain = self.channel.connection().chain_b(); + let a_subscription = &a_chain.subscribe(a_chain.id())?; let b_subscription = &b_chain.subscribe(b_chain.id())?; loop { - self.relay_from_events(&a_chain, &b_chain, a_subscription)?; - self.relay_from_events(&b_chain, &a_chain, b_subscription)?; + self.relay_from_events(a_chain.clone(), b_chain.clone(), a_subscription)?; + self.relay_from_events(b_chain.clone(), a_chain.clone(), b_subscription)?; } } } fn handle_packet_event( - dst_chain: &impl ChainHandle, - src_chain: &impl ChainHandle, + dst_chain: Box, + src_chain: Box, event: &IBCEvent, ) -> Result, Error> { match event { @@ -186,8 +186,8 @@ fn handle_packet_event( } fn build_packet_recv_msg_from_send_event( - dst_chain: &impl ChainHandle, - src_chain: &impl ChainHandle, + dst_chain: Box, + src_chain: Box, event: &SendPacket, ) -> Result { let packet = Packet { @@ -239,8 +239,8 @@ fn build_packet_recv_msg_from_send_event( } fn build_packet_recv_msgs( - dst_chain: &impl ChainHandle, - src_chain: &impl ChainHandle, + dst_chain: Box, + src_chain: Box, src_channel_id: &ChannelId, src_port: &PortId, src_height: Height, @@ -274,7 +274,7 @@ fn build_packet_recv_msgs( let mut msgs = vec![]; for event in events.iter_mut() { event.set_height(query_height); - if let Some(new_msg) = handle_packet_event(dst_chain, src_chain, event)? { + if let Some(new_msg) = handle_packet_event(dst_chain.clone(), src_chain.clone(), event)? { msgs.append(&mut vec![new_msg]); } } @@ -291,8 +291,8 @@ pub struct PacketOptions { } fn target_height_and_sequences_of_recv_packets( - dst_chain: &impl ChainHandle, - src_chain: &impl ChainHandle, + dst_chain: Box, + src_chain: Box, opts: &PacketOptions, ) -> Result<(Vec, Height), Error> { let src_channel = src_chain @@ -372,20 +372,25 @@ fn target_height_and_sequences_of_recv_packets( } pub fn build_and_send_recv_packet_messages( - dst_chain: &impl ChainHandle, - src_chain: &impl ChainHandle, + dst_chain: Box, + src_chain: Box, opts: &PacketOptions, ) -> Result, Error> { let (sequences, height) = - target_height_and_sequences_of_recv_packets(dst_chain, src_chain, opts)?; + target_height_and_sequences_of_recv_packets(dst_chain.clone(), src_chain.clone(), opts)?; if sequences.is_empty() { return Ok(vec!["No sent packets on source chain".to_string()]); } - let mut msgs = build_update_client(dst_chain, src_chain, &opts.dst_client_id.clone(), height)?; + let mut msgs = build_update_client( + dst_chain.clone(), + src_chain.clone(), + &opts.dst_client_id.clone(), + height, + )?; let mut packet_msgs = build_packet_recv_msgs( - dst_chain, + dst_chain.clone(), src_chain, &opts.src_channel_id, &opts.src_port_id, diff --git a/relayer/src/relay.rs b/relayer/src/relay.rs index 674bac6176..45fe952a77 100644 --- a/relayer/src/relay.rs +++ b/relayer/src/relay.rs @@ -9,45 +9,34 @@ use crate::link::Link; pub(crate) const MAX_ITER: u32 = 10; pub fn channel_relay( - a_chain_handle: impl ChainHandle, - b_chain_handle: impl ChainHandle, + a_chain_handle: Box, + b_chain_handle: Box, conn_cfg: ConnectionConfig, chan_cfg: ChannelConfig, ) -> Result<(), BoxError> { // Instantiate the foreign client on the source chain. let client_on_a = ForeignClient::new( - &a_chain_handle, - &b_chain_handle, + a_chain_handle.clone(), + b_chain_handle.clone(), ForeignClientConfig::new(conn_cfg.a_end().chain_id(), conn_cfg.a_end().client_id()), )?; // Instantiate the foreign client on the destination chain. let client_on_b = ForeignClient::new( - &b_chain_handle, - &a_chain_handle, + b_chain_handle.clone(), + a_chain_handle.clone(), ForeignClientConfig::new(conn_cfg.b_end().chain_id(), conn_cfg.b_end().client_id()), )?; // Setup the connection between the two chains - let connection = Connection::new( - a_chain_handle.clone(), - b_chain_handle.clone(), - client_on_a, - client_on_b, - conn_cfg, - )?; + let connection = Connection::new(client_on_a, client_on_b, conn_cfg)?; // Setup the channel over the connection - let channel = Channel::new( - a_chain_handle.clone(), - b_chain_handle.clone(), - connection, - chan_cfg, - )?; + let channel = Channel::new(connection, chan_cfg)?; let link = Link::new(channel); - link.run(a_chain_handle, b_chain_handle)?; + link.run()?; Ok(()) }