// Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 // TEMPORARY WORKAROUND: // those features are expected as the below should only get activated whenever // the corresponding features in tendermint-rpc are enabled transitively #![allow(unexpected_cfgs)] use async_trait::async_trait; use cosmrs::tendermint::{self, abci, block::Height, evidence::Evidence, Genesis, Hash}; use serde::{de::DeserializeOwned, Serialize}; use std::fmt; use tendermint_rpc::{ endpoint::{validators::DEFAULT_VALIDATORS_PER_PAGE, *}, query::Query, Error, Order, Paging, SimpleRequest, }; #[cfg(feature = "http-client")] use crate::error::TendermintRpcError; #[cfg(feature = "http-client")] use crate::HttpRpcClient; #[cfg(feature = "http-client")] use tendermint_rpc::client::CompatMode; #[cfg(feature = "http-client")] use tendermint_rpc::HttpClientUrl; pub mod reqwest; #[cfg(feature = "http-client")] pub fn http_client(url: U) -> Result where U: TryInto, { HttpRpcClient::builder(url.try_into()?) .compat_mode(CompatMode::V0_37) .build() } // we have to create a sealed trait since `TendermintClient` needs T: Send (due to how async trait is created) // which we can't do in wasm #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] pub trait TendermintRpcClient { /// `/abci_info`: get information about the ABCI application. async fn abci_info(&self) -> Result { Ok(self.perform(abci_info::Request).await?.response) } /// `/abci_query`: query the ABCI application async fn abci_query( &self, path: Option, data: V, height: Option, prove: bool, ) -> Result where V: Into> + Send, { Ok(self .perform(abci_query::Request::new(path, data, height, prove)) .await? .response) } /// `/block`: get block at a given height. async fn block(&self, height: H) -> Result where H: Into + Send, { self.perform(block::Request::new(height.into())).await } /// `/block_by_hash`: get block by hash. async fn block_by_hash( &self, hash: tendermint::Hash, ) -> Result { self.perform(block_by_hash::Request::new(hash)).await } /// `/block`: get the latest block. async fn latest_block(&self) -> Result { self.perform(block::Request::default()).await } /// `/header`: get block header at a given height. async fn header(&self, height: H) -> Result where H: Into + Send, { self.perform(header::Request::new(height.into())).await } /// `/header_by_hash`: get block by hash. async fn header_by_hash( &self, hash: tendermint::Hash, ) -> Result { self.perform(header_by_hash::Request::new(hash)).await } /// `/block_results`: get ABCI results for a block at a particular height. async fn block_results(&self, height: H) -> Result where H: Into + Send, { self.perform(block_results::Request::new(height.into())) .await } /// `/block_results`: get ABCI results for the latest block. async fn latest_block_results(&self) -> Result { self.perform(block_results::Request::default()).await } /// `/block_search`: search for blocks by BeginBlock and EndBlock events. async fn block_search( &self, query: Query, page: u32, per_page: u8, order: Order, ) -> Result { self.perform(block_search::Request::new(query, page, per_page, order)) .await } /// `/blockchain`: get block headers for `min` <= `height` <= `max`. /// /// Block headers are returned in descending order (highest first). /// /// Returns at most 20 items. async fn blockchain(&self, min: H, max: H) -> Result where H: Into + Send, { // TODO(tarcieri): return errors for invalid params before making request? self.perform(blockchain::Request::new(min.into(), max.into())) .await } /// `/broadcast_tx_async`: broadcast a transaction, returning immediately. async fn broadcast_tx_async(&self, tx: T) -> Result where T: Into> + Send, { self.perform(broadcast::tx_async::Request::new(tx)).await } /// `/broadcast_tx_sync`: broadcast a transaction, returning the response /// from `CheckTx`. async fn broadcast_tx_sync(&self, tx: T) -> Result where T: Into> + Send, { self.perform(broadcast::tx_sync::Request::new(tx)).await } /// `/broadcast_tx_commit`: broadcast a transaction, returning the response /// from `DeliverTx`. async fn broadcast_tx_commit(&self, tx: T) -> Result where T: Into> + Send, { self.perform(broadcast::tx_commit::Request::new(tx)).await } /// `/commit`: get block commit at a given height. async fn commit(&self, height: H) -> Result where H: Into + Send, { self.perform(commit::Request::new(height.into())).await } /// `/consensus_params`: get current consensus parameters at the specified /// height. async fn consensus_params(&self, height: H) -> Result where H: Into + Send, { self.perform(consensus_params::Request::new(Some(height.into()))) .await } /// `/consensus_state`: get current consensus state async fn consensus_state(&self) -> Result { self.perform(consensus_state::Request::new()).await } // TODO(thane): Simplify once validators endpoint removes pagination. /// `/validators`: get validators a given height. async fn validators(&self, height: H, paging: Paging) -> Result where H: Into + Send, { let height = height.into(); match paging { Paging::Default => { self.perform(validators::Request::new(Some(height), None, None)) .await } Paging::Specific { page_number, per_page, } => { self.perform(validators::Request::new( Some(height), Some(page_number), Some(per_page), )) .await } Paging::All => { let mut page_num = 1_usize; let mut validators = Vec::new(); let per_page = DEFAULT_VALIDATORS_PER_PAGE.into(); loop { let response = self .perform(validators::Request::new( Some(height), Some(page_num.into()), Some(per_page), )) .await?; validators.extend(response.validators); if validators.len() as i32 == response.total { return Ok(validators::Response::new( response.block_height, validators, response.total, )); } page_num += 1; } } } } /// `/consensus_params`: get the latest consensus parameters. async fn latest_consensus_params(&self) -> Result { self.perform(consensus_params::Request::new(None)).await } /// `/commit`: get the latest block commit async fn latest_commit(&self) -> Result { self.perform(commit::Request::default()).await } /// `/health`: get node health. /// /// Returns empty result (200 OK) on success, no response in case of an error. async fn health(&self) -> Result<(), Error> { self.perform(health::Request).await?; Ok(()) } /// `/genesis`: get genesis file. async fn genesis(&self) -> Result, Error> where AppState: fmt::Debug + Serialize + DeserializeOwned + Send, { Ok(self.perform(genesis::Request::default()).await?.genesis) } /// `/net_info`: obtain information about P2P and other network connections. async fn net_info(&self) -> Result { self.perform(net_info::Request).await } /// `/status`: get Tendermint status including node info, pubkey, latest /// block hash, app hash, block height and time. async fn status(&self) -> Result { self.perform(status::Request).await } /// `/broadcast_evidence`: broadcast an evidence. async fn broadcast_evidence(&self, e: Evidence) -> Result { self.perform(evidence::Request::new(e)).await } /// `/tx`: find transaction by hash. async fn tx(&self, hash: Hash, prove: bool) -> Result { self.perform(tx::Request::new(hash, prove)).await } /// `/tx_search`: search for transactions with their results. async fn tx_search( &self, query: Query, prove: bool, page: u32, per_page: u8, order: Order, ) -> Result { self.perform(tx_search::Request::new(query, prove, page, per_page, order)) .await } #[cfg(any( feature = "tendermint-rpc-http-client", feature = "tendermint-rpc-websocket-client" ))] /// Poll the `/health` endpoint until it returns a successful result or /// the given `timeout` has elapsed. async fn wait_until_healthy(&self, timeout: T) -> Result<(), Error> where T: Into + Send, { let timeout = timeout.into(); let poll_interval = core::time::Duration::from_millis(200); let mut attempts_remaining = timeout.as_millis() / poll_interval.as_millis(); while self.health().await.is_err() { if attempts_remaining == 0 { return Err(Error::timeout(timeout)); } attempts_remaining -= 1; tokio::time::sleep(poll_interval).await; } Ok(()) } /// Perform a request against the RPC endpoint. /// /// This method is used by the default implementations of specific /// endpoint methods. The latest protocol dialect is assumed to be invoked. async fn perform(&self, request: R) -> Result where R: SimpleRequest; } #[cfg(not(target_arch = "wasm32"))] mod non_wasm { use super::*; use cosmrs::tendermint::abci::response::Info; use std::fmt::Debug; use tendermint_rpc::endpoint::abci_query::AbciQuery; use tendermint_rpc::endpoint::block::Response; #[async_trait] impl TendermintRpcClient for C where C: tendermint_rpc::client::Client + Sync, { async fn abci_info(&self) -> Result { self.abci_info().await } async fn abci_query( &self, path: Option, data: V, height: Option, prove: bool, ) -> Result where V: Into> + Send, { self.abci_query(path, data, height, prove).await } async fn block(&self, height: H) -> Result where H: Into + Send, { self.block(height).await } async fn block_by_hash(&self, hash: Hash) -> Result { self.block_by_hash(hash).await } async fn latest_block(&self) -> Result { self.latest_block().await } async fn header(&self, height: H) -> Result where H: Into + Send, { self.header(height).await } async fn header_by_hash(&self, hash: Hash) -> Result { self.header_by_hash(hash).await } async fn block_results(&self, height: H) -> Result where H: Into + Send, { self.block_results(height).await } async fn latest_block_results(&self) -> Result { self.latest_block_results().await } async fn block_search( &self, query: Query, page: u32, per_page: u8, order: Order, ) -> Result { self.block_search(query, page, per_page, order).await } async fn blockchain(&self, min: H, max: H) -> Result where H: Into + Send, { self.blockchain(min, max).await } async fn broadcast_tx_async(&self, tx: T) -> Result where T: Into> + Send, { self.broadcast_tx_async(tx).await } async fn broadcast_tx_sync(&self, tx: T) -> Result where T: Into> + Send, { self.broadcast_tx_sync(tx).await } async fn broadcast_tx_commit( &self, tx: T, ) -> Result where T: Into> + Send, { self.broadcast_tx_commit(tx).await } async fn commit(&self, height: H) -> Result where H: Into + Send, { self.commit(height).await } async fn consensus_params(&self, height: H) -> Result where H: Into + Send, { self.consensus_params(height).await } async fn consensus_state(&self) -> Result { self.consensus_state().await } async fn validators( &self, height: H, paging: Paging, ) -> Result where H: Into + Send, { self.validators(height, paging).await } async fn latest_consensus_params(&self) -> Result { self.latest_consensus_params().await } async fn latest_commit(&self) -> Result { self.latest_commit().await } async fn health(&self) -> Result<(), Error> { self.health().await } async fn genesis(&self) -> Result, Error> where AppState: Debug + Serialize + DeserializeOwned + Send, { self.genesis().await } async fn net_info(&self) -> Result { self.net_info().await } async fn status(&self) -> Result { self.status().await } async fn broadcast_evidence(&self, e: Evidence) -> Result { self.broadcast_evidence(e).await } async fn tx(&self, hash: Hash, prove: bool) -> Result { self.tx(hash, prove).await } async fn tx_search( &self, query: Query, prove: bool, page: u32, per_page: u8, order: Order, ) -> Result { self.tx_search(query, prove, page, per_page, order).await } #[cfg(any( feature = "tendermint-rpc-http-client", feature = "tendermint-rpc-websocket-client" ))] async fn wait_until_healthy(&self, timeout: T) -> Result<(), Error> where T: Into + Send, { self.wait_until_healthy(timeout).await } async fn perform(&self, request: R) -> Result where R: SimpleRequest, { self.perform(request).await } } }