layer_climb_core/querier/
basic.rs

1use tracing::instrument;
2
3use crate::prelude::*;
4
5use super::ConnectionMode;
6
7impl QueryClient {
8    #[instrument]
9    pub async fn balance(&self, addr: Address, denom: Option<String>) -> Result<Option<u128>> {
10        self.run_with_middleware(BalanceReq { addr, denom }).await
11    }
12
13    #[instrument]
14    pub async fn all_balances(
15        &self,
16        addr: Address,
17        limit_per_page: Option<u64>,
18    ) -> Result<Vec<layer_climb_proto::Coin>> {
19        self.run_with_middleware(AllBalancesReq {
20            addr,
21            limit_per_page,
22        })
23        .await
24    }
25
26    #[instrument]
27    pub async fn base_account(
28        &self,
29        addr: &Address,
30    ) -> Result<layer_climb_proto::auth::BaseAccount> {
31        self.run_with_middleware(BaseAccountReq { addr: addr.clone() })
32            .await
33    }
34
35    #[instrument]
36    pub async fn staking_params(&self) -> Result<layer_climb_proto::staking::Params> {
37        self.run_with_middleware(StakingParamsReq {}).await
38    }
39
40    #[instrument]
41    pub async fn block(&self, height: Option<u64>) -> Result<BlockResp> {
42        self.run_with_middleware(BlockReq { height }).await
43    }
44
45    #[instrument]
46    pub async fn block_header(&self, height: Option<u64>) -> Result<BlockHeaderResp> {
47        self.run_with_middleware(BlockHeaderReq { height }).await
48    }
49
50    #[instrument]
51    pub async fn block_height(&self) -> Result<u64> {
52        self.run_with_middleware(BlockHeightReq {}).await
53    }
54
55    #[instrument]
56    pub async fn node_info(&self) -> Result<layer_climb_proto::tendermint::GetNodeInfoResponse> {
57        self.run_with_middleware(NodeInfoReq {}).await
58    }
59}
60
61#[derive(Clone, Debug)]
62pub struct BalanceReq {
63    pub addr: Address,
64    pub denom: Option<String>,
65}
66
67impl QueryRequest for BalanceReq {
68    type QueryResponse = Option<u128>;
69
70    async fn request(&self, client: QueryClient) -> Result<Self::QueryResponse> {
71        let denom = self
72            .denom
73            .clone()
74            .unwrap_or(client.chain_config.gas_denom.clone());
75
76        let req = layer_climb_proto::bank::QueryBalanceRequest {
77            address: self.addr.to_string(),
78            denom,
79        };
80
81        let coin = match client.get_connection_mode() {
82            ConnectionMode::Grpc => {
83                let mut query_client = layer_climb_proto::bank::query_client::QueryClient::new(
84                    client.clone_grpc_channel()?,
85                );
86
87                query_client
88                    .balance(req)
89                    .await
90                    .map(|res| res.into_inner().balance)?
91            }
92            ConnectionMode::Rpc => client
93                .rpc_client()?
94                .abci_protobuf_query::<_, layer_climb_proto::bank::QueryBalanceResponse>(
95                    "/cosmos.bank.v1beta1.Query/Balance",
96                    req,
97                    None,
98                )
99                .await
100                .map(|res| res.balance)?,
101        };
102
103        match coin {
104            None => Ok(None),
105            Some(coin) => {
106                let amount = coin
107                    .amount
108                    .parse::<u128>()
109                    .context("couldn't parse amount")?;
110                Ok(Some(amount))
111            }
112        }
113    }
114}
115
116#[derive(Clone, Debug)]
117pub struct AllBalancesReq {
118    pub addr: Address,
119    pub limit_per_page: Option<u64>,
120}
121
122impl QueryRequest for AllBalancesReq {
123    type QueryResponse = Vec<layer_climb_proto::Coin>;
124
125    async fn request(&self, client: QueryClient) -> Result<Self::QueryResponse> {
126        let mut coins = Vec::new();
127
128        let mut pagination = None;
129
130        let limit = self
131            .limit_per_page
132            .unwrap_or(client.balances_pagination_limit);
133
134        let mut grpc_query_client = match client.get_connection_mode() {
135            ConnectionMode::Grpc => Some(layer_climb_proto::bank::query_client::QueryClient::new(
136                client.clone_grpc_channel()?,
137            )),
138            ConnectionMode::Rpc => None,
139        };
140
141        // for RPC, keep a consistent height
142        let height = match client.get_connection_mode() {
143            ConnectionMode::Grpc => None,
144            ConnectionMode::Rpc => Some(BlockHeightReq {}.request(client.clone()).await?),
145        };
146
147        loop {
148            let req = layer_climb_proto::bank::QueryAllBalancesRequest {
149                address: self.addr.to_string(),
150                pagination,
151                resolve_denom: true,
152            };
153
154            let resp = match client.get_connection_mode() {
155                ConnectionMode::Grpc => grpc_query_client
156                    .as_mut()
157                    .unwrap()
158                    .all_balances(req)
159                    .await
160                    .map(|res| res.into_inner())?,
161                ConnectionMode::Rpc => client
162                    .rpc_client()?
163                    .abci_protobuf_query::<_, layer_climb_proto::bank::QueryAllBalancesResponse>(
164                        "/cosmos.bank.v1beta1.Query/AllBalances",
165                        req,
166                        height,
167                    )
168                    .await?,
169            };
170
171            coins.extend(resp.balances);
172
173            match &resp.pagination {
174                None => break,
175                Some(pagination_response) => {
176                    if pagination_response.next_key.is_empty() {
177                        break;
178                    }
179                }
180            }
181
182            pagination = resp
183                .pagination
184                .map(|p| layer_climb_proto::query::PageRequest {
185                    key: p.next_key,
186                    offset: 0,
187                    limit,
188                    count_total: false,
189                    reverse: false,
190                });
191        }
192
193        Ok(coins)
194    }
195}
196
197#[derive(Clone, Debug)]
198pub struct BaseAccountReq {
199    pub addr: Address,
200}
201
202impl QueryRequest for BaseAccountReq {
203    type QueryResponse = layer_climb_proto::auth::BaseAccount;
204
205    async fn request(&self, client: QueryClient) -> Result<Self::QueryResponse> {
206        let req = layer_climb_proto::auth::QueryAccountRequest {
207            address: self.addr.to_string(),
208        };
209
210        let query_resp = match client.get_connection_mode() {
211            ConnectionMode::Grpc => {
212                let mut query_client = layer_climb_proto::auth::query_client::QueryClient::new(
213                    client.clone_grpc_channel()?,
214                );
215
216                query_client
217                    .account(req)
218                    .await
219                    .map(|res| res.into_inner().account)
220                    .map_err(|err| {
221                        anyhow::Error::from(err).context(format!("account {} not found", self.addr))
222                    })?
223                    .ok_or_else(|| anyhow!("account {} not found", self.addr))?
224            }
225            ConnectionMode::Rpc => client
226                .rpc_client()?
227                .abci_protobuf_query::<_, layer_climb_proto::auth::QueryAccountResponse>(
228                    "/cosmos.auth.v1beta1.Query/Account",
229                    req,
230                    None,
231                )
232                .await?
233                .account
234                .ok_or_else(|| anyhow!("account {} not found", self.addr))?,
235        };
236
237        let account = layer_climb_proto::auth::BaseAccount::decode(query_resp.value.as_slice())
238            .context("couldn't decode account")?;
239
240        Ok(account)
241    }
242}
243
244#[derive(Clone, Debug)]
245pub struct StakingParamsReq {}
246
247impl QueryRequest for StakingParamsReq {
248    type QueryResponse = layer_climb_proto::staking::Params;
249
250    async fn request(&self, client: QueryClient) -> Result<layer_climb_proto::staking::Params> {
251        let resp = match client.get_connection_mode() {
252            ConnectionMode::Grpc => {
253                let mut query_client = layer_climb_proto::staking::query_client::QueryClient::new(
254                    client.clone_grpc_channel()?,
255                );
256
257                query_client
258                    .params(layer_climb_proto::staking::QueryParamsRequest {})
259                    .await
260                    .map(|res| res.into_inner())
261                    .context("couldn't get staking params")?
262            }
263            ConnectionMode::Rpc => {
264                client
265                    .rpc_client()?
266                    .abci_protobuf_query::<_, layer_climb_proto::staking::QueryParamsResponse>(
267                        "/cosmos.staking.v1beta1.Query/Params",
268                        layer_climb_proto::staking::QueryParamsRequest {},
269                        None,
270                    )
271                    .await?
272            }
273        };
274
275        resp.params.ok_or(anyhow!("no staking params found"))
276    }
277}
278
279#[derive(Clone, Debug)]
280pub struct NodeInfoReq {}
281
282impl QueryRequest for NodeInfoReq {
283    type QueryResponse = layer_climb_proto::tendermint::GetNodeInfoResponse;
284
285    async fn request(
286        &self,
287        client: QueryClient,
288    ) -> Result<layer_climb_proto::tendermint::GetNodeInfoResponse> {
289        let req = layer_climb_proto::tendermint::GetNodeInfoRequest {};
290
291        match client.get_connection_mode() {
292            ConnectionMode::Grpc => {
293                layer_climb_proto::tendermint::service_client::ServiceClient::new(
294                    client.clone_grpc_channel()?,
295                )
296                .get_node_info(req)
297                .await
298                .map(|resp| resp.into_inner())
299                .context("couldn't get node info")
300            }
301            ConnectionMode::Rpc => client
302                .rpc_client()?
303                .abci_protobuf_query::<_, layer_climb_proto::tendermint::GetNodeInfoResponse>(
304                    "/cosmos.base.tendermint.v1beta1.Service/GetNodeInfo",
305                    req,
306                    None,
307                )
308                .await
309                .context("couldn't get node info"),
310        }
311    }
312}
313
314#[derive(Clone, Debug)]
315pub struct BlockReq {
316    pub height: Option<u64>,
317}
318
319#[derive(Debug)]
320pub enum BlockResp {
321    Sdk(layer_climb_proto::block::SdkBlock),
322    Old(layer_climb_proto::block::TendermintBlock),
323}
324
325impl QueryRequest for BlockReq {
326    type QueryResponse = BlockResp;
327
328    async fn request(&self, client: QueryClient) -> Result<Self::QueryResponse> {
329        let height = self.height;
330
331        match client.get_connection_mode() {
332            ConnectionMode::Grpc => {
333                let mut query_client =
334                    layer_climb_proto::tendermint::service_client::ServiceClient::new(
335                        client.clone_grpc_channel()?,
336                    );
337
338                match height {
339                    Some(height) => query_client
340                        .get_block_by_height(
341                            layer_climb_proto::tendermint::GetBlockByHeightRequest {
342                                height: height.try_into()?,
343                            },
344                        )
345                        .await
346                        .map_err(|err| err.into())
347                        .and_then(|res| {
348                            let res = res.into_inner();
349                            match res.sdk_block {
350                                Some(block) => Ok(BlockResp::Sdk(block)),
351                                None => res
352                                    .block
353                                    .map(BlockResp::Old)
354                                    .ok_or(anyhow!("no block found")),
355                            }
356                        }),
357                    None => query_client
358                        .get_latest_block(layer_climb_proto::tendermint::GetLatestBlockRequest {})
359                        .await
360                        .map_err(|err| err.into())
361                        .and_then(|res| {
362                            let res = res.into_inner();
363                            match res.sdk_block {
364                                Some(block) => Ok(BlockResp::Sdk(block)),
365                                None => res
366                                    .block
367                                    .map(BlockResp::Old)
368                                    .ok_or(anyhow!("no block found")),
369                            }
370                        }),
371                }
372                .with_context(move || match height {
373                    Some(height) => format!("no block found at height {height}"),
374                    None => "no latest block found".to_string(),
375                })
376            }
377            ConnectionMode::Rpc => {
378                let resp = client.rpc_client()?.block(height).await?;
379
380                Ok(BlockResp::Old(resp.block.into()))
381            }
382        }
383    }
384}
385
386#[derive(Clone, Debug)]
387pub struct BlockHeaderReq {
388    pub height: Option<u64>,
389}
390
391#[derive(Debug)]
392pub enum BlockHeaderResp {
393    Sdk(layer_climb_proto::block::SdkHeader),
394    Old(layer_climb_proto::block::TendermintHeader),
395}
396
397impl BlockHeaderResp {
398    pub fn height(&self) -> Result<u64> {
399        Ok(match self {
400            BlockHeaderResp::Sdk(header) => header.height.try_into()?,
401            BlockHeaderResp::Old(header) => header.height.try_into()?,
402        })
403    }
404
405    pub fn time(&self) -> Option<layer_climb_proto::Timestamp> {
406        match self {
407            BlockHeaderResp::Sdk(header) => header.time,
408            BlockHeaderResp::Old(header) => header.time.map(|time| layer_climb_proto::Timestamp {
409                seconds: time.seconds,
410                nanos: time.nanos,
411            }),
412        }
413    }
414
415    pub fn app_hash(&self) -> Vec<u8> {
416        match self {
417            BlockHeaderResp::Sdk(header) => header.app_hash.clone(),
418            BlockHeaderResp::Old(header) => header.app_hash.clone(),
419        }
420    }
421
422    pub fn next_validators_hash(&self) -> Vec<u8> {
423        match self {
424            BlockHeaderResp::Sdk(header) => header.next_validators_hash.clone(),
425            BlockHeaderResp::Old(header) => header.next_validators_hash.clone(),
426        }
427    }
428}
429
430impl QueryRequest for BlockHeaderReq {
431    type QueryResponse = BlockHeaderResp;
432
433    async fn request(&self, client: QueryClient) -> Result<Self::QueryResponse> {
434        let block = BlockReq {
435            height: self.height,
436        }
437        .request(client)
438        .await?;
439
440        match block {
441            BlockResp::Sdk(block) => Ok(BlockHeaderResp::Sdk(
442                block.header.context("no header found")?,
443            )),
444            BlockResp::Old(block) => Ok(BlockHeaderResp::Old(
445                block.header.context("no header found")?,
446            )),
447        }
448    }
449}
450
451#[derive(Clone, Debug)]
452pub struct BlockHeightReq {}
453
454impl QueryRequest for BlockHeightReq {
455    type QueryResponse = u64;
456
457    async fn request(&self, client: QueryClient) -> Result<u64> {
458        let header = BlockHeaderReq { height: None }.request(client).await?;
459
460        Ok(match header {
461            BlockHeaderResp::Sdk(header) => header.height,
462            BlockHeaderResp::Old(header) => header.height,
463        }
464        .try_into()?)
465    }
466}