penumbra_sdk_ibc/component/rpc/
connection_query.rs

1use async_trait::async_trait;
2
3use ibc_proto::ibc::core::client::v1::{Height, IdentifiedClientState};
4use ibc_proto::ibc::core::connection::v1::query_server::Query as ConnectionQuery;
5use ibc_proto::ibc::core::connection::v1::{
6    ConnectionEnd, QueryClientConnectionsRequest, QueryClientConnectionsResponse,
7    QueryConnectionClientStateRequest, QueryConnectionClientStateResponse,
8    QueryConnectionConsensusStateRequest, QueryConnectionConsensusStateResponse,
9    QueryConnectionRequest, QueryConnectionResponse, QueryConnectionsRequest,
10    QueryConnectionsResponse,
11};
12
13use ibc_types::core::client::ClientId;
14use ibc_types::core::connection::{ClientPaths, ConnectionId, IdentifiedConnectionEnd};
15use ibc_types::path::{
16    ClientConnectionPath, ClientConsensusStatePath, ClientStatePath, ConnectionPath,
17};
18use ibc_types::DomainType;
19use prost::Message;
20use std::str::FromStr;
21
22use crate::component::rpc::utils::determine_snapshot_from_metadata;
23use crate::component::{ConnectionStateReadExt, HostInterface};
24use crate::prefix::MerklePrefixExt;
25use crate::IBC_COMMITMENT_PREFIX;
26
27use super::IbcQuery;
28
29#[async_trait]
30impl<HI: HostInterface + Send + Sync + 'static> ConnectionQuery for IbcQuery<HI> {
31    /// Connection queries an IBC connection end.
32    #[tracing::instrument(skip(self), err, level = "debug")]
33    async fn connection(
34        &self,
35        request: tonic::Request<QueryConnectionRequest>,
36    ) -> std::result::Result<tonic::Response<QueryConnectionResponse>, tonic::Status> {
37        let snapshot = match determine_snapshot_from_metadata(self.storage.clone(), request.metadata()) {
38            Err(err) => return Err(tonic::Status::aborted(
39                format!("could not determine the correct snapshot to open given the `\"height\"` header of the request: {err:#}")
40            )),
41            Ok(snapshot) => snapshot,
42        };
43
44        let connection_id = &ConnectionId::from_str(&request.get_ref().connection_id)
45            .map_err(|e| tonic::Status::aborted(format!("invalid connection id: {e}")))?;
46
47        let (conn, proof) = snapshot
48            .get_with_proof(
49                IBC_COMMITMENT_PREFIX
50                    .apply_string(ConnectionPath::new(connection_id).to_string())
51                    .as_bytes()
52                    .to_vec(),
53            )
54            .await
55            .map(|res| {
56                let conn = res
57                    .0
58                    .map(|conn_bytes| ConnectionEnd::decode(conn_bytes.as_ref()))
59                    .transpose();
60
61                (conn, res.1)
62            })
63            .map_err(|e| tonic::Status::aborted(format!("couldn't get connection: {e}")))?;
64
65        let conn =
66            conn.map_err(|e| tonic::Status::aborted(format!("couldn't decode connection: {e}")))?;
67
68        let res = QueryConnectionResponse {
69            connection: conn,
70            proof: proof.encode_to_vec(),
71            proof_height: Some(ibc_proto::ibc::core::client::v1::Height {
72                revision_height: HI::get_block_height(&snapshot)
73                    .await
74                    .map_err(|e| tonic::Status::aborted(format!("couldn't decode height: {e}")))?
75                    + 1,
76                revision_number: HI::get_revision_number(&snapshot)
77                    .await
78                    .map_err(|e| tonic::Status::aborted(format!("couldn't decode height: {e}")))?,
79            }),
80        };
81
82        Ok(tonic::Response::new(res))
83    }
84
85    async fn connection_params(
86        &self,
87        _request: tonic::Request<
88            ibc_proto::ibc::core::connection::v1::QueryConnectionParamsRequest,
89        >,
90    ) -> std::result::Result<
91        tonic::Response<ibc_proto::ibc::core::connection::v1::QueryConnectionParamsResponse>,
92        tonic::Status,
93    > {
94        Err(tonic::Status::unimplemented("not implemented"))
95    }
96
97    /// Connections queries all the IBC connections of a chain.
98    #[tracing::instrument(skip(self), err, level = "debug")]
99    async fn connections(
100        &self,
101        _request: tonic::Request<QueryConnectionsRequest>,
102    ) -> std::result::Result<tonic::Response<QueryConnectionsResponse>, tonic::Status> {
103        let snapshot = self.storage.latest_snapshot();
104        let height = HI::get_block_height(&snapshot)
105            .await
106            .map_err(|e| tonic::Status::aborted(format!("couldn't decode height: {e}")))?
107            + 1;
108
109        let connection_counter = snapshot
110            .get_connection_counter()
111            .await
112            .map_err(|e| tonic::Status::aborted(format!("couldn't get connection counter: {e}")))?;
113
114        let mut connections = vec![];
115        for conn_idx in 0..connection_counter.0 {
116            let conn_id = ConnectionId(format!("connection-{}", conn_idx));
117            let connection = snapshot
118                .get_connection(&conn_id)
119                .await
120                .map_err(|e| {
121                    tonic::Status::aborted(format!("couldn't get connection {conn_id}: {e}"))
122                })?
123                .ok_or("unable to get connection")
124                .map_err(|e| {
125                    tonic::Status::aborted(format!("couldn't get connection {conn_id}: {e}"))
126                })?;
127            let id_conn = IdentifiedConnectionEnd {
128                connection_id: conn_id,
129                connection_end: connection,
130            };
131            connections.push(id_conn.into());
132        }
133
134        let height = Height {
135            revision_number: 0,
136            revision_height: height,
137        };
138
139        let res = QueryConnectionsResponse {
140            connections,
141            pagination: None,
142            height: Some(height),
143        };
144
145        Ok(tonic::Response::new(res))
146    }
147    /// ClientConnections queries the connection paths associated with a client
148    /// state.
149    #[tracing::instrument(skip(self), err, level = "debug")]
150    async fn client_connections(
151        &self,
152        request: tonic::Request<QueryClientConnectionsRequest>,
153    ) -> std::result::Result<tonic::Response<QueryClientConnectionsResponse>, tonic::Status> {
154        let snapshot = match determine_snapshot_from_metadata(self.storage.clone(), request.metadata()) {
155            Err(err) => return Err(tonic::Status::aborted(
156                format!("could not determine the correct snapshot to open given the `\"height\"` header of the request: {err:#}")
157            )),
158            Ok(snapshot) => snapshot,
159        };
160        let client_id = &ClientId::from_str(&request.get_ref().client_id)
161            .map_err(|e| tonic::Status::aborted(format!("invalid client id: {e}")))?;
162
163        let (client_connections, proof) = snapshot
164            .get_with_proof(
165                IBC_COMMITMENT_PREFIX
166                    .apply_string(ClientConnectionPath::new(client_id).to_string())
167                    .as_bytes()
168                    .to_vec(),
169            )
170            .await
171            .map_err(|e| tonic::Status::aborted(format!("couldn't get client connections: {e}")))?;
172
173        let connection_paths: Vec<String> = client_connections
174            .map(|client_connections| ClientPaths::decode(client_connections.as_ref()))
175            .transpose()
176            .map_err(|e| {
177                tonic::Status::aborted(format!("couldn't decode client connections: {e}"))
178            })?
179            .map(|client_paths| client_paths.paths)
180            .map(|paths| paths.into_iter().map(|path| path.to_string()).collect())
181            .unwrap_or_default();
182
183        Ok(tonic::Response::new(QueryClientConnectionsResponse {
184            connection_paths,
185            proof: proof.encode_to_vec(),
186            proof_height: Some(ibc_proto::ibc::core::client::v1::Height {
187                revision_height: HI::get_block_height(&snapshot)
188                    .await
189                    .map_err(|e| tonic::Status::aborted(format!("couldn't decode height: {e}")))?
190                    + 1,
191                revision_number: HI::get_revision_number(&snapshot)
192                    .await
193                    .map_err(|e| tonic::Status::aborted(format!("couldn't decode height: {e}")))?,
194            }),
195        }))
196    }
197    /// ConnectionClientState queries the client state associated with the
198    /// connection.
199    #[tracing::instrument(skip(self), err, level = "debug")]
200    async fn connection_client_state(
201        &self,
202        request: tonic::Request<QueryConnectionClientStateRequest>,
203    ) -> std::result::Result<tonic::Response<QueryConnectionClientStateResponse>, tonic::Status>
204    {
205        let snapshot = match determine_snapshot_from_metadata(self.storage.clone(), request.metadata()) {
206            Err(err) => return Err(tonic::Status::aborted(
207                format!("could not determine the correct snapshot to open given the `\"height\"` header of the request: {err:#}")
208            )),
209            Ok(snapshot) => snapshot,
210        };
211        let connection_id = &ConnectionId::from_str(&request.get_ref().connection_id)
212            .map_err(|e| tonic::Status::aborted(format!("invalid connection id: {e}")))?;
213
214        let client_id = snapshot
215            .get_connection(connection_id)
216            .await
217            .map_err(|e| tonic::Status::aborted(format!("couldn't get connection: {e}")))?
218            .ok_or("unable to get connection")
219            .map_err(|e| tonic::Status::aborted(format!("couldn't get connection: {e}")))?
220            .client_id;
221
222        let (client_state, proof) = snapshot
223            .get_with_proof(
224                IBC_COMMITMENT_PREFIX
225                    .apply_string(ClientStatePath::new(&client_id).to_string())
226                    .as_bytes()
227                    .to_vec(),
228            )
229            .await
230            .map_err(|e| tonic::Status::aborted(format!("couldn't get client state: {e}")))?;
231
232        let client_state_any = client_state
233            .map(|cs_bytes| ibc_proto::google::protobuf::Any::decode(cs_bytes.as_ref()))
234            .transpose()
235            .map_err(|e| tonic::Status::aborted(format!("couldn't decode client state: {e}")))?;
236
237        let identified_client_state = IdentifiedClientState {
238            client_id: client_id.clone().to_string(),
239            client_state: client_state_any,
240        };
241
242        Ok(tonic::Response::new(QueryConnectionClientStateResponse {
243            identified_client_state: Some(identified_client_state),
244            proof: proof.encode_to_vec(),
245            proof_height: Some(ibc_proto::ibc::core::client::v1::Height {
246                revision_height: HI::get_block_height(&snapshot)
247                    .await
248                    .map_err(|e| tonic::Status::aborted(format!("couldn't decode height: {e}")))?
249                    + 1,
250                revision_number: HI::get_revision_number(&snapshot)
251                    .await
252                    .map_err(|e| tonic::Status::aborted(format!("couldn't decode height: {e}")))?,
253            }),
254        }))
255    }
256    /// ConnectionConsensusState queries the consensus state associated with the
257    /// connection.
258    #[tracing::instrument(skip(self), err, level = "debug")]
259    async fn connection_consensus_state(
260        &self,
261        request: tonic::Request<QueryConnectionConsensusStateRequest>,
262    ) -> std::result::Result<tonic::Response<QueryConnectionConsensusStateResponse>, tonic::Status>
263    {
264        let snapshot = match determine_snapshot_from_metadata(self.storage.clone(), request.metadata()) {
265            Err(err) => return Err(tonic::Status::aborted(
266                format!("could not determine the correct snapshot to open given the `\"height\"` header of the request: {err:#}")
267            )),
268            Ok(snapshot) => snapshot,
269        };
270        let consensus_state_height = ibc_types::core::client::Height {
271            revision_number: request.get_ref().revision_number,
272            revision_height: request.get_ref().revision_height,
273        };
274        let connection_id = &ConnectionId::from_str(&request.get_ref().connection_id)
275            .map_err(|e| tonic::Status::aborted(format!("invalid connection id: {e}")))?;
276
277        let client_id = snapshot
278            .get_connection(connection_id)
279            .await
280            .map_err(|e| tonic::Status::aborted(format!("couldn't get connection: {e}")))?
281            .ok_or("unable to get connection")
282            .map_err(|e| tonic::Status::aborted(format!("couldn't get connection: {e}")))?
283            .client_id;
284
285        let (consensus_state, proof) = snapshot
286            .get_with_proof(
287                IBC_COMMITMENT_PREFIX
288                    .apply_string(
289                        ClientConsensusStatePath::new(&client_id, &consensus_state_height)
290                            .to_string(),
291                    )
292                    .as_bytes()
293                    .to_vec(),
294            )
295            .await
296            .map_err(|e| tonic::Status::aborted(format!("couldn't get client state: {e}")))?;
297
298        let consensus_state_any = consensus_state
299            .map(|cs_bytes| ibc_proto::google::protobuf::Any::decode(cs_bytes.as_ref()))
300            .transpose()
301            .map_err(|e| tonic::Status::aborted(format!("couldn't decode client state: {e}")))?;
302
303        Ok(tonic::Response::new(
304            QueryConnectionConsensusStateResponse {
305                consensus_state: consensus_state_any,
306                client_id: client_id.to_string(),
307                proof: proof.encode_to_vec(),
308                proof_height: Some(ibc_proto::ibc::core::client::v1::Height {
309                    revision_height: HI::get_block_height(&snapshot).await.map_err(|e| {
310                        tonic::Status::aborted(format!("couldn't decode height: {e}"))
311                    })? + 1,
312                    revision_number: HI::get_revision_number(&snapshot).await.map_err(|e| {
313                        tonic::Status::aborted(format!("couldn't decode height: {e}"))
314                    })?,
315                }),
316            },
317        ))
318    }
319}