Skip to main content

nym_validator_client/rpc/
mod.rs

1// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: Apache-2.0
3
4// TEMPORARY WORKAROUND:
5// those features are expected as the below should only get activated whenever
6// the corresponding features in tendermint-rpc are enabled transitively
7#![allow(unexpected_cfgs)]
8
9use async_trait::async_trait;
10use cosmrs::tendermint::{self, abci, block::Height, evidence::Evidence, Genesis, Hash};
11use serde::{de::DeserializeOwned, Serialize};
12use std::fmt;
13use tendermint_rpc::{
14    endpoint::{validators::DEFAULT_VALIDATORS_PER_PAGE, *},
15    query::Query,
16    Error, Order, Paging, SimpleRequest,
17};
18
19#[cfg(feature = "http-client")]
20use crate::error::TendermintRpcError;
21#[cfg(feature = "http-client")]
22use crate::HttpRpcClient;
23#[cfg(feature = "http-client")]
24use tendermint_rpc::client::CompatMode;
25#[cfg(feature = "http-client")]
26use tendermint_rpc::HttpClientUrl;
27
28pub mod reqwest;
29
30#[cfg(feature = "http-client")]
31pub fn http_client<U>(url: U) -> Result<HttpRpcClient, TendermintRpcError>
32where
33    U: TryInto<HttpClientUrl, Error = Error>,
34{
35    HttpRpcClient::builder(url.try_into()?)
36        .compat_mode(CompatMode::V0_37)
37        .build()
38}
39
40// we have to create a sealed trait since `TendermintClient` needs T: Send (due to how async trait is created)
41// which we can't do in wasm
42#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
43#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
44pub trait TendermintRpcClient {
45    /// `/abci_info`: get information about the ABCI application.
46    async fn abci_info(&self) -> Result<abci::response::Info, Error> {
47        Ok(self.perform(abci_info::Request).await?.response)
48    }
49
50    /// `/abci_query`: query the ABCI application
51    async fn abci_query<V>(
52        &self,
53        path: Option<String>,
54        data: V,
55        height: Option<Height>,
56        prove: bool,
57    ) -> Result<abci_query::AbciQuery, Error>
58    where
59        V: Into<Vec<u8>> + Send,
60    {
61        Ok(self
62            .perform(abci_query::Request::new(path, data, height, prove))
63            .await?
64            .response)
65    }
66
67    /// `/block`: get block at a given height.
68    async fn block<H>(&self, height: H) -> Result<block::Response, Error>
69    where
70        H: Into<Height> + Send,
71    {
72        self.perform(block::Request::new(height.into())).await
73    }
74
75    /// `/block_by_hash`: get block by hash.
76    async fn block_by_hash(
77        &self,
78        hash: tendermint::Hash,
79    ) -> Result<block_by_hash::Response, Error> {
80        self.perform(block_by_hash::Request::new(hash)).await
81    }
82
83    /// `/block`: get the latest block.
84    async fn latest_block(&self) -> Result<block::Response, Error> {
85        self.perform(block::Request::default()).await
86    }
87
88    /// `/header`: get block header at a given height.
89    async fn header<H>(&self, height: H) -> Result<header::Response, Error>
90    where
91        H: Into<Height> + Send,
92    {
93        self.perform(header::Request::new(height.into())).await
94    }
95
96    /// `/header_by_hash`: get block by hash.
97    async fn header_by_hash(
98        &self,
99        hash: tendermint::Hash,
100    ) -> Result<header_by_hash::Response, Error> {
101        self.perform(header_by_hash::Request::new(hash)).await
102    }
103
104    /// `/block_results`: get ABCI results for a block at a particular height.
105    async fn block_results<H>(&self, height: H) -> Result<block_results::Response, Error>
106    where
107        H: Into<Height> + Send,
108    {
109        self.perform(block_results::Request::new(height.into()))
110            .await
111    }
112
113    /// `/block_results`: get ABCI results for the latest block.
114    async fn latest_block_results(&self) -> Result<block_results::Response, Error> {
115        self.perform(block_results::Request::default()).await
116    }
117
118    /// `/block_search`: search for blocks by BeginBlock and EndBlock events.
119    async fn block_search(
120        &self,
121        query: Query,
122        page: u32,
123        per_page: u8,
124        order: Order,
125    ) -> Result<block_search::Response, Error> {
126        self.perform(block_search::Request::new(query, page, per_page, order))
127            .await
128    }
129
130    /// `/blockchain`: get block headers for `min` <= `height` <= `max`.
131    ///
132    /// Block headers are returned in descending order (highest first).
133    ///
134    /// Returns at most 20 items.
135    async fn blockchain<H>(&self, min: H, max: H) -> Result<blockchain::Response, Error>
136    where
137        H: Into<Height> + Send,
138    {
139        // TODO(tarcieri): return errors for invalid params before making request?
140        self.perform(blockchain::Request::new(min.into(), max.into()))
141            .await
142    }
143
144    /// `/broadcast_tx_async`: broadcast a transaction, returning immediately.
145    async fn broadcast_tx_async<T>(&self, tx: T) -> Result<broadcast::tx_async::Response, Error>
146    where
147        T: Into<Vec<u8>> + Send,
148    {
149        self.perform(broadcast::tx_async::Request::new(tx)).await
150    }
151
152    /// `/broadcast_tx_sync`: broadcast a transaction, returning the response
153    /// from `CheckTx`.
154    async fn broadcast_tx_sync<T>(&self, tx: T) -> Result<broadcast::tx_sync::Response, Error>
155    where
156        T: Into<Vec<u8>> + Send,
157    {
158        self.perform(broadcast::tx_sync::Request::new(tx)).await
159    }
160
161    /// `/broadcast_tx_commit`: broadcast a transaction, returning the response
162    /// from `DeliverTx`.
163    async fn broadcast_tx_commit<T>(&self, tx: T) -> Result<broadcast::tx_commit::Response, Error>
164    where
165        T: Into<Vec<u8>> + Send,
166    {
167        self.perform(broadcast::tx_commit::Request::new(tx)).await
168    }
169
170    /// `/commit`: get block commit at a given height.
171    async fn commit<H>(&self, height: H) -> Result<commit::Response, Error>
172    where
173        H: Into<Height> + Send,
174    {
175        self.perform(commit::Request::new(height.into())).await
176    }
177
178    /// `/consensus_params`: get current consensus parameters at the specified
179    /// height.
180    async fn consensus_params<H>(&self, height: H) -> Result<consensus_params::Response, Error>
181    where
182        H: Into<Height> + Send,
183    {
184        self.perform(consensus_params::Request::new(Some(height.into())))
185            .await
186    }
187
188    /// `/consensus_state`: get current consensus state
189    async fn consensus_state(&self) -> Result<consensus_state::Response, Error> {
190        self.perform(consensus_state::Request::new()).await
191    }
192
193    // TODO(thane): Simplify once validators endpoint removes pagination.
194    /// `/validators`: get validators a given height.
195    async fn validators<H>(&self, height: H, paging: Paging) -> Result<validators::Response, Error>
196    where
197        H: Into<Height> + Send,
198    {
199        let height = height.into();
200        match paging {
201            Paging::Default => {
202                self.perform(validators::Request::new(Some(height), None, None))
203                    .await
204            }
205            Paging::Specific {
206                page_number,
207                per_page,
208            } => {
209                self.perform(validators::Request::new(
210                    Some(height),
211                    Some(page_number),
212                    Some(per_page),
213                ))
214                .await
215            }
216            Paging::All => {
217                let mut page_num = 1_usize;
218                let mut validators = Vec::new();
219                let per_page = DEFAULT_VALIDATORS_PER_PAGE.into();
220                loop {
221                    let response = self
222                        .perform(validators::Request::new(
223                            Some(height),
224                            Some(page_num.into()),
225                            Some(per_page),
226                        ))
227                        .await?;
228                    validators.extend(response.validators);
229                    if validators.len() as i32 == response.total {
230                        return Ok(validators::Response::new(
231                            response.block_height,
232                            validators,
233                            response.total,
234                        ));
235                    }
236                    page_num += 1;
237                }
238            }
239        }
240    }
241
242    /// `/consensus_params`: get the latest consensus parameters.
243    async fn latest_consensus_params(&self) -> Result<consensus_params::Response, Error> {
244        self.perform(consensus_params::Request::new(None)).await
245    }
246
247    /// `/commit`: get the latest block commit
248    async fn latest_commit(&self) -> Result<commit::Response, Error> {
249        self.perform(commit::Request::default()).await
250    }
251
252    /// `/health`: get node health.
253    ///
254    /// Returns empty result (200 OK) on success, no response in case of an error.
255    async fn health(&self) -> Result<(), Error> {
256        self.perform(health::Request).await?;
257        Ok(())
258    }
259
260    /// `/genesis`: get genesis file.
261    async fn genesis<AppState>(&self) -> Result<Genesis<AppState>, Error>
262    where
263        AppState: fmt::Debug + Serialize + DeserializeOwned + Send,
264    {
265        Ok(self.perform(genesis::Request::default()).await?.genesis)
266    }
267
268    /// `/net_info`: obtain information about P2P and other network connections.
269    async fn net_info(&self) -> Result<net_info::Response, Error> {
270        self.perform(net_info::Request).await
271    }
272
273    /// `/status`: get Tendermint status including node info, pubkey, latest
274    /// block hash, app hash, block height and time.
275    async fn status(&self) -> Result<status::Response, Error> {
276        self.perform(status::Request).await
277    }
278
279    /// `/broadcast_evidence`: broadcast an evidence.
280    async fn broadcast_evidence(&self, e: Evidence) -> Result<evidence::Response, Error> {
281        self.perform(evidence::Request::new(e)).await
282    }
283
284    /// `/tx`: find transaction by hash.
285    async fn tx(&self, hash: Hash, prove: bool) -> Result<tx::Response, Error> {
286        self.perform(tx::Request::new(hash, prove)).await
287    }
288
289    /// `/tx_search`: search for transactions with their results.
290    async fn tx_search(
291        &self,
292        query: Query,
293        prove: bool,
294        page: u32,
295        per_page: u8,
296        order: Order,
297    ) -> Result<tx_search::Response, Error> {
298        self.perform(tx_search::Request::new(query, prove, page, per_page, order))
299            .await
300    }
301
302    #[cfg(any(
303        feature = "tendermint-rpc-http-client",
304        feature = "tendermint-rpc-websocket-client"
305    ))]
306    /// Poll the `/health` endpoint until it returns a successful result or
307    /// the given `timeout` has elapsed.
308    async fn wait_until_healthy<T>(&self, timeout: T) -> Result<(), Error>
309    where
310        T: Into<core::time::Duration> + Send,
311    {
312        let timeout = timeout.into();
313        let poll_interval = core::time::Duration::from_millis(200);
314        let mut attempts_remaining = timeout.as_millis() / poll_interval.as_millis();
315
316        while self.health().await.is_err() {
317            if attempts_remaining == 0 {
318                return Err(Error::timeout(timeout));
319            }
320
321            attempts_remaining -= 1;
322            tokio::time::sleep(poll_interval).await;
323        }
324
325        Ok(())
326    }
327
328    /// Perform a request against the RPC endpoint.
329    ///
330    /// This method is used by the default implementations of specific
331    /// endpoint methods. The latest protocol dialect is assumed to be invoked.
332    async fn perform<R>(&self, request: R) -> Result<R::Output, Error>
333    where
334        R: SimpleRequest;
335}
336
337#[cfg(not(target_arch = "wasm32"))]
338mod non_wasm {
339    use super::*;
340    use cosmrs::tendermint::abci::response::Info;
341    use std::fmt::Debug;
342    use tendermint_rpc::endpoint::abci_query::AbciQuery;
343    use tendermint_rpc::endpoint::block::Response;
344
345    #[async_trait]
346    impl<C> TendermintRpcClient for C
347    where
348        C: tendermint_rpc::client::Client + Sync,
349    {
350        async fn abci_info(&self) -> Result<Info, Error> {
351            self.abci_info().await
352        }
353
354        async fn abci_query<V>(
355            &self,
356            path: Option<String>,
357            data: V,
358            height: Option<Height>,
359            prove: bool,
360        ) -> Result<AbciQuery, Error>
361        where
362            V: Into<Vec<u8>> + Send,
363        {
364            self.abci_query(path, data, height, prove).await
365        }
366
367        async fn block<H>(&self, height: H) -> Result<Response, Error>
368        where
369            H: Into<Height> + Send,
370        {
371            self.block(height).await
372        }
373
374        async fn block_by_hash(&self, hash: Hash) -> Result<block_by_hash::Response, Error> {
375            self.block_by_hash(hash).await
376        }
377
378        async fn latest_block(&self) -> Result<Response, Error> {
379            self.latest_block().await
380        }
381
382        async fn header<H>(&self, height: H) -> Result<header::Response, Error>
383        where
384            H: Into<Height> + Send,
385        {
386            self.header(height).await
387        }
388
389        async fn header_by_hash(&self, hash: Hash) -> Result<header_by_hash::Response, Error> {
390            self.header_by_hash(hash).await
391        }
392
393        async fn block_results<H>(&self, height: H) -> Result<block_results::Response, Error>
394        where
395            H: Into<Height> + Send,
396        {
397            self.block_results(height).await
398        }
399
400        async fn latest_block_results(&self) -> Result<block_results::Response, Error> {
401            self.latest_block_results().await
402        }
403
404        async fn block_search(
405            &self,
406            query: Query,
407            page: u32,
408            per_page: u8,
409            order: Order,
410        ) -> Result<block_search::Response, Error> {
411            self.block_search(query, page, per_page, order).await
412        }
413
414        async fn blockchain<H>(&self, min: H, max: H) -> Result<blockchain::Response, Error>
415        where
416            H: Into<Height> + Send,
417        {
418            self.blockchain(min, max).await
419        }
420
421        async fn broadcast_tx_async<T>(&self, tx: T) -> Result<broadcast::tx_async::Response, Error>
422        where
423            T: Into<Vec<u8>> + Send,
424        {
425            self.broadcast_tx_async(tx).await
426        }
427
428        async fn broadcast_tx_sync<T>(&self, tx: T) -> Result<broadcast::tx_sync::Response, Error>
429        where
430            T: Into<Vec<u8>> + Send,
431        {
432            self.broadcast_tx_sync(tx).await
433        }
434
435        async fn broadcast_tx_commit<T>(
436            &self,
437            tx: T,
438        ) -> Result<broadcast::tx_commit::Response, Error>
439        where
440            T: Into<Vec<u8>> + Send,
441        {
442            self.broadcast_tx_commit(tx).await
443        }
444
445        async fn commit<H>(&self, height: H) -> Result<commit::Response, Error>
446        where
447            H: Into<Height> + Send,
448        {
449            self.commit(height).await
450        }
451
452        async fn consensus_params<H>(&self, height: H) -> Result<consensus_params::Response, Error>
453        where
454            H: Into<Height> + Send,
455        {
456            self.consensus_params(height).await
457        }
458
459        async fn consensus_state(&self) -> Result<consensus_state::Response, Error> {
460            self.consensus_state().await
461        }
462
463        async fn validators<H>(
464            &self,
465            height: H,
466            paging: Paging,
467        ) -> Result<validators::Response, Error>
468        where
469            H: Into<Height> + Send,
470        {
471            self.validators(height, paging).await
472        }
473
474        async fn latest_consensus_params(&self) -> Result<consensus_params::Response, Error> {
475            self.latest_consensus_params().await
476        }
477
478        async fn latest_commit(&self) -> Result<commit::Response, Error> {
479            self.latest_commit().await
480        }
481
482        async fn health(&self) -> Result<(), Error> {
483            self.health().await
484        }
485
486        async fn genesis<AppState>(&self) -> Result<Genesis<AppState>, Error>
487        where
488            AppState: Debug + Serialize + DeserializeOwned + Send,
489        {
490            self.genesis().await
491        }
492
493        async fn net_info(&self) -> Result<net_info::Response, Error> {
494            self.net_info().await
495        }
496
497        async fn status(&self) -> Result<status::Response, Error> {
498            self.status().await
499        }
500
501        async fn broadcast_evidence(&self, e: Evidence) -> Result<evidence::Response, Error> {
502            self.broadcast_evidence(e).await
503        }
504
505        async fn tx(&self, hash: Hash, prove: bool) -> Result<tx::Response, Error> {
506            self.tx(hash, prove).await
507        }
508
509        async fn tx_search(
510            &self,
511            query: Query,
512            prove: bool,
513            page: u32,
514            per_page: u8,
515            order: Order,
516        ) -> Result<tx_search::Response, Error> {
517            self.tx_search(query, prove, page, per_page, order).await
518        }
519
520        #[cfg(any(
521            feature = "tendermint-rpc-http-client",
522            feature = "tendermint-rpc-websocket-client"
523        ))]
524        async fn wait_until_healthy<T>(&self, timeout: T) -> Result<(), Error>
525        where
526            T: Into<core::time::Duration> + Send,
527        {
528            self.wait_until_healthy(timeout).await
529        }
530
531        async fn perform<R>(&self, request: R) -> Result<R::Output, Error>
532        where
533            R: SimpleRequest,
534        {
535            self.perform(request).await
536        }
537    }
538}