layer_climb_core/querier/
ibc.rs

1// TODO - test RPC mode
2use 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                // in MerkleRoot comment itself: "In the Cosmos SDK, the AppHash of a block header becomes the root."
522                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        // 2/3 of the unbonding period gives enough time to trust without constant checking
539        // but still within enough time to punish misbehaviour
540        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        // value taken from ibc-go tests: https://github.com/cosmos/ibc-go/blob/049bef96f730ee7f29647b1d5833530444395abc/testing/values.go#L33
552        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            // https://github.com/cosmos/ibc-go/blob/049bef96f730ee7f29647b1d5833530444395abc/modules/light-clients/07-tendermint/fraction.go#L9
568            // -> https://github.com/cometbft/cometbft/blob/27a460641ad835b9e6ae47523c12b0678b4619a8/light/verifier.go#L15
569            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            // https://github.com/cosmos/ibc-go/blob/0613ec84a1a38ca797931343f7e2da330ec7c508/modules/core/23-commitment/types/merkle.go#L18
579            proof_specs: vec![
580                layer_climb_proto::ibc::ics23::iavl_spec(),
581                layer_climb_proto::ibc::ics23::tendermint_spec(),
582            ],
583            // in the ClientState definition itself:
584            // > For SDK chains using the default upgrade module, upgrade_path should be []string{"upgrade", "upgradedIBCState"}`
585            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}