layer_climb_core/querier/
abci.rs

1use tracing::instrument;
2
3use crate::{
4    ibc_types::{IbcChannelId, IbcClientId, IbcConnectionId, IbcPortId},
5    prelude::*,
6};
7
8use super::ConnectionMode;
9
10impl QueryClient {
11    // from looking at other implementations, it might seem like getting proof_height from the current remote block height is the way to go
12    // ... but, it just doesn't seem to work.
13    // instead, getting proof height from the client state (i.e. local client state, which is the state of the remote chain) seems to work just fine
14
15    // height - 1 is documented here: https://github.com/cosmos/ibc-go/blob/main/modules/core/client/query.go#L26
16    #[instrument]
17    pub async fn abci_proof(
18        &self,
19        kind: AbciProofKind,
20        height: Option<u64>,
21    ) -> Result<AbciProofResponse> {
22        self.run_with_middleware(AbciProofReq {
23            kind: kind.clone(),
24            height,
25        })
26        .await
27    }
28}
29
30#[derive(Clone, Debug)]
31pub struct AbciProofReq {
32    pub kind: AbciProofKind,
33    pub height: Option<u64>,
34}
35
36#[derive(Clone, Debug)]
37pub enum AbciProofKind {
38    IbcClientState {
39        client_id: IbcClientId,
40    },
41    IbcConnection {
42        connection_id: IbcConnectionId,
43    },
44    IbcConsensus {
45        client_id: IbcClientId,
46        height: layer_climb_proto::RevisionHeight,
47    },
48    IbcChannel {
49        channel_id: IbcChannelId,
50        port_id: IbcPortId,
51    },
52    IbcPacketCommitment {
53        port_id: IbcPortId,
54        channel_id: IbcChannelId,
55        sequence: u64,
56    },
57    IbcPacketReceive {
58        port_id: IbcPortId,
59        channel_id: IbcChannelId,
60        sequence: u64,
61    },
62    IbcPacketAck {
63        port_id: IbcPortId,
64        channel_id: IbcChannelId,
65        sequence: u64,
66    },
67    StakingParams,
68    AuthBaseAccount {
69        address: Address,
70    },
71}
72impl AbciProofKind {
73    pub fn path(&self) -> &str {
74        match self {
75            Self::IbcClientState { .. }
76            | Self::IbcConnection { .. }
77            | Self::IbcConsensus { .. }
78            | Self::IbcChannel { .. }
79            | Self::IbcPacketCommitment { .. }
80            | Self::IbcPacketReceive { .. }
81            | Self::IbcPacketAck { .. } => "store/ibc/key",
82            Self::StakingParams => "store/staking/key",
83            Self::AuthBaseAccount { .. } => "store/acc/key",
84        }
85    }
86    pub fn data_bytes(&self) -> Vec<u8> {
87        // https://github.com/cosmos/ibc/blob/main/spec/core/ics-024-host-requirements/README.md
88        match self {
89            Self::IbcClientState { client_id } => {
90                format!("clients/{client_id}/clientState").into_bytes()
91            }
92            Self::IbcConnection { connection_id } => {
93                format!("connections/{connection_id}").into_bytes()
94            }
95            Self::IbcConsensus { client_id, height } => format!(
96                "clients/{client_id}/consensusStates/{}-{}",
97                height.revision_number, height.revision_height
98            )
99            .into_bytes(),
100            Self::IbcChannel {
101                channel_id,
102                port_id,
103            } => format!("channelEnds/ports/{port_id}/channels/{channel_id}").into_bytes(),
104            Self::IbcPacketCommitment {
105                port_id,
106                channel_id,
107                sequence,
108            } => format!("commitments/ports/{port_id}/channels/{channel_id}/sequences/{sequence}")
109                .into_bytes(),
110            Self::IbcPacketReceive {
111                port_id,
112                channel_id,
113                sequence,
114            } => format!("receipts/ports/{port_id}/channels/{channel_id}/sequences/{sequence}")
115                .into_bytes(),
116            Self::IbcPacketAck {
117                port_id,
118                channel_id,
119                sequence,
120            } => format!("acks/ports/{port_id}/channels/{channel_id}/sequences/{sequence}")
121                .into_bytes(),
122            Self::StakingParams => vec![0x01],
123            Self::AuthBaseAccount { address } => {
124                let mut data = vec![0x01];
125                data.extend(address.as_bytes());
126                data
127            }
128        }
129    }
130}
131
132#[derive(Debug)]
133pub struct AbciProofResponse {
134    pub proof: Vec<u8>,
135    pub value: Vec<u8>,
136    // could add more fields like height, but imho that's more confusing than helpful for now
137}
138
139impl QueryRequest for AbciProofReq {
140    type QueryResponse = AbciProofResponse;
141
142    async fn request(&self, client: QueryClient) -> Result<AbciProofResponse> {
143        match client.get_connection_mode() {
144            ConnectionMode::Grpc => {
145                let req = tonic::Request::new(layer_climb_proto::tendermint::AbciQueryRequest {
146                    path: self.kind.path().to_string(),
147                    data: self.kind.data_bytes(),
148                    height: match self.height {
149                        Some(height) => height.try_into()?,
150                        // according to the rpc docs, 0 is latest...
151                        None => 0.into(),
152                    },
153                    prove: true,
154                });
155
156                // I think, don't do this, since height is part of the request?
157                // apply_grpc_height(&mut req, Some(self.height))?;
158
159                let mut query_client =
160                    layer_climb_proto::tendermint::service_client::ServiceClient::new(
161                        client.clone_grpc_channel()?,
162                    );
163                let resp: layer_climb_proto::tendermint::AbciQueryResponse = query_client
164                    .abci_query(req)
165                    .await
166                    .map(|res| res.into_inner())
167                    .with_context(|| format!("couldn't get abci proof for {:?}", self.kind))?;
168
169                //log_abci_resp(&resp, &self.kind, self.height);
170
171                // get a byte-string from resp.value which is a Vec<u8>:
172                let proof_ops = resp.proof_ops.context("missing proof_ops in abci_query")?;
173
174                let proof = AbciProofToConvert::Grpc(proof_ops).convert_abci_proof()?;
175
176                Ok(AbciProofResponse {
177                    proof,
178                    value: resp.value,
179                })
180            }
181            ConnectionMode::Rpc => {
182                // https://github.com/cosmos/ibc-go/blob/73061ee020a6be676f2d5843b7430082d2fe275c/modules/core/client/query.go#L26
183                let resp = client
184                    .rpc_client()?
185                    .abci_query(
186                        self.kind.path().to_string(),
187                        self.kind.data_bytes(),
188                        self.height,
189                        true,
190                    )
191                    .await?;
192
193                let proof_ops = resp.proof.context("missing proof_ops in abci_query")?;
194
195                let proof = AbciProofToConvert::Rpc(proof_ops).convert_abci_proof()?;
196
197                Ok(AbciProofResponse {
198                    proof,
199                    value: resp.value,
200                })
201            }
202        }
203    }
204}
205
206enum AbciProofToConvert {
207    Grpc(layer_climb_proto::tendermint::ProofOps),
208    Rpc(tendermint::merkle::proof::ProofOps),
209}
210
211impl AbciProofToConvert {
212    fn into_vec(self) -> Vec<Vec<u8>> {
213        match self {
214            Self::Grpc(proof_ops) => proof_ops.ops.into_iter().map(|op| op.data).collect(),
215            Self::Rpc(proof_ops) => proof_ops.ops.into_iter().map(|op| op.data).collect(),
216        }
217    }
218
219    fn convert_abci_proof(self) -> Result<Vec<u8>> {
220        let mut proofs = Vec::new();
221
222        for op in self.into_vec() {
223            let mut parsed = layer_climb_proto::ibc::ics23::CommitmentProof { proof: None };
224            layer_climb_proto::Message::merge(&mut parsed, op.as_slice())?;
225            // if let layer_climb_proto::ibc::ics23::commitment_proof::Proof::Exist(layer_climb_proto::ibc::ics23::ExistenceProof{ key, value, leaf, path}) = parsed.proof.as_mut().unwrap() {
226            //     println!("{} vs. {:?}", op.field_type, path);
227            // }
228            proofs.push(parsed);
229        }
230
231        let merkle_proof = layer_climb_proto::MerkleProof { proofs };
232
233        let mut bytes = Vec::new();
234        layer_climb_proto::Message::encode(&merkle_proof, &mut bytes)?;
235
236        Ok(bytes)
237    }
238}
239
240// in theory, with this working, we could save some requests... but, it's finicky
241// fn log_abci_resp(resp: &layer_climb_proto::cosmos::base::tendermint::v1beta1::AbciQueryResponse, kind: &AbciProofKind, height: u64 ) {
242//     match layer_climb_proto::Any::decode(resp.value.as_slice()) {
243//         Ok(any) => {
244//             match any.type_url.as_str() {
245//                 "/ibc.lightclients.tendermint.v1.ClientState" => {
246//                     match layer_climb_proto::ibc::light_client::ClientState::decode(any.value.as_slice()) {
247//                         Ok(client_state) => {
248//                             println!("abci_query response for {} at height {}: {:?}", kind.data_string(), height, client_state);
249//                         },
250//                         Err(e) => {
251//                             println!("abci_query response for {} at height {}: [error decoding client state] {}", kind.data_string(), height, e);
252//                         }
253//                     }
254//                 },
255//                 "/ibc.lightclients.tendermint.v1.ConsensusState" => {
256//                     match layer_climb_proto::ibc::light_client::ConsensusState::decode(any.value.as_slice()) {
257//                         Ok(consensus_state) => {
258//                             println!("abci_query response for {} at height {}: {:?}", kind.data_string(), height, consensus_state);
259//                         },
260//                         Err(e) => {
261//                             println!("abci_query response for {} at height {}: [error decoding consensus state] {}", kind.data_string(), height, e);
262//                         }
263//                     }
264//                 },
265//                 _ => {
266//                     println!("abci_query response for {} at height {}: [unknown protobuf] {}", kind.data_string(), height, any.type_url);
267//                 }
268//             }
269//         },
270//         Err(_) => {
271//             println!("abci_query response for {} at height {}: {}", kind.data_string(), height, std::str::from_utf8(&resp.value).unwrap_or("[COULDN'T DECODE VALUE]"));
272//         }
273//     }
274// }