diff --git a/common/client-libs/validator-client/examples/offline_signing.rs b/common/client-libs/validator-client/examples/offline_signing.rs index baa2d10035..0e5bce5681 100644 --- a/common/client-libs/validator-client/examples/offline_signing.rs +++ b/common/client-libs/validator-client/examples/offline_signing.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use cosmrs::bank::MsgSend; -use cosmrs::rpc::HttpClient; use cosmrs::tx::Msg; use cosmrs::{tx, AccountId, Coin, Denom}; +use nym_validator_client::http_client; use nym_validator_client::nyxd::CosmWasmClient; use nym_validator_client::signing::direct_wallet::DirectSecp256k1HdWallet; use nym_validator_client::signing::tx_signer::TxSigner; @@ -27,7 +27,7 @@ async fn main() { // possibly remote client that doesn't do ANY signing // (only broadcasts + queries for sequence numbers) - let broadcaster = HttpClient::new(validator).unwrap(); + let broadcaster = http_client(validator).unwrap(); // get signer information let sequence_response = broadcaster.get_sequence(&signer_address).await.unwrap(); diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index e70cbdaf2f..6352dabff0 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -26,6 +26,8 @@ pub use nym_mixnet_contract_common::{ // re-export the type to not break existing imports pub use crate::coconut::CoconutApiClient; +#[cfg(feature = "http-client")] +use crate::rpc::http_client; #[cfg(feature = "http-client")] use crate::{DirectSigningHttpRpcValidatorClient, HttpRpcClient, QueryHttpRpcValidatorClient}; @@ -95,7 +97,7 @@ impl Client { config: Config, mnemonic: bip39::Mnemonic, ) -> Result { - let rpc_client = HttpRpcClient::new(config.nyxd_url.as_str())?; + let rpc_client = http_client(config.nyxd_url.as_str())?; let prefix = &config.nyxd_config.chain_details.bech32_account_prefix; let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic); @@ -126,7 +128,7 @@ impl Client { #[cfg(feature = "http-client")] impl Client { pub fn new_query(config: Config) -> Result { - let rpc_client = HttpRpcClient::new(config.nyxd_url.as_str())?; + let rpc_client = http_client(config.nyxd_url.as_str())?; Ok(Self::new_with_rpc_client(config, rpc_client)) } diff --git a/common/client-libs/validator-client/src/lib.rs b/common/client-libs/validator-client/src/lib.rs index c6391673a0..b3dca0a473 100644 --- a/common/client-libs/validator-client/src/lib.rs +++ b/common/client-libs/validator-client/src/lib.rs @@ -20,6 +20,8 @@ pub use nym_api_requests::*; #[cfg(feature = "http-client")] pub use cosmrs::rpc::HttpClient as HttpRpcClient; +#[cfg(feature = "http-client")] +pub use rpc::http_client; // some type aliasing diff --git a/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs index 7ebff2ef16..ab78a8c418 100644 --- a/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/mod.rs @@ -3,18 +3,25 @@ use crate::nyxd::cosmwasm_client::client_traits::{CosmWasmClient, SigningCosmWasmClient}; use crate::nyxd::error::NyxdError; -use crate::nyxd::{Config, GasPrice}; +use crate::nyxd::{Config, GasPrice, Hash, Height}; use crate::rpc::TendermintRpcClient; use crate::signing::{ signer::{NoSigner, OfflineSigner}, AccountData, }; use async_trait::async_trait; -use tendermint_rpc::{Error as TendermintRpcError, SimpleRequest}; +use cosmrs::tendermint::{abci, evidence::Evidence, Genesis}; +use cosmrs::tx::{Raw, SignDoc}; +use serde::{de::DeserializeOwned, Serialize}; +use std::fmt::Debug; +use tendermint_rpc::endpoint::*; +use tendermint_rpc::query::Query; +use tendermint_rpc::{Error as TendermintRpcError, Order, Paging, SimpleRequest}; +#[cfg(feature = "http-client")] +use crate::http_client; #[cfg(feature = "http-client")] use cosmrs::rpc::{HttpClient, HttpClientUrl}; -use cosmrs::tx::{Raw, SignDoc}; pub mod client_traits; mod helpers; @@ -73,7 +80,7 @@ impl MaybeSigningClient { where U: TryInto, { - self.client = HttpClient::new(new_endpoint)?; + self.client = http_client(new_endpoint)?; Ok(()) } } @@ -85,6 +92,216 @@ where C: TendermintRpcClient + Send + Sync, S: Send + Sync, { + async fn abci_info(&self) -> Result { + self.client.abci_info().await + } + + async fn abci_query( + &self, + path: Option, + data: V, + height: Option, + prove: bool, + ) -> Result + where + V: Into> + Send, + { + self.client.abci_query(path, data, height, prove).await + } + + async fn block(&self, height: H) -> Result + where + H: Into + Send, + { + self.client.block(height).await + } + + async fn block_by_hash( + &self, + hash: Hash, + ) -> Result { + self.client.block_by_hash(hash).await + } + + async fn latest_block(&self) -> Result { + self.client.latest_block().await + } + + async fn header(&self, height: H) -> Result + where + H: Into + Send, + { + self.client.header(height).await + } + + async fn header_by_hash( + &self, + hash: Hash, + ) -> Result { + self.client.header_by_hash(hash).await + } + + async fn block_results( + &self, + height: H, + ) -> Result + where + H: Into + Send, + { + self.client.block_results(height).await + } + + async fn latest_block_results(&self) -> Result { + self.client.latest_block_results().await + } + + async fn block_search( + &self, + query: Query, + page: u32, + per_page: u8, + order: Order, + ) -> Result { + self.client.block_search(query, page, per_page, order).await + } + + async fn blockchain( + &self, + min: H, + max: H, + ) -> Result + where + H: Into + Send, + { + self.client.blockchain(min, max).await + } + + async fn broadcast_tx_async( + &self, + tx: T, + ) -> Result + where + T: Into> + Send, + { + self.client.broadcast_tx_async(tx).await + } + + async fn broadcast_tx_sync( + &self, + tx: T, + ) -> Result + where + T: Into> + Send, + { + self.client.broadcast_tx_sync(tx).await + } + + async fn broadcast_tx_commit( + &self, + tx: T, + ) -> Result + where + T: Into> + Send, + { + self.client.broadcast_tx_commit(tx).await + } + + async fn commit(&self, height: H) -> Result + where + H: Into + Send, + { + self.client.commit(height).await + } + + async fn consensus_params( + &self, + height: H, + ) -> Result + where + H: Into + Send, + { + self.client.consensus_params(height).await + } + + async fn consensus_state(&self) -> Result { + self.client.consensus_state().await + } + + async fn validators( + &self, + height: H, + paging: Paging, + ) -> Result + where + H: Into + Send, + { + self.client.validators(height, paging).await + } + + async fn latest_consensus_params( + &self, + ) -> Result { + self.client.latest_consensus_params().await + } + + async fn latest_commit(&self) -> Result { + self.client.latest_commit().await + } + + async fn health(&self) -> Result<(), TendermintRpcError> { + self.client.health().await + } + + async fn genesis(&self) -> Result, TendermintRpcError> + where + AppState: Debug + Serialize + DeserializeOwned + Send, + { + self.client.genesis().await + } + + async fn net_info(&self) -> Result { + self.client.net_info().await + } + + async fn status(&self) -> Result { + self.client.status().await + } + + async fn broadcast_evidence( + &self, + e: Evidence, + ) -> Result { + self.client.broadcast_evidence(e).await + } + + async fn tx(&self, hash: Hash, prove: bool) -> Result { + self.client.tx(hash, prove).await + } + + async fn tx_search( + &self, + query: Query, + prove: bool, + page: u32, + per_page: u8, + order: Order, + ) -> Result { + self.client + .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.client.wait_until_healthy(timeout).await + } + async fn perform(&self, request: R) -> Result where R: SimpleRequest, diff --git a/common/client-libs/validator-client/src/nyxd/mod.rs b/common/client-libs/validator-client/src/nyxd/mod.rs index 16027a1970..10cca6de7e 100644 --- a/common/client-libs/validator-client/src/nyxd/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/mod.rs @@ -9,19 +9,25 @@ use crate::nyxd::cosmwasm_client::types::{ use crate::nyxd::cosmwasm_client::MaybeSigningClient; use crate::nyxd::error::NyxdError; use crate::nyxd::fee::DEFAULT_SIMULATED_GAS_MULTIPLIER; +use crate::signing::direct_wallet::DirectSecp256k1HdWallet; use crate::signing::signer::NoSigner; use crate::signing::signer::OfflineSigner; use crate::signing::tx_signer::TxSigner; use crate::signing::AccountData; +use crate::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient, ReqwestRpcClient}; use async_trait::async_trait; use cosmrs::cosmwasm; +use cosmrs::tendermint::{abci, evidence::Evidence, Genesis}; use cosmrs::tx::{Msg, Raw, SignDoc}; use cosmwasm_std::Addr; use nym_network_defaults::{ChainDetails, NymNetworkDetails}; -use serde::Serialize; +use serde::{de::DeserializeOwned, Serialize}; +use std::fmt::Debug; use std::time::SystemTime; use tendermint_rpc::endpoint::block::Response as BlockResponse; -use tendermint_rpc::Error as TendermintRpcError; +use tendermint_rpc::endpoint::*; +use tendermint_rpc::{Error as TendermintRpcError, Order}; +use url::Url; pub use crate::nyxd::cosmwasm_client::client_traits::{CosmWasmClient, SigningCosmWasmClient}; pub use crate::nyxd::fee::Fee; @@ -45,14 +51,13 @@ pub use tendermint_rpc::{ }; pub use tendermint_rpc::{Request, Response, SimpleRequest}; -// #[cfg(feature = "http-client")] -use crate::signing::direct_wallet::DirectSecp256k1HdWallet; +#[cfg(feature = "http-client")] +use crate::http_client; #[cfg(feature = "http-client")] use crate::{DirectSigningHttpRpcNyxdClient, QueryHttpRpcNyxdClient}; -use crate::{DirectSigningReqwestRpcNyxdClient, QueryReqwestRpcNyxdClient, ReqwestRpcClient}; #[cfg(feature = "http-client")] use cosmrs::rpc::{HttpClient, HttpClientUrl}; -use url::Url; +use tendermint_rpc::query::Query; pub mod coin; pub mod contract_traits; @@ -97,7 +102,7 @@ impl NyxdClient { where U: TryInto, { - let client = HttpClient::new(endpoint)?; + let client = http_client(endpoint)?; Ok(NyxdClient { client: MaybeSigningClient::new(client, (&config).into()), @@ -140,7 +145,7 @@ impl NyxdClient { where U: TryInto, { - let client = HttpClient::new(endpoint)?; + let client = http_client(endpoint)?; let prefix = &config.chain_details.bech32_account_prefix; let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic); @@ -568,6 +573,216 @@ where C: TendermintRpcClient + Send + Sync, S: Send + Sync, { + async fn abci_info(&self) -> Result { + self.client.abci_info().await + } + + async fn abci_query( + &self, + path: Option, + data: V, + height: Option, + prove: bool, + ) -> Result + where + V: Into> + Send, + { + self.client.abci_query(path, data, height, prove).await + } + + async fn block(&self, height: H) -> Result + where + H: Into + Send, + { + self.client.block(height).await + } + + async fn block_by_hash( + &self, + hash: Hash, + ) -> Result { + self.client.block_by_hash(hash).await + } + + async fn latest_block(&self) -> Result { + self.client.latest_block().await + } + + async fn header(&self, height: H) -> Result + where + H: Into + Send, + { + self.client.header(height).await + } + + async fn header_by_hash( + &self, + hash: Hash, + ) -> Result { + self.client.header_by_hash(hash).await + } + + async fn block_results( + &self, + height: H, + ) -> Result + where + H: Into + Send, + { + self.client.block_results(height).await + } + + async fn latest_block_results(&self) -> Result { + self.client.latest_block_results().await + } + + async fn block_search( + &self, + query: Query, + page: u32, + per_page: u8, + order: Order, + ) -> Result { + self.client.block_search(query, page, per_page, order).await + } + + async fn blockchain( + &self, + min: H, + max: H, + ) -> Result + where + H: Into + Send, + { + self.client.blockchain(min, max).await + } + + async fn broadcast_tx_async( + &self, + tx: T, + ) -> Result + where + T: Into> + Send, + { + TendermintRpcClient::broadcast_tx_async(&self.client, tx).await + } + + async fn broadcast_tx_sync( + &self, + tx: T, + ) -> Result + where + T: Into> + Send, + { + TendermintRpcClient::broadcast_tx_sync(&self.client, tx).await + } + + async fn broadcast_tx_commit( + &self, + tx: T, + ) -> Result + where + T: Into> + Send, + { + TendermintRpcClient::broadcast_tx_commit(&self.client, tx).await + } + + async fn commit(&self, height: H) -> Result + where + H: Into + Send, + { + self.client.commit(height).await + } + + async fn consensus_params( + &self, + height: H, + ) -> Result + where + H: Into + Send, + { + self.client.consensus_params(height).await + } + + async fn consensus_state(&self) -> Result { + self.client.consensus_state().await + } + + async fn validators( + &self, + height: H, + paging: Paging, + ) -> Result + where + H: Into + Send, + { + self.client.validators(height, paging).await + } + + async fn latest_consensus_params( + &self, + ) -> Result { + self.client.latest_consensus_params().await + } + + async fn latest_commit(&self) -> Result { + self.client.latest_commit().await + } + + async fn health(&self) -> Result<(), TendermintRpcError> { + self.client.health().await + } + + async fn genesis(&self) -> Result, TendermintRpcError> + where + AppState: Debug + Serialize + DeserializeOwned + Send, + { + self.client.genesis().await + } + + async fn net_info(&self) -> Result { + self.client.net_info().await + } + + async fn status(&self) -> Result { + self.client.status().await + } + + async fn broadcast_evidence( + &self, + e: Evidence, + ) -> Result { + self.client.broadcast_evidence(e).await + } + + async fn tx(&self, hash: Hash, prove: bool) -> Result { + self.client.tx(hash, prove).await + } + + async fn tx_search( + &self, + query: Query, + prove: bool, + page: u32, + per_page: u8, + order: Order, + ) -> Result { + self.client + .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.client.wait_until_healthy(timeout).await + } + async fn perform(&self, request: R) -> Result where R: SimpleRequest, diff --git a/common/client-libs/validator-client/src/rpc/mod.rs b/common/client-libs/validator-client/src/rpc/mod.rs index f483f1067c..fd3f7991ba 100644 --- a/common/client-libs/validator-client/src/rpc/mod.rs +++ b/common/client-libs/validator-client/src/rpc/mod.rs @@ -11,8 +11,27 @@ use tendermint_rpc::{ 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_34) + .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))] diff --git a/common/client-libs/validator-client/src/rpc/reqwest.rs b/common/client-libs/validator-client/src/rpc/reqwest.rs index 9c8411d666..ffad7dd815 100644 --- a/common/client-libs/validator-client/src/rpc/reqwest.rs +++ b/common/client-libs/validator-client/src/rpc/reqwest.rs @@ -3,12 +3,32 @@ use crate::rpc::TendermintRpcClient; use async_trait::async_trait; +use cosmrs::tendermint::{block::Height, evidence::Evidence, Hash}; use reqwest::header::HeaderMap; use reqwest::{header, RequestBuilder}; -use tendermint_rpc::{Error, Response, SimpleRequest}; +use tendermint_rpc::{ + client::CompatMode, + dialect::{self, Dialect}, + endpoint::{self, *}, + query::Query, + Error, Order, Response, SimpleRequest, +}; + use url::Url; +// copied macro from tendermint-rpc crate because that's exactly what we have to do here too +macro_rules! perform_with_compat { + ($self:expr, $request:expr) => {{ + let request = $request; + match $self.compat { + CompatMode::V0_37 => $self.perform_v0_37(request).await, + CompatMode::V0_34 => $self.perform_v0_34(request).await, + } + }}; +} + pub struct ReqwestRpcClient { + compat: CompatMode, inner: reqwest::Client, url: Url, } @@ -16,12 +36,21 @@ pub struct ReqwestRpcClient { impl ReqwestRpcClient { pub fn new(url: Url) -> Self { ReqwestRpcClient { + compat: CompatMode::V0_34, inner: reqwest::Client::new(), url, } } - fn build_request(&self, request: R) -> RequestBuilder { + pub fn set_compat_mode(&mut self, compat: CompatMode) { + self.compat = compat; + } + + fn build_request(&self, request: R) -> RequestBuilder + where + R: SimpleRequest, + S: Dialect, + { let mut headers = HeaderMap::new(); headers.insert(header::CONTENT_TYPE, "application/json".parse().unwrap()); headers.insert( @@ -39,6 +68,38 @@ impl ReqwestRpcClient { .body(request.into_json().into_bytes()) .headers(headers) } + + async fn perform_request(&self, request: R) -> Result + where + R: SimpleRequest, + S: Dialect, + { + let request = self.build_request(request); + // that's extremely unfortunate. the trait requires returning tendermint rpc error so we have to make best effort error mapping + let response = request + .send() + .await + .map_err(TendermintRpcErrorMap::into_rpc_err)?; + let bytes = response + .bytes() + .await + .map_err(TendermintRpcErrorMap::into_rpc_err)?; + R::Response::from_string(bytes).map(Into::into) + } + + async fn perform_v0_34(&self, request: R) -> Result + where + R: SimpleRequest, + { + self.perform_request(request).await + } + + async fn perform_v0_37(&self, request: R) -> Result + where + R: SimpleRequest, + { + self.perform_request(request).await + } } trait TendermintRpcErrorMap { @@ -58,17 +119,81 @@ impl TendermintRpcClient for ReqwestRpcClient { where R: SimpleRequest, { - let request = self.build_request(request); - // that's extremely unfortunate. the trait requires returning tendermint rpc error so we have to make best effort error mapping - let response = request - .send() - .await - .map_err(TendermintRpcErrorMap::into_rpc_err)?; - let bytes = response - .bytes() - .await - .map_err(TendermintRpcErrorMap::into_rpc_err)?; - R::Response::from_string(bytes).map(Into::into) + self.perform_request(request).await + } + + async fn block_results(&self, height: H) -> Result + where + H: Into + Send, + { + perform_with_compat!(self, block_results::Request::new(height.into())) + } + + async fn latest_block_results(&self) -> Result { + perform_with_compat!(self, block_results::Request::default()) + } + + async fn header(&self, height: H) -> Result + where + H: Into + Send, + { + let height = height.into(); + match self.compat { + CompatMode::V0_37 => self.perform(endpoint::header::Request::new(height)).await, + CompatMode::V0_34 => { + // Back-fill with a request to /block endpoint and + // taking just the header from the response. + let resp = self.perform_v0_34(block::Request::new(height)).await?; + Ok(resp.into()) + } + } + } + + async fn header_by_hash(&self, hash: Hash) -> Result { + match self.compat { + CompatMode::V0_37 => self.perform(header_by_hash::Request::new(hash)).await, + CompatMode::V0_34 => { + // Back-fill with a request to /block_by_hash endpoint and + // taking just the header from the response. + let resp = self + .perform_v0_34(block_by_hash::Request::new(hash)) + .await?; + Ok(resp.into()) + } + } + } + + /// `/broadcast_evidence`: broadcast an evidence. + async fn broadcast_evidence(&self, e: Evidence) -> Result { + match self.compat { + CompatMode::V0_37 => self.perform(evidence::Request::new(e)).await, + CompatMode::V0_34 => self.perform_v0_34(evidence::Request::new(e)).await, + } + } + + async fn tx(&self, hash: Hash, prove: bool) -> Result { + perform_with_compat!(self, tx::Request::new(hash, prove)) + } + + async fn tx_search( + &self, + query: Query, + prove: bool, + page: u32, + per_page: u8, + order: Order, + ) -> Result { + perform_with_compat!( + self, + tx_search::Request::new(query, prove, page, per_page, order) + ) + } + + async fn broadcast_tx_commit(&self, tx: T) -> Result + where + T: Into> + Send, + { + perform_with_compat!(self, broadcast::tx_commit::Request::new(tx)) } } diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index 47977894ba..612a9d8149 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -3991,7 +3991,7 @@ dependencies = [ [[package]] name = "nym-connect" -version = "1.1.17" +version = "1.1.18" dependencies = [ "anyhow", "bip39",