ibc_relayer_types/core/ics03_connection/msgs/
conn_open_ack.rs

1use ibc_proto::google::protobuf::Any;
2use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenAck as RawMsgConnectionOpenAck;
3use ibc_proto::Protobuf;
4
5use crate::core::ics03_connection::error::Error;
6use crate::core::ics03_connection::version::Version;
7use crate::core::ics23_commitment::commitment::CommitmentProofBytes;
8use crate::core::ics24_host::identifier::ConnectionId;
9use crate::proofs::{ConsensusProof, Proofs};
10use crate::signer::Signer;
11use crate::tx_msg::Msg;
12use crate::Height;
13
14pub const TYPE_URL: &str = "/ibc.core.connection.v1.MsgConnectionOpenAck";
15
16/// Message definition `MsgConnectionOpenAck`  (i.e., `ConnOpenAck` datagram).
17#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct MsgConnectionOpenAck {
19    pub connection_id: ConnectionId,
20    pub counterparty_connection_id: ConnectionId,
21    pub client_state: Option<Any>,
22    pub proofs: Proofs,
23    pub version: Version,
24    pub signer: Signer,
25}
26
27impl MsgConnectionOpenAck {
28    /// Getter for accessing the `consensus_height` field from this message.
29    /// Returns `None` if this field is not set.
30    pub fn consensus_height(&self) -> Option<Height> {
31        self.proofs.consensus_proof().map(|proof| proof.height())
32    }
33}
34
35impl Msg for MsgConnectionOpenAck {
36    type ValidationError = Error;
37    type Raw = RawMsgConnectionOpenAck;
38
39    fn route(&self) -> String {
40        crate::keys::ROUTER_KEY.to_string()
41    }
42
43    fn type_url(&self) -> String {
44        TYPE_URL.to_string()
45    }
46}
47
48impl Protobuf<RawMsgConnectionOpenAck> for MsgConnectionOpenAck {}
49
50impl TryFrom<RawMsgConnectionOpenAck> for MsgConnectionOpenAck {
51    type Error = Error;
52
53    fn try_from(msg: RawMsgConnectionOpenAck) -> Result<Self, Self::Error> {
54        let consensus_height = msg
55            .consensus_height
56            .and_then(|raw_height| raw_height.try_into().ok())
57            .ok_or_else(Error::missing_consensus_height)?;
58
59        let consensus_proof = ConsensusProof::new(
60            msg.proof_consensus
61                .try_into()
62                .map_err(Error::invalid_proof)?,
63            consensus_height,
64        )
65        .map_err(Error::invalid_proof)?;
66
67        let proof_height = msg
68            .proof_height
69            .and_then(|raw_height| raw_height.try_into().ok())
70            .ok_or_else(Error::missing_proof_height)?;
71
72        let client_proof =
73            CommitmentProofBytes::try_from(msg.proof_client).map_err(Error::invalid_proof)?;
74
75        // Host consensus state proof can be missing for IBC-Go < 7.2.0
76        let consensus_state_proof =
77            CommitmentProofBytes::try_from(msg.host_consensus_state_proof).ok();
78
79        Ok(Self {
80            connection_id: msg
81                .connection_id
82                .parse()
83                .map_err(Error::invalid_identifier)?,
84            counterparty_connection_id: msg
85                .counterparty_connection_id
86                .parse()
87                .map_err(Error::invalid_identifier)?,
88            client_state: msg.client_state,
89            version: msg.version.ok_or_else(Error::empty_versions)?.try_into()?,
90            proofs: Proofs::new(
91                msg.proof_try.try_into().map_err(Error::invalid_proof)?,
92                Some(client_proof),
93                Some(consensus_proof),
94                consensus_state_proof,
95                None,
96                proof_height,
97            )
98            .map_err(Error::invalid_proof)?,
99            signer: msg.signer.parse().map_err(Error::signer)?,
100        })
101    }
102}
103
104impl From<MsgConnectionOpenAck> for RawMsgConnectionOpenAck {
105    fn from(ics_msg: MsgConnectionOpenAck) -> Self {
106        RawMsgConnectionOpenAck {
107            connection_id: ics_msg.connection_id.as_str().to_string(),
108            counterparty_connection_id: ics_msg.counterparty_connection_id.as_str().to_string(),
109            client_state: ics_msg.client_state,
110            proof_height: Some(ics_msg.proofs.height().into()),
111            proof_try: ics_msg.proofs.object_proof().clone().into(),
112            proof_client: ics_msg
113                .proofs
114                .client_proof()
115                .map_or_else(Vec::new, |v| v.to_bytes()),
116            proof_consensus: ics_msg
117                .proofs
118                .consensus_proof()
119                .map_or_else(Vec::new, |v| v.proof().to_bytes()),
120            consensus_height: ics_msg
121                .proofs
122                .consensus_proof()
123                .map_or_else(|| None, |h| Some(h.height().into())),
124            host_consensus_state_proof: ics_msg
125                .proofs
126                .host_consensus_state_proof()
127                .map_or_else(Vec::new, |v| v.to_bytes()),
128            version: Some(ics_msg.version.into()),
129            signer: ics_msg.signer.to_string(),
130        }
131    }
132}
133
134#[cfg(test)]
135pub mod test_util {
136
137    use ibc_proto::ibc::core::client::v1::Height;
138    use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenAck as RawMsgConnectionOpenAck;
139
140    use crate::core::ics03_connection::version::Version;
141    use crate::core::ics24_host::identifier::ConnectionId;
142    use crate::test_utils::{get_dummy_bech32_account, get_dummy_proof};
143
144    pub fn get_dummy_raw_msg_conn_open_ack(
145        proof_height: u64,
146        consensus_height: u64,
147    ) -> RawMsgConnectionOpenAck {
148        RawMsgConnectionOpenAck {
149            connection_id: ConnectionId::new(0).to_string(),
150            counterparty_connection_id: ConnectionId::new(1).to_string(),
151            proof_try: get_dummy_proof(),
152            proof_height: Some(Height {
153                revision_number: 0,
154                revision_height: proof_height,
155            }),
156            proof_consensus: get_dummy_proof(),
157            host_consensus_state_proof: get_dummy_proof(),
158            consensus_height: Some(Height {
159                revision_number: 0,
160                revision_height: consensus_height,
161            }),
162            client_state: None,
163            proof_client: get_dummy_proof(),
164            version: Some(Version::default().into()),
165            signer: get_dummy_bech32_account(),
166        }
167    }
168}
169
170#[cfg(test)]
171mod tests {
172
173    use test_log::test;
174
175    use ibc_proto::ibc::core::client::v1::Height;
176    use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenAck as RawMsgConnectionOpenAck;
177
178    use crate::core::ics03_connection::msgs::conn_open_ack::test_util::get_dummy_raw_msg_conn_open_ack;
179    use crate::core::ics03_connection::msgs::conn_open_ack::MsgConnectionOpenAck;
180
181    #[test]
182    fn parse_connection_open_ack_msg() {
183        #[derive(Clone, Debug, PartialEq)]
184        struct Test {
185            name: String,
186            raw: RawMsgConnectionOpenAck,
187            want_pass: bool,
188        }
189
190        let default_ack_msg = get_dummy_raw_msg_conn_open_ack(5, 5);
191
192        let tests: Vec<Test> = vec![
193            Test {
194                name: "Good parameters".to_string(),
195                raw: default_ack_msg.clone(),
196                want_pass: true,
197            },
198            Test {
199                name: "Bad connection id, non-alpha".to_string(),
200                raw: RawMsgConnectionOpenAck {
201                    connection_id: "con007".to_string(),
202                    ..default_ack_msg.clone()
203                },
204                want_pass: false,
205            },
206            Test {
207                name: "Bad version, missing version".to_string(),
208                raw: RawMsgConnectionOpenAck {
209                    version: None,
210                    ..default_ack_msg.clone()
211                },
212                want_pass: false,
213            },
214            Test {
215                name: "Bad proof height, height is 0".to_string(),
216                raw: RawMsgConnectionOpenAck {
217                    proof_height: Some(Height {
218                        revision_number: 1,
219                        revision_height: 0,
220                    }),
221                    ..default_ack_msg.clone()
222                },
223                want_pass: false,
224            },
225            Test {
226                name: "Bad consensus height, height is 0".to_string(),
227                raw: RawMsgConnectionOpenAck {
228                    consensus_height: Some(Height {
229                        revision_number: 1,
230                        revision_height: 0,
231                    }),
232                    ..default_ack_msg
233                },
234                want_pass: false,
235            },
236        ]
237        .into_iter()
238        .collect();
239
240        for test in tests {
241            let msg = MsgConnectionOpenAck::try_from(test.raw.clone());
242
243            assert_eq!(
244                test.want_pass,
245                msg.is_ok(),
246                "MsgConnOpenAck::new failed for test {}, \nmsg {:?} with error {:?}",
247                test.name,
248                test.raw,
249                msg.err(),
250            );
251        }
252    }
253
254    #[test]
255    fn to_and_from() {
256        let raw = get_dummy_raw_msg_conn_open_ack(5, 6);
257        let msg = MsgConnectionOpenAck::try_from(raw.clone()).unwrap();
258        let raw_back = RawMsgConnectionOpenAck::from(msg.clone());
259        let msg_back = MsgConnectionOpenAck::try_from(raw_back.clone()).unwrap();
260        assert_eq!(raw, raw_back);
261        assert_eq!(msg, msg_back);
262    }
263}