1use tracing::instrument;
3
4use crate::{
5 ibc_types::{IbcChannelId, IbcClientId, IbcConnectionId, IbcPortId},
6 prelude::*,
7};
8
9use super::{
10 abci::{AbciProofKind, AbciProofReq},
11 basic::{BlockHeaderReq, BlockHeightReq, StakingParamsReq},
12};
13
14impl QueryClient {
15 #[instrument]
16 pub async fn ibc_connection_proofs(
17 &self,
18 proof_height: layer_climb_proto::RevisionHeight,
19 client_id: &IbcClientId,
20 connection_id: &IbcConnectionId,
21 ) -> Result<IbcConnectionProofs> {
22 self.run_with_middleware(IbcConnectionProofsReq {
23 proof_height,
24 client_id: client_id.clone(),
25 connection_id: connection_id.clone(),
26 })
27 .await
28 }
29
30 #[instrument]
31 pub async fn ibc_channel_proofs(
32 &self,
33 proof_height: layer_climb_proto::RevisionHeight,
34 channel_id: &IbcChannelId,
35 port_id: &IbcPortId,
36 ) -> Result<IbcChannelProofs> {
37 self.run_with_middleware(IbcChannelProofsReq {
38 proof_height,
39 channel_id: channel_id.clone(),
40 port_id: port_id.clone(),
41 })
42 .await
43 }
44
45 #[instrument]
46 pub async fn ibc_client_state(
47 &self,
48 ibc_client_id: &IbcClientId,
49 height: Option<u64>,
50 ) -> Result<layer_climb_proto::ibc::light_client::ClientState> {
51 self.run_with_middleware(IbcClientStateReq {
52 ibc_client_id: ibc_client_id.clone(),
53 height,
54 })
55 .await
56 }
57
58 #[instrument]
59 pub async fn ibc_connection(
60 &self,
61 connection_id: &IbcConnectionId,
62 height: Option<u64>,
63 ) -> Result<layer_climb_proto::ibc::connection::ConnectionEnd> {
64 self.run_with_middleware(IbcConnectionReq {
65 connection_id: connection_id.clone(),
66 height,
67 })
68 .await
69 }
70
71 #[instrument]
72 pub async fn ibc_connection_consensus_state(
73 &self,
74 connection_id: &IbcConnectionId,
75 consensus_height: Option<layer_climb_proto::RevisionHeight>,
76 height: Option<u64>,
77 ) -> Result<layer_climb_proto::Any> {
78 self.run_with_middleware(IbcConnectionConsensusStateReq {
79 connection_id: connection_id.clone(),
80 consensus_height,
81 height,
82 })
83 .await
84 }
85
86 #[instrument]
87 pub async fn ibc_channel(
88 &self,
89 channel_id: &IbcChannelId,
90 port_id: &IbcPortId,
91 height: Option<u64>,
92 ) -> Result<layer_climb_proto::ibc::channel::Channel> {
93 self.run_with_middleware(IbcChannelReq {
94 channel_id: channel_id.clone(),
95 port_id: port_id.clone(),
96 height,
97 })
98 .await
99 }
100
101 #[instrument]
102 pub async fn ibc_create_client_consensus_state(
103 &self,
104 trusting_period_secs: Option<u64>,
105 ) -> Result<(
106 layer_climb_proto::ibc::light_client::ClientState,
107 layer_climb_proto::ibc::light_client::ConsensusState,
108 )> {
109 self.run_with_middleware(IbcCreateClientConsensusStateReq {
110 trusting_period_secs,
111 })
112 .await
113 }
114}
115
116#[derive(Clone, Debug)]
117struct IbcConnectionProofsReq {
118 pub proof_height: layer_climb_proto::RevisionHeight,
119 pub client_id: IbcClientId,
120 pub connection_id: IbcConnectionId,
121}
122
123impl QueryRequest for IbcConnectionProofsReq {
124 type QueryResponse = IbcConnectionProofs;
125
126 async fn request(&self, client: QueryClient) -> Result<IbcConnectionProofs> {
127 let IbcConnectionProofsReq {
128 proof_height,
129 client_id,
130 connection_id,
131 } = self;
132
133 let query_height = proof_height.revision_height - 1;
134
135 let connection = IbcConnectionReq {
136 connection_id: connection_id.clone(),
137 height: Some(query_height),
138 }
139 .request(client.clone())
140 .await?;
141 let connection_proof = AbciProofReq {
142 kind: AbciProofKind::IbcConnection {
143 connection_id: connection_id.clone(),
144 },
145 height: Some(query_height),
146 }
147 .request(client.clone())
148 .await?
149 .proof;
150
151 let client_state = IbcClientStateReq {
152 ibc_client_id: client_id.clone(),
153 height: Some(query_height),
154 }
155 .request(client.clone())
156 .await?;
157 let client_state_proof = AbciProofReq {
158 kind: AbciProofKind::IbcClientState {
159 client_id: client_id.clone(),
160 },
161 height: Some(query_height),
162 }
163 .request(client.clone())
164 .await?
165 .proof;
166
167 let consensus_height = *client_state
168 .latest_height
169 .as_ref()
170 .context("missing client state latest height")?;
171
172 let consensus_proof = AbciProofReq {
173 kind: AbciProofKind::IbcConsensus {
174 client_id: client_id.clone(),
175 height: consensus_height,
176 },
177 height: Some(query_height),
178 }
179 .request(client.clone())
180 .await?
181 .proof;
182
183 if client_state_proof.is_empty() {
184 bail!("missing client state proof");
185 }
186 if connection_proof.is_empty() {
187 bail!("missing connection proof");
188 }
189 if consensus_proof.is_empty() {
190 bail!("missing consensus proof");
191 }
192
193 Ok(IbcConnectionProofs {
194 proof_height: *proof_height,
195 consensus_height,
196 query_height,
197 connection,
198 connection_proof,
199 client_state_proof,
200 consensus_proof,
201 client_state,
202 })
203 }
204}
205
206#[derive(Clone, Debug)]
207struct IbcChannelProofsReq {
208 pub proof_height: layer_climb_proto::RevisionHeight,
209 pub channel_id: IbcChannelId,
210 pub port_id: IbcPortId,
211}
212
213impl QueryRequest for IbcChannelProofsReq {
214 type QueryResponse = IbcChannelProofs;
215
216 async fn request(&self, client: QueryClient) -> Result<IbcChannelProofs> {
217 let IbcChannelProofsReq {
218 proof_height,
219 channel_id,
220 port_id,
221 } = self;
222
223 let query_height = proof_height.revision_height - 1;
224
225 let channel = IbcChannelReq {
226 channel_id: channel_id.clone(),
227 port_id: port_id.clone(),
228 height: Some(query_height),
229 }
230 .request(client.clone())
231 .await?;
232 let channel_proof = AbciProofReq {
233 kind: AbciProofKind::IbcChannel {
234 channel_id: channel_id.clone(),
235 port_id: port_id.clone(),
236 },
237 height: Some(query_height),
238 }
239 .request(client)
240 .await?
241 .proof;
242
243 Ok(IbcChannelProofs {
244 proof_height: *proof_height,
245 query_height,
246 channel,
247 channel_proof,
248 })
249 }
250}
251
252#[derive(Clone, Debug)]
253struct IbcClientStateReq {
254 pub ibc_client_id: IbcClientId,
255 pub height: Option<u64>,
256}
257
258impl QueryRequest for IbcClientStateReq {
259 type QueryResponse = layer_climb_proto::ibc::light_client::ClientState;
260
261 async fn request(
262 &self,
263 client: QueryClient,
264 ) -> Result<layer_climb_proto::ibc::light_client::ClientState> {
265 let IbcClientStateReq {
266 ibc_client_id,
267 height,
268 } = self;
269
270 let req = layer_climb_proto::ibc::client::QueryClientStateRequest {
271 client_id: ibc_client_id.to_string(),
272 };
273
274 let resp = match client.get_connection_mode() {
275 ConnectionMode::Grpc => {
276 let mut req = tonic::Request::new(req);
277 apply_grpc_height(&mut req, *height)?;
278
279 let mut query_client =
280 layer_climb_proto::ibc::client::query_client::QueryClient::new(
281 client.clone_grpc_channel()?,
282 );
283 let resp: layer_climb_proto::ibc::client::QueryClientStateResponse = query_client
284 .client_state(req)
285 .await
286 .map(|res| res.into_inner())
287 .context("couldn't get client state")?;
288
289 resp
290 }
291
292 ConnectionMode::Rpc => client
293 .rpc_client()?
294 .abci_protobuf_query::<_, layer_climb_proto::ibc::client::QueryClientStateResponse>(
295 "/ibc.core.client.v1.Query/ClientState",
296 req,
297 *height,
298 )
299 .await
300 .context("couldn't get client state")?,
301 };
302
303 let client_state = resp
304 .client_state
305 .map(|client_state| match client_state.type_url.as_str() {
306 "/ibc.lightclients.tendermint.v1.ClientState" => {
307 layer_climb_proto::ibc::light_client::ClientState::decode(
308 client_state.value.as_slice(),
309 )
310 .map_err(|e| e.into())
311 }
312 _ => Err(anyhow::anyhow!(
313 "unsupported client state type: {}",
314 client_state.type_url
315 )),
316 })
317 .transpose()?
318 .context("missing client state")?;
319 Ok(client_state)
320 }
321}
322
323#[derive(Clone, Debug)]
324struct IbcConnectionReq {
325 pub connection_id: IbcConnectionId,
326 pub height: Option<u64>,
327}
328
329impl QueryRequest for IbcConnectionReq {
330 type QueryResponse = layer_climb_proto::ibc::connection::ConnectionEnd;
331
332 async fn request(
333 &self,
334 client: QueryClient,
335 ) -> Result<layer_climb_proto::ibc::connection::ConnectionEnd> {
336 let IbcConnectionReq {
337 connection_id,
338 height,
339 } = self;
340
341 let req = layer_climb_proto::ibc::connection::QueryConnectionRequest {
342 connection_id: connection_id.to_string(),
343 };
344
345 let resp = match client.get_connection_mode() {
346 ConnectionMode::Grpc => {
347 let mut req = tonic::Request::new(req);
348
349 apply_grpc_height(&mut req, *height)?;
350
351 let mut query_client = layer_climb_proto::ibc::connection::query_client::QueryClient::new(
352 client.clone_grpc_channel()?,
353 );
354
355 query_client
356 .connection(req)
357 .await
358 .map(|res| res.into_inner())
359 .context("couldn't get connection")?
360 }
361
362 ConnectionMode::Rpc => {
363 client.rpc_client()?
364 .abci_protobuf_query::<_, layer_climb_proto::ibc::connection::QueryConnectionResponse>("/ibc.core.connection.v1.Query/Connection", req, *height)
365 .await
366 .context("couldn't get connection")?
367 }
368 };
369
370 resp.connection.context("missing connection")
371 }
372}
373
374#[derive(Clone, Debug)]
375struct IbcConnectionConsensusStateReq {
376 pub connection_id: IbcConnectionId,
377 pub consensus_height: Option<layer_climb_proto::RevisionHeight>,
378 pub height: Option<u64>,
379}
380
381impl QueryRequest for IbcConnectionConsensusStateReq {
382 type QueryResponse = layer_climb_proto::Any;
383
384 async fn request(&self, client: QueryClient) -> Result<layer_climb_proto::Any> {
385 let IbcConnectionConsensusStateReq {
386 connection_id,
387 consensus_height,
388 height,
389 } = self;
390
391 let consensus_height = match consensus_height {
392 Some(h) => *h,
393 None => layer_climb_proto::RevisionHeight {
394 revision_number: client.chain_config.ibc_client_revision()?,
395 revision_height: match height {
396 Some(h) => *h,
397 None => BlockHeightReq {}.request(client.clone()).await?,
398 },
399 },
400 };
401
402 let req = layer_climb_proto::ibc::connection::QueryConnectionConsensusStateRequest {
403 connection_id: connection_id.to_string(),
404 revision_number: consensus_height.revision_number,
405 revision_height: consensus_height.revision_height,
406 };
407
408 let resp = match client.get_connection_mode() {
409 ConnectionMode::Grpc => {
410 let mut query_client = layer_climb_proto::ibc::connection::query_client::QueryClient::new(
411 client.clone_grpc_channel()?,
412 );
413
414
415 let mut req = tonic::Request::new(req);
416
417 apply_grpc_height(&mut req, *height)?;
418
419 query_client
420 .connection_consensus_state(req)
421 .await
422 .map(|res| res.into_inner())
423 .context("couldn't get consensus state")?
424 },
425 ConnectionMode::Rpc => {
426 client.rpc_client()?
427 .abci_protobuf_query::<_, layer_climb_proto::ibc::connection::QueryConnectionConsensusStateResponse>("/ibc.core.connection.v1.Query/ConnectionConsensusState", req, *height)
428 .await
429 .context("couldn't get consensus state")?
430 }
431 };
432
433 resp.consensus_state.context("missing consensus state")
434 }
435}
436
437#[derive(Clone, Debug)]
438struct IbcChannelReq {
439 pub channel_id: IbcChannelId,
440 pub port_id: IbcPortId,
441 pub height: Option<u64>,
442}
443
444impl QueryRequest for IbcChannelReq {
445 type QueryResponse = layer_climb_proto::ibc::channel::Channel;
446
447 async fn request(
448 &self,
449 client: QueryClient,
450 ) -> Result<layer_climb_proto::ibc::channel::Channel> {
451 let IbcChannelReq {
452 channel_id,
453 port_id,
454 height,
455 } = self;
456
457 let req = layer_climb_proto::ibc::channel::QueryChannelRequest {
458 channel_id: channel_id.to_string(),
459 port_id: port_id.to_string(),
460 };
461
462 let resp = match client.get_connection_mode() {
463 ConnectionMode::Grpc => {
464 let mut req = tonic::Request::new(req);
465
466 apply_grpc_height(&mut req, *height)?;
467
468 let mut query_client =
469 layer_climb_proto::ibc::channel::query_client::QueryClient::new(
470 client.clone_grpc_channel()?,
471 );
472
473 query_client
474 .channel(req)
475 .await
476 .map(|res| res.into_inner())
477 .context("couldn't get channel")?
478 }
479 ConnectionMode::Rpc => client
480 .rpc_client()?
481 .abci_protobuf_query::<_, layer_climb_proto::ibc::channel::QueryChannelResponse>(
482 "/ibc.core.channel.v1.Query/Channel",
483 req,
484 *height,
485 )
486 .await
487 .context("couldn't get channel")?,
488 };
489
490 resp.channel.context("missing channel")
491 }
492}
493
494#[derive(Clone, Debug)]
495struct IbcCreateClientConsensusStateReq {
496 pub trusting_period_secs: Option<u64>,
497}
498
499impl QueryRequest for IbcCreateClientConsensusStateReq {
500 type QueryResponse = (
501 layer_climb_proto::ibc::light_client::ClientState,
502 layer_climb_proto::ibc::light_client::ConsensusState,
503 );
504
505 async fn request(
506 &self,
507 client: QueryClient,
508 ) -> Result<(
509 layer_climb_proto::ibc::light_client::ClientState,
510 layer_climb_proto::ibc::light_client::ConsensusState,
511 )> {
512 let trusting_period_secs = self.trusting_period_secs;
513
514 let latest_block_header = BlockHeaderReq { height: None }
515 .request(client.clone())
516 .await?;
517
518 let consensus_state = layer_climb_proto::ibc::light_client::ConsensusState {
519 timestamp: latest_block_header.time(),
520 root: Some(layer_climb_proto::MerkleRoot {
521 hash: latest_block_header.app_hash(),
523 }),
524 next_validators_hash: latest_block_header.next_validators_hash(),
525 };
526
527 let staking_params = StakingParamsReq {}.request(client.clone()).await?;
528
529 let unbonding_period = staking_params
530 .unbonding_time
531 .context("missing unbonding time")?;
532
533 let unbonding_period = layer_climb_proto::Duration {
534 seconds: unbonding_period.seconds,
535 nanos: unbonding_period.nanos,
536 };
537
538 let trusting_period = match trusting_period_secs {
541 Some(trusting_period_secs) => layer_climb_proto::Duration {
542 seconds: trusting_period_secs.try_into()?,
543 nanos: 0,
544 },
545 None => layer_climb_proto::Duration {
546 seconds: (unbonding_period.seconds * 2) / 3,
547 nanos: (unbonding_period.nanos * 2) / 3,
548 },
549 };
550
551 let max_clock_drift = layer_climb_proto::Duration {
553 seconds: 10,
554 nanos: 0,
555 };
556
557 let chain_id = client.chain_config.chain_id.to_string();
558
559 let latest_height = layer_climb_proto::RevisionHeight {
560 revision_number: client.chain_config.ibc_client_revision()?,
561 revision_height: latest_block_header.height()?,
562 };
563
564 #[allow(deprecated)]
565 let client_state = layer_climb_proto::ibc::light_client::ClientState {
566 chain_id,
567 trust_level: Some(layer_climb_proto::ibc::light_client::Fraction {
570 numerator: 1,
571 denominator: 3,
572 }),
573 trusting_period: Some(trusting_period),
574 unbonding_period: Some(unbonding_period),
575 max_clock_drift: Some(max_clock_drift),
576 frozen_height: None,
577 latest_height: Some(latest_height),
578 proof_specs: vec![
580 layer_climb_proto::ibc::ics23::iavl_spec(),
581 layer_climb_proto::ibc::ics23::tendermint_spec(),
582 ],
583 upgrade_path: vec!["upgrade".to_string(), "upgradedIBCState".to_string()],
586 allow_update_after_expiry: false,
587 allow_update_after_misbehaviour: false,
588 };
589
590 Ok((client_state, consensus_state))
591 }
592}
593
594#[derive(Debug, Clone)]
595pub struct IbcConnectionProofs {
596 pub proof_height: layer_climb_proto::RevisionHeight,
597 pub consensus_height: layer_climb_proto::RevisionHeight,
598 pub query_height: u64,
599 pub connection: layer_climb_proto::ibc::connection::ConnectionEnd,
600 pub connection_proof: Vec<u8>,
601 pub client_state_proof: Vec<u8>,
602 pub consensus_proof: Vec<u8>,
603 pub client_state: layer_climb_proto::ibc::light_client::ClientState,
604}
605
606#[derive(Debug, Clone)]
607pub struct IbcChannelProofs {
608 pub proof_height: layer_climb_proto::RevisionHeight,
609 pub query_height: u64,
610 pub channel: layer_climb_proto::ibc::channel::Channel,
611 pub channel_proof: Vec<u8>,
612}