penumbra_sdk_ibc/component/rpc/
client_query.rs1use async_trait::async_trait;
2
3use ibc_proto::ibc::core::client::v1::query_server::Query as ClientQuery;
4use ibc_proto::ibc::core::client::v1::{
5 ConsensusStateWithHeight, IdentifiedClientState, QueryClientParamsRequest,
6 QueryClientParamsResponse, QueryClientStateRequest, QueryClientStateResponse,
7 QueryClientStatesRequest, QueryClientStatesResponse, QueryClientStatusRequest,
8 QueryClientStatusResponse, QueryConsensusStateHeightsRequest,
9 QueryConsensusStateHeightsResponse, QueryConsensusStateRequest, QueryConsensusStateResponse,
10 QueryConsensusStatesRequest, QueryConsensusStatesResponse, QueryUpgradedClientStateRequest,
11 QueryUpgradedClientStateResponse, QueryUpgradedConsensusStateRequest,
12 QueryUpgradedConsensusStateResponse,
13};
14use prost::Message;
15
16use ibc_types::core::client::ClientId;
17use ibc_types::core::client::Height;
18use ibc_types::path::ClientConsensusStatePath;
19use ibc_types::path::ClientStatePath;
20use ibc_types::DomainType;
21
22use std::str::FromStr;
23use tonic::{Response, Status};
24
25use crate::component::{ClientStateReadExt, HostInterface};
26use crate::prefix::MerklePrefixExt;
27use crate::IBC_COMMITMENT_PREFIX;
28
29use super::utils::determine_snapshot_from_metadata;
30use super::IbcQuery;
31
32#[async_trait]
33impl<HI: HostInterface + Send + Sync + 'static> ClientQuery for IbcQuery<HI> {
34 async fn client_state(
35 &self,
36 request: tonic::Request<QueryClientStateRequest>,
37 ) -> std::result::Result<Response<QueryClientStateResponse>, Status> {
38 let snapshot = match determine_snapshot_from_metadata(self.storage.clone(), request.metadata()) {
39 Err(err) => return Err(tonic::Status::aborted(
40 format!("could not determine the correct snapshot to open given the `\"height\"` header of the request: {err:#}")
41 )),
42 Ok(snapshot) => snapshot,
43 };
44
45 let client_id = ClientId::from_str(&request.get_ref().client_id)
46 .map_err(|e| tonic::Status::invalid_argument(format!("invalid client id: {e}")))?;
47
48 let (cs_opt, proof) = snapshot
50 .get_with_proof(
51 IBC_COMMITMENT_PREFIX
52 .apply_string(ClientStatePath(client_id.clone()).to_string())
53 .as_bytes()
54 .to_vec(),
55 )
56 .await
57 .map_err(|e| tonic::Status::aborted(format!("couldn't get client: {e}")))?;
58
59 let client_state = cs_opt
60 .map(|cs_opt| ibc_proto::google::protobuf::Any::decode(cs_opt.as_ref()))
61 .transpose()
62 .map_err(|e| tonic::Status::aborted(format!("couldn't decode client state: {e}")))?;
63
64 let res = QueryClientStateResponse {
65 client_state,
66 proof: proof.encode_to_vec(),
67 proof_height: Some(ibc_proto::ibc::core::client::v1::Height {
68 revision_height: HI::get_block_height(&snapshot)
69 .await
70 .map_err(|e| tonic::Status::aborted(format!("couldn't decode height: {e}")))?
71 + 1,
72 revision_number: HI::get_revision_number(&snapshot)
73 .await
74 .map_err(|e| tonic::Status::aborted(format!("couldn't decode height: {e}")))?,
75 }),
76 };
77
78 Ok(tonic::Response::new(res))
79 }
80
81 async fn client_states(
83 &self,
84 _request: tonic::Request<QueryClientStatesRequest>,
85 ) -> std::result::Result<tonic::Response<QueryClientStatesResponse>, tonic::Status> {
86 let snapshot = self.storage.latest_snapshot();
87
88 let client_counter = snapshot
89 .client_counter()
90 .await
91 .map_err(|e| tonic::Status::aborted(format!("couldn't get client counter: {e}")))?
92 .0;
93
94 let mut client_states = vec![];
95 for client_idx in 0..client_counter {
96 let client_id = ClientId::from_str(format!("07-tendermint-{}", client_idx).as_str())
98 .map_err(|e| tonic::Status::aborted(format!("invalid client id: {e}")))?;
99 let client_state = snapshot.get_client_state(&client_id).await;
100 let id_client = IdentifiedClientState {
101 client_id: client_id.to_string(),
102 client_state: client_state.ok().map(|state| state.into()), };
104 client_states.push(id_client);
105 }
106
107 let res = QueryClientStatesResponse {
108 client_states,
109 pagination: None,
110 };
111
112 Ok(tonic::Response::new(res))
113 }
114
115 async fn consensus_state(
118 &self,
119 request: tonic::Request<QueryConsensusStateRequest>,
120 ) -> std::result::Result<tonic::Response<QueryConsensusStateResponse>, tonic::Status> {
121 let snapshot = match determine_snapshot_from_metadata(self.storage.clone(), request.metadata()) {
122 Err(err) => return Err(tonic::Status::aborted(
123 format!("could not determine the correct snapshot to open given the `\"height\"` header of the request: {err:#}")
124 )),
125 Ok(snapshot) => snapshot,
126 };
127
128 let client_id = ClientId::from_str(&request.get_ref().client_id)
129 .map_err(|e| tonic::Status::invalid_argument(format!("invalid client id: {e}")))?;
130 let height = if request.get_ref().latest_height {
131 get_latest_verified_height(&snapshot, &client_id).await?
132 } else {
133 Height {
134 revision_number: request.get_ref().revision_number,
135 revision_height: request.get_ref().revision_height,
136 }
137 };
138
139 let (cs_opt, proof) = snapshot
140 .get_with_proof(
141 IBC_COMMITMENT_PREFIX
142 .apply_string(ClientConsensusStatePath::new(&client_id, &height).to_string())
143 .as_bytes()
144 .to_vec(),
145 )
146 .await
147 .map_err(|e| tonic::Status::aborted(format!("couldn't get consensus state: {e}")))?;
148
149 let consensus_state = cs_opt
150 .map(|cs_opt| ibc_proto::google::protobuf::Any::decode(cs_opt.as_ref()))
151 .transpose()
152 .map_err(|e| tonic::Status::aborted(format!("couldn't decode consensus state: {e}")))?;
153
154 let res = QueryConsensusStateResponse {
155 consensus_state,
156 proof: proof.encode_to_vec(),
157 proof_height: Some(ibc_proto::ibc::core::client::v1::Height {
158 revision_height: HI::get_block_height(&snapshot)
159 .await
160 .map_err(|e| tonic::Status::aborted(format!("couldn't decode height: {e}")))?
161 + 1,
162 revision_number: HI::get_revision_number(&snapshot)
163 .await
164 .map_err(|e| tonic::Status::aborted(format!("couldn't decode height: {e}")))?,
165 }),
166 };
167
168 Ok(tonic::Response::new(res))
169 }
170
171 async fn consensus_states(
174 &self,
175 request: tonic::Request<QueryConsensusStatesRequest>,
176 ) -> std::result::Result<tonic::Response<QueryConsensusStatesResponse>, tonic::Status> {
177 let snapshot = self.storage.latest_snapshot();
178 let client_id = ClientId::from_str(&request.get_ref().client_id)
179 .map_err(|e| tonic::Status::invalid_argument(format!("invalid client id: {e}")))?;
180
181 let verified_heights = snapshot
182 .get_verified_heights(&client_id)
183 .await
184 .map_err(|e| tonic::Status::aborted(format!("couldn't get verified heights: {e}")))?;
185 let resp = if let Some(verified_heights) = verified_heights {
186 let mut consensus_states = Vec::with_capacity(verified_heights.heights.len());
187 for height in verified_heights.heights {
188 let consensus_state = snapshot
189 .get_verified_consensus_state(&height, &client_id)
190 .await
191 .map_err(|e| {
192 tonic::Status::aborted(format!("couldn't get consensus state: {e}"))
193 })?;
194 consensus_states.push(ConsensusStateWithHeight {
195 height: Some(height.into()),
196 consensus_state: Some(consensus_state.into()),
197 });
198 }
199 QueryConsensusStatesResponse {
200 consensus_states,
201 pagination: None,
202 }
203 } else {
204 QueryConsensusStatesResponse {
205 consensus_states: vec![],
206 pagination: None,
207 }
208 };
209
210 Ok(tonic::Response::new(resp))
211 }
212
213 async fn consensus_state_heights(
215 &self,
216 request: tonic::Request<QueryConsensusStateHeightsRequest>,
217 ) -> std::result::Result<tonic::Response<QueryConsensusStateHeightsResponse>, tonic::Status>
218 {
219 let snapshot = self.storage.latest_snapshot();
220 let client_id = ClientId::from_str(&request.get_ref().client_id)
221 .map_err(|e| tonic::Status::invalid_argument(format!("invalid client id: {e}")))?;
222
223 let verified_heights = snapshot
224 .get_verified_heights(&client_id)
225 .await
226 .map_err(|e| tonic::Status::aborted(format!("couldn't get verified heights: {e}")))?;
227 let resp = if let Some(verified_heights) = verified_heights {
228 QueryConsensusStateHeightsResponse {
229 consensus_state_heights: verified_heights
230 .heights
231 .into_iter()
232 .map(|h| h.into())
233 .collect(),
234 pagination: None,
235 }
236 } else {
237 QueryConsensusStateHeightsResponse {
238 consensus_state_heights: vec![],
239 pagination: None,
240 }
241 };
242
243 Ok(tonic::Response::new(resp))
244 }
245
246 async fn client_status(
248 &self,
249 request: tonic::Request<QueryClientStatusRequest>,
250 ) -> std::result::Result<tonic::Response<QueryClientStatusResponse>, tonic::Status> {
251 let snapshot = self.storage.latest_snapshot();
252 let client_id = ClientId::from_str(&request.get_ref().client_id)
253 .map_err(|e| tonic::Status::invalid_argument(format!("invalid client id: {e}")))?;
254 let timestamp = HI::get_block_timestamp(snapshot.clone())
255 .await
256 .map_err(|e| tonic::Status::aborted(format!("couldn't get block timestamp: {e}")))?;
257 let client_status = snapshot.get_client_status(&client_id, timestamp).await;
258 let resp = QueryClientStatusResponse {
259 status: client_status.to_string(),
260 };
261
262 Ok(tonic::Response::new(resp))
263 }
264 async fn client_params(
266 &self,
267 _request: tonic::Request<QueryClientParamsRequest>,
268 ) -> std::result::Result<tonic::Response<QueryClientParamsResponse>, tonic::Status> {
269 Err(tonic::Status::unimplemented("not implemented"))
270 }
271 async fn upgraded_client_state(
273 &self,
274 _request: tonic::Request<QueryUpgradedClientStateRequest>,
275 ) -> std::result::Result<tonic::Response<QueryUpgradedClientStateResponse>, tonic::Status> {
276 Err(tonic::Status::unimplemented("not implemented"))
277 }
278 async fn upgraded_consensus_state(
280 &self,
281 _request: tonic::Request<QueryUpgradedConsensusStateRequest>,
282 ) -> std::result::Result<tonic::Response<QueryUpgradedConsensusStateResponse>, tonic::Status>
283 {
284 Err(tonic::Status::unimplemented("not implemented"))
285 }
286}
287
288async fn get_latest_verified_height(
289 snapshot: &cnidarium::Snapshot,
290 client_id: &ClientId,
291) -> Result<Height, tonic::Status> {
292 let verified_heights = snapshot
293 .get_verified_heights(&client_id)
294 .await
295 .map_err(|e| tonic::Status::aborted(format!("couldn't get verified heights: {e}")))?;
296
297 let Some(mut verified_heights) = verified_heights else {
298 return Err(tonic::Status::not_found(
299 "couldn't find verified heights for client: {client_id}",
300 ));
301 };
302
303 verified_heights.heights.sort();
304 if verified_heights.heights.is_empty() {
305 return Err(tonic::Status::not_found(
306 "verified heights for client were empty: {client_id}",
307 ));
308 }
309
310 Ok(verified_heights.heights.pop().expect("must be non-empty"))
311}