1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
//! Various utilities for the Hermes CLI

use alloc::sync::Arc;
use eyre::eyre;
use tokio::runtime::Runtime as TokioRuntime;
use tracing::debug;

use ibc_relayer::chain::counterparty::{channel_connection_client, ChannelConnectionClient};
use ibc_relayer::chain::handle::{BaseChainHandle, ChainHandle};
use ibc_relayer::chain::requests::{
    IncludeProof, QueryChannelRequest, QueryClientStateRequest, QueryConnectionRequest, QueryHeight,
};
use ibc_relayer::config::Config;
use ibc_relayer::spawn;
use ibc_relayer_types::core::ics24_host::identifier::{ChainId, ChannelId, PortId};

use crate::error::Error;

#[derive(Clone, Debug)]
/// Pair of chain handles that are used by most CLIs.
pub struct ChainHandlePair<Chain: ChainHandle = BaseChainHandle> {
    /// Source chain handle
    pub src: Chain,
    /// Destination chain handle
    pub dst: Chain,
}

impl<Chain: ChainHandle> ChainHandlePair<Chain> {
    /// Spawn the source and destination chain runtime from the configuration and chain identifiers,
    /// and return the pair of associated handles.
    pub fn spawn_generic(
        config: &Config,
        src_chain_id: &ChainId,
        dst_chain_id: &ChainId,
    ) -> Result<Self, Error> {
        let src = spawn_chain_runtime_generic(config, src_chain_id)?;
        let dst = spawn_chain_runtime_generic(config, dst_chain_id)?;

        Ok(ChainHandlePair { src, dst })
    }
}

impl ChainHandlePair<BaseChainHandle> {
    pub fn spawn(
        config: &Config,
        src_chain_id: &ChainId,
        dst_chain_id: &ChainId,
    ) -> Result<Self, Error> {
        Self::spawn_generic(config, src_chain_id, dst_chain_id)
    }
}

/// Spawns a chain runtime for the chain in the configuration identified by given a chain identifier.
///
/// This function will use the default [`ChainHandle`] implementation, ie. the [`BaseChainHandle`].
///
/// Returns the corresponding handle if successful.
pub fn spawn_chain_runtime(config: &Config, chain_id: &ChainId) -> Result<impl ChainHandle, Error> {
    spawn_chain_runtime_generic::<BaseChainHandle>(config, chain_id)
}

/// Spawns a chain runtime for the chain in the configuraiton identified by the given chain identifier.
///
/// The `Handle` type parameter allows choosing which kind of [`ChainHandle`] implementation to use.
///
/// Returns the corresponding handle if successful.
pub fn spawn_chain_runtime_generic<Handle: ChainHandle>(
    config: &Config,
    chain_id: &ChainId,
) -> Result<Handle, Error> {
    let rt = Arc::new(TokioRuntime::new().unwrap());
    spawn::spawn_chain_runtime(config, chain_id, rt).map_err(Error::spawn)
}

/// Spawns a chain runtime for specified chain identifier, queries the counterparty chain associated
/// with specified port and channel id, and spawns a chain runtime for the counterparty chain.
///
/// The `Handle` type parameter allows choosing which kind of `ChainHandle` implementation to use.
///
/// Returns a tuple with a pair of associated chain handles and the ChannelEnd
pub fn spawn_chain_counterparty<Chain: ChainHandle>(
    config: &Config,
    chain_id: &ChainId,
    port_id: &PortId,
    channel_id: &ChannelId,
) -> Result<(ChainHandlePair<Chain>, ChannelConnectionClient), Error> {
    let chain = spawn_chain_runtime_generic::<Chain>(config, chain_id)?;
    let channel_connection_client =
        channel_connection_client(&chain, port_id, channel_id).map_err(Error::supervisor)?;
    let counterparty_chain = {
        let counterparty_chain_id = channel_connection_client.client.client_state.chain_id();
        spawn_chain_runtime_generic::<Chain>(config, &counterparty_chain_id)?
    };

    Ok((
        ChainHandlePair {
            src: chain,
            dst: counterparty_chain,
        },
        channel_connection_client,
    ))
}

/// Check that the relayer can send on the given channel and ensure that channels and chain identifiers match.
/// To do this, fetch from the source chain the channel end, then the associated connection
/// end, and then the underlying client state; finally, check that this client is verifying
/// headers for the destination chain.
pub fn check_can_send_on_channel<Chain: ChainHandle>(
    src_chain: &Chain,
    src_channel_id: &ChannelId,
    src_port_id: &PortId,
    dst_chain_id: &ChainId,
) -> Result<(), eyre::Report> {
    // Fetch from the source chain the channel end and check that it is open.
    let (channel_end_src, _) = src_chain.query_channel(
        QueryChannelRequest {
            port_id: src_port_id.clone(),
            channel_id: src_channel_id.clone(),
            height: QueryHeight::Latest,
        },
        IncludeProof::No,
    )?;

    if !channel_end_src.is_open() {
        return Err(eyre!(
            "the requested port/channel ('{}'/'{}') on chain id '{}' is in state '{}'; expected 'open' state",
            src_port_id,
            src_channel_id,
            src_chain.id(),
            channel_end_src.state
        ));
    }

    let conn_id = match channel_end_src.connection_hops.first() {
        Some(cid) => cid,
        None => {
            return Err(eyre!(
                "could not retrieve the connection hop underlying port/channel '{}'/'{}' on chain '{}'",
                src_port_id, src_channel_id, src_chain.id()
            ));
        }
    };

    // Fetch the associated connection end.
    let (conn_end, _) = src_chain.query_connection(
        QueryConnectionRequest {
            connection_id: conn_id.clone(),
            height: QueryHeight::Latest,
        },
        IncludeProof::No,
    )?;

    debug!("connection hop underlying the channel: {:?}", conn_end);

    // Fetch the underlying client state.
    let (src_chain_client_state, _) = src_chain.query_client_state(
        QueryClientStateRequest {
            client_id: conn_end.client_id().clone(),
            height: QueryHeight::Latest,
        },
        IncludeProof::No,
    )?;

    debug!(
        "client state underlying the channel: {:?}",
        src_chain_client_state
    );

    // Check that this client is verifying headers for the destination chain.
    if &src_chain_client_state.chain_id() != dst_chain_id {
        return Err(eyre!(
            "the requested port/channel ('{}'/'{}') provides a path from chain '{}' to \
             chain '{}' (not to the destination chain '{}'). Bailing due to mismatching arguments.",
            src_port_id,
            src_channel_id,
            src_chain.id(),
            src_chain_client_state.chain_id(),
            dst_chain_id
        ));
    }

    Ok(())
}