penumbra_sdk_ibc/component/rpc/
connection_query.rs1use 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 #[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 #[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 #[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 #[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 #[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}