diff --git a/Cargo.lock b/Cargo.lock index 559f45feb0..a0e7935434 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -556,7 +556,7 @@ dependencies = [ "primitives", "ripemd160", "serialization", - "sha-1 0.9.8", + "sha-1", "sha2 0.9.9", "sha3", "siphasher", @@ -1086,6 +1086,8 @@ dependencies = [ "gstuff", "hex 0.4.2", "http 0.2.7", + "hyper", + "hyper-rustls", "itertools", "js-sys", "jsonrpc-core 8.0.1", @@ -1137,10 +1139,11 @@ dependencies = [ "spl-associated-token-account", "spl-token", "spv_validation", + "tendermint-config", "tendermint-rpc", "tiny-bip39", "tokio", - "tokio-rustls 0.23.2", + "tokio-rustls", "tonic", "tonic-build", "utxo_signer", @@ -1150,7 +1153,7 @@ dependencies = [ "wasm-bindgen-test", "web-sys", "web3", - "webpki-roots 0.22.3", + "webpki-roots", "winapi", "zbase32", "zcash_client_backend", @@ -1214,7 +1217,7 @@ dependencies = [ "http 0.2.7", "http-body 0.1.0", "hyper", - "hyper-rustls 0.23.0", + "hyper-rustls", "itertools", "js-sys", "lazy_static", @@ -1305,16 +1308,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" -[[package]] -name = "core-foundation" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -1360,7 +1353,6 @@ dependencies = [ "serde_json", "subtle-encoding", "tendermint", - "tendermint-rpc", "thiserror", ] @@ -1654,15 +1646,6 @@ dependencies = [ "crypto_api", ] -[[package]] -name = "ct-logs" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" -dependencies = [ - "sct 0.6.0", -] - [[package]] name = "ctor" version = "0.1.16" @@ -2875,31 +2858,6 @@ dependencies = [ "hashbrown 0.9.1", ] -[[package]] -name = "headers" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" -dependencies = [ - "base64 0.13.0", - "bitflags", - "bytes 1.1.0", - "headers-core", - "http 0.2.7", - "httpdate", - "mime", - "sha-1 0.10.0", -] - -[[package]] -name = "headers-core" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" -dependencies = [ - "http 0.2.7", -] - [[package]] name = "heck" version = "0.4.0" @@ -3144,43 +3102,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-proxy" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca815a891b24fdfb243fa3239c86154392b0953ee584aa1a2a1f66d20cbe75cc" -dependencies = [ - "bytes 1.1.0", - "futures 0.3.15", - "headers", - "http 0.2.7", - "hyper", - "hyper-rustls 0.22.1", - "rustls-native-certs", - "tokio", - "tokio-rustls 0.22.0", - "tower-service", - "webpki 0.21.3", -] - -[[package]] -name = "hyper-rustls" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" -dependencies = [ - "ct-logs", - "futures-util", - "hyper", - "log 0.4.14", - "rustls 0.19.1", - "rustls-native-certs", - "tokio", - "tokio-rustls 0.22.0", - "webpki 0.21.3", - "webpki-roots 0.21.1", -] - [[package]] name = "hyper-rustls" version = "0.23.0" @@ -3191,8 +3112,8 @@ dependencies = [ "hyper", "rustls 0.20.4", "tokio", - "tokio-rustls 0.23.2", - "webpki-roots 0.22.3", + "tokio-rustls", + "webpki-roots", ] [[package]] @@ -3845,7 +3766,7 @@ dependencies = [ "rw-stream-sink", "soketto", "url 2.2.2", - "webpki-roots 0.22.3", + "webpki-roots", ] [[package]] @@ -4587,7 +4508,7 @@ dependencies = [ "derive_more", "futures 0.3.15", "hyper", - "hyper-rustls 0.23.0", + "hyper-rustls", "itertools", "metrics", "metrics-exporter-prometheus", @@ -4964,12 +4885,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - [[package]] name = "ordered-float" version = "2.10.0" @@ -5196,7 +5111,7 @@ dependencies = [ "libc", "redox_syscall 0.2.10", "smallvec 1.6.1", - "windows-sys 0.32.0", + "windows-sys", ] [[package]] @@ -6046,7 +5961,7 @@ dependencies = [ "http 0.2.7", "http-body 0.4.4", "hyper", - "hyper-rustls 0.23.0", + "hyper-rustls", "ipnet", "js-sys", "lazy_static", @@ -6060,12 +5975,12 @@ dependencies = [ "serde_json", "serde_urlencoded", "tokio", - "tokio-rustls 0.23.2", + "tokio-rustls", "url 2.2.2", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.22.3", + "webpki-roots", "winreg", ] @@ -6310,18 +6225,6 @@ dependencies = [ "webpki 0.22.0", ] -[[package]] -name = "rustls-native-certs" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" -dependencies = [ - "openssl-probe", - "rustls 0.19.1", - "schannel", - "security-framework", -] - [[package]] name = "rustls-pemfile" version = "0.2.1" @@ -6395,16 +6298,6 @@ dependencies = [ "syn 1.0.95", ] -[[package]] -name = "schannel" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" -dependencies = [ - "lazy_static", - "windows-sys 0.36.1", -] - [[package]] name = "scoped-tls" version = "1.0.0" @@ -6522,29 +6415,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "security-framework" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "semver" version = "0.9.0" @@ -6714,17 +6584,6 @@ dependencies = [ "opaque-debug 0.3.0", ] -[[package]] -name = "sha-1" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" -dependencies = [ - "cfg-if 1.0.0", - "cpufeatures 0.2.1", - "digest 0.10.3", -] - [[package]] name = "sha1" version = "0.6.0" @@ -6937,7 +6796,7 @@ dependencies = [ "httparse", "log 0.4.14", "rand 0.8.4", - "sha-1 0.9.8", + "sha-1", ] [[package]] @@ -8125,15 +7984,9 @@ version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13e63f57ee05a1e927887191c76d1b139de9fa40c180b9f8727ee44377242a6" dependencies = [ - "async-trait", "bytes 1.1.0", "flex-error", - "futures 0.3.15", "getrandom 0.2.6", - "http 0.2.7", - "hyper", - "hyper-proxy", - "hyper-rustls 0.22.1", "peg", "pin-project 1.0.10", "serde", @@ -8145,8 +7998,6 @@ dependencies = [ "tendermint-proto", "thiserror", "time 0.3.11", - "tokio", - "tracing", "url 2.2.2", "uuid 0.8.2", "walkdir", @@ -8403,17 +8254,6 @@ dependencies = [ "syn 1.0.95", ] -[[package]] -name = "tokio-rustls" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" -dependencies = [ - "rustls 0.19.1", - "tokio", - "webpki 0.21.3", -] - [[package]] name = "tokio-rustls" version = "0.23.2" @@ -8508,7 +8348,7 @@ dependencies = [ "prost-derive", "rustls-pemfile 1.0.0", "tokio", - "tokio-rustls 0.23.2", + "tokio-rustls", "tokio-stream", "tokio-util 0.7.2", "tower", @@ -8516,7 +8356,7 @@ dependencies = [ "tower-service", "tracing", "tracing-futures", - "webpki-roots 0.22.3", + "webpki-roots", ] [[package]] @@ -8746,12 +8586,12 @@ dependencies = [ "log 0.4.14", "rand 0.8.4", "rustls 0.20.4", - "sha-1 0.9.8", + "sha-1", "thiserror", "url 2.2.2", "utf-8", "webpki 0.22.0", - "webpki-roots 0.22.3", + "webpki-roots", ] [[package]] @@ -9184,15 +9024,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "webpki-roots" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" -dependencies = [ - "webpki 0.21.3", -] - [[package]] name = "webpki-roots" version = "0.22.3" @@ -9264,24 +9095,11 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" dependencies = [ - "windows_aarch64_msvc 0.32.0", - "windows_i686_gnu 0.32.0", - "windows_i686_msvc 0.32.0", - "windows_x86_64_gnu 0.32.0", - "windows_x86_64_msvc 0.32.0", -] - -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", ] [[package]] @@ -9290,60 +9108,30 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - [[package]] name = "windows_i686_gnu" version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - [[package]] name = "windows_i686_msvc" version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - [[package]] name = "windows_x86_64_gnu" version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - [[package]] name = "windows_x86_64_msvc" version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - [[package]] name = "winreg" version = "0.7.0" diff --git a/mm2src/coins/Cargo.toml b/mm2src/coins/Cargo.toml index cec7de3a37..d624828c0f 100644 --- a/mm2src/coins/Cargo.toml +++ b/mm2src/coins/Cargo.toml @@ -25,6 +25,7 @@ bytes = "0.4" cfg-if = "1.0" chain = { path = "../mm2_bitcoin/chain" } common = { path = "../common" } +cosmrs = { version = "0.7" } crossbeam = "0.7" crypto = { path = "../crypto" } db_common = { path = "../db_common" } @@ -80,6 +81,8 @@ spv_validation = { path = "../mm2_bitcoin/spv_validation" } sha2 = "0.9" sha3 = "0.9" utxo_signer = { path = "utxo_signer" } +# using the same version as cosmrs +tendermint-rpc = { version = "=0.23.7", default-features = false } tiny-bip39 = "0.8.0" uuid = { version = "0.7", features = ["serde", "v4"] } # One of web3 dependencies is the old `tokio-uds 0.1.7` which fails cross-compiling to ARM. @@ -95,21 +98,21 @@ spl-token = { version = "3" } spl-associated-token-account = "1" [target.'cfg(target_arch = "wasm32")'.dependencies] -cosmrs = { version = "0.7" } js-sys = { version = "0.3.27" } mm2_db = { path = "../mm2_db" } mm2_test_helpers = { path = "../mm2_test_helpers" } -# using the same version as cosmrs -tendermint-rpc = { version = "=0.23.7", default-features = false } wasm-bindgen = { version = "0.2.50", features = ["nightly"] } wasm-bindgen-futures = { version = "0.4.1" } wasm-bindgen-test = { version = "0.3.2" } web-sys = { version = "0.3.55", features = ["console", "Headers", "Request", "RequestInit", "RequestMode", "Response", "Window"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -cosmrs = { version = "0.7", features = ["rpc"] } dirs = { version = "1" } bitcoin = "0.28.1" +hyper = { version = "0.14.11", features = ["client", "http2", "server", "tcp"] } +# using webpki-tokio to avoid rejecting valid certificates +# got "invalid certificate: UnknownIssuer" for https://ropsten.infura.io on iOS using default-features +hyper-rustls = { version = "0.23", default-features = false, features = ["http1", "http2", "webpki-tokio"] } lightning = "0.0.110" lightning-background-processor = "0.0.110" lightning-invoice = { version = "0.18.0", features = ["serde"] } @@ -118,6 +121,7 @@ lightning-rapid-gossip-sync = "0.0.110" rust-ini = { version = "0.13" } rustls = { version = "0.20", features = ["dangerous_configuration"] } secp256k1v22 = { version = "0.22", package = "secp256k1" } +tendermint-config = { version = "0.23.7", default-features = false } tokio = { version = "1.7" } tokio-rustls = { version = "0.23" } tonic = { version = "0.7", features = ["tls", "tls-webpki-roots", "compression"] } diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index d9a1d658a1..b817810081 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -196,8 +196,8 @@ impl From for ValidatePaymentError { } #[cfg(not(target_arch = "wasm32"))] -impl From for TendermintCoinRpcError { - fn from(err: cosmrs::rpc::Error) -> Self { TendermintCoinRpcError::PerformError(err.to_string()) } +impl From for TendermintCoinRpcError { + fn from(err: tendermint_rpc::Error) -> Self { TendermintCoinRpcError::PerformError(err.to_string()) } } #[cfg(target_arch = "wasm32")] diff --git a/mm2src/coins/tendermint/tendermint_native_rpc.rs b/mm2src/coins/tendermint/tendermint_native_rpc.rs index 71dc4ddbc2..88466e7f89 100644 --- a/mm2src/coins/tendermint/tendermint_native_rpc.rs +++ b/mm2src/coins/tendermint/tendermint_native_rpc.rs @@ -1,4 +1,498 @@ -pub use cosmrs::rpc::endpoint::{abci_query::Request as AbciRequest, health::Request as HealthRequest, - tx_search::Request as TxSearchRequest}; -pub use cosmrs::rpc::{query::Query as TendermintQuery, Client, HttpClient, Order as TendermintResultOrder}; +use async_trait::async_trait; +use core::convert::{TryFrom, TryInto}; +use core::str::FromStr; pub use cosmrs::tendermint::abci::Path as AbciPath; +use cosmrs::tendermint::abci::{self, Transaction}; +use cosmrs::tendermint::block::Height; +use cosmrs::tendermint::evidence::Evidence; +use cosmrs::tendermint::Genesis; +use serde::{de::DeserializeOwned, Serialize}; +use std::fmt; +use std::time::Duration; +use tendermint_config::net; +use tendermint_rpc::endpoint::validators::DEFAULT_VALIDATORS_PER_PAGE; +use tendermint_rpc::endpoint::*; +pub use tendermint_rpc::endpoint::{abci_query::Request as AbciRequest, health::Request as HealthRequest, + tx_search::Request as TxSearchRequest}; +use tendermint_rpc::Paging; +pub use tendermint_rpc::{query::Query as TendermintQuery, Error, Order as TendermintResultOrder, Scheme, + SimpleRequest, Url}; +use tokio::time; + +/// Provides lightweight access to the Tendermint RPC. It gives access to all +/// endpoints with the exception of the event subscription-related ones. +/// +/// To access event subscription capabilities, use a client that implements the +/// [`SubscriptionClient`] trait. +/// +/// [`SubscriptionClient`]: trait.SubscriptionClient.html +#[async_trait] +pub trait Client { + /// `/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`: get the latest block. + async fn latest_block(&self) -> Result { self.perform(block::Request::default()).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: TendermintQuery, + page: u32, + per_page: u8, + order: TendermintResultOrder, + ) -> 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: Transaction) -> Result { + 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: Transaction) -> Result { + 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: Transaction) -> Result { + 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: abci::transaction::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: TendermintQuery, + prove: bool, + page: u32, + per_page: u8, + order: TendermintResultOrder, + ) -> Result { + self.perform(tx_search::Request::new(query, prove, page, per_page, order)) + .await + } + + /// 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 = 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; + time::sleep(poll_interval).await; + } + + Ok(()) + } + + /// Perform a request against the RPC endpoint + async fn perform(&self, request: R) -> Result + where + R: SimpleRequest; +} + +/// A JSON-RPC/HTTP Tendermint RPC client (implements [`crate::Client`]). +/// +/// Supports both HTTP and HTTPS connections to Tendermint RPC endpoints, and +/// allows for the use of HTTP proxies (see [`HttpClient::new_with_proxy`] for +/// details). +/// +/// Does not provide [`crate::event::Event`] subscription facilities (see +/// [`crate::WebSocketClient`] for a client that does). +/// +/// ## Examples +/// +/// ```rust,ignore +/// use tendermint_rpc::{HttpClient, Client}; +/// +/// #[tokio::main] +/// async fn main() { +/// let client = HttpClient::new("http://127.0.0.1:26657") +/// .unwrap(); +/// +/// let abci_info = client.abci_info() +/// .await +/// .unwrap(); +/// +/// println!("Got ABCI info: {:?}", abci_info); +/// } +/// ``` +#[derive(Debug, Clone)] +pub struct HttpClient { + inner: sealed::HttpClient, +} + +impl HttpClient { + /// Construct a new Tendermint RPC HTTP/S client connecting to the given + /// URL. + pub fn new(url: U) -> Result + where + U: TryInto, + { + let url = url.try_into()?; + Ok(Self { + inner: if url.0.is_secure() { + sealed::HttpClient::new_https(url.try_into()?) + } else { + sealed::HttpClient::new_http(url.try_into()?) + }, + }) + } +} + +#[async_trait] +impl Client for HttpClient { + async fn perform(&self, request: R) -> Result + where + R: SimpleRequest, + { + self.inner.perform(request).await + } +} + +/// A URL limited to use with HTTP clients. +/// +/// Facilitates useful type conversions and inferences. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct HttpClientUrl(Url); + +impl TryFrom for HttpClientUrl { + type Error = Error; + + fn try_from(value: Url) -> Result { + match value.scheme() { + Scheme::Http | Scheme::Https => Ok(Self(value)), + _ => Err(Error::invalid_url(value)), + } + } +} + +impl FromStr for HttpClientUrl { + type Err = Error; + + fn from_str(s: &str) -> Result { + let url: Url = s.parse()?; + url.try_into() + } +} + +impl TryFrom<&str> for HttpClientUrl { + type Error = Error; + + fn try_from(value: &str) -> Result { value.parse() } +} + +impl TryFrom for HttpClientUrl { + type Error = Error; + + fn try_from(value: net::Address) -> Result { + match value { + net::Address::Tcp { peer_id: _, host, port } => format!("http://{}:{}", host, port).parse(), + net::Address::Unix { .. } => Err(Error::invalid_network_address()), + } + } +} + +impl From for Url { + fn from(url: HttpClientUrl) -> Self { url.0 } +} + +impl TryFrom for hyper::Uri { + type Error = Error; + + fn try_from(value: HttpClientUrl) -> Result { + value + .0 + .to_string() + .parse() + .map_err(|e: http::uri::InvalidUri| Error::parse(e.to_string())) + } +} + +mod sealed { + use common::log::debug; + use hyper::body::Buf; + use hyper::client::connect::Connect; + use hyper::client::HttpConnector; + use hyper::{header, Uri}; + use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder}; + use std::io::Read; + use tendermint_rpc::{Error, Response, SimpleRequest}; + + fn https_connector() -> HttpsConnector { + HttpsConnectorBuilder::new() + .with_webpki_roots() + .https_or_http() + .enable_http1() + .enable_http2() + .build() + } + + /// A wrapper for a `hyper`-based client, generic over the connector type. + #[derive(Debug, Clone)] + pub struct HyperClient { + uri: Uri, + inner: hyper::Client, + } + + impl HyperClient { + pub fn new(uri: Uri, inner: hyper::Client) -> Self { Self { uri, inner } } + } + + impl HyperClient + where + C: Connect + Clone + Send + Sync + 'static, + { + pub async fn perform(&self, request: R) -> Result + where + R: SimpleRequest, + { + let request = self.build_request(request)?; + let response = self + .inner + .request(request) + .await + .map_err(|e| Error::client_internal(e.to_string()))?; + let response_body = response_to_string(response).await?; + debug!("Incoming response: {}", response_body); + R::Response::from_string(&response_body) + } + } + + impl HyperClient { + /// Build a request using the given Tendermint RPC request. + pub fn build_request(&self, request: R) -> Result, Error> { + let request_body = request.into_json(); + + let mut request = hyper::Request::builder() + .method("POST") + .uri(&self.uri) + .body(hyper::Body::from(request_body.into_bytes())) + .map_err(|e| Error::client_internal(e.to_string()))?; + + { + let headers = request.headers_mut(); + headers.insert(header::CONTENT_TYPE, "application/json".parse().unwrap()); + headers.insert( + header::USER_AGENT, + format!("tendermint.rs/{}", env!("CARGO_PKG_VERSION")).parse().unwrap(), + ); + } + + Ok(request) + } + } + + /// We offer several variations of `hyper`-based client. + /// + /// Here we erase the type signature of the underlying `hyper`-based + /// client, allowing the higher-level HTTP client to operate via HTTP or + /// HTTPS, and with or without a proxy. + #[derive(Debug, Clone)] + pub enum HttpClient { + Http(HyperClient), + Https(HyperClient>), + } + + impl HttpClient { + pub fn new_http(uri: Uri) -> Self { Self::Http(HyperClient::new(uri, hyper::Client::new())) } + + pub fn new_https(uri: Uri) -> Self { + Self::Https(HyperClient::new(uri, hyper::Client::builder().build(https_connector()))) + } + + pub async fn perform(&self, request: R) -> Result + where + R: SimpleRequest, + { + match self { + HttpClient::Http(c) => c.perform(request).await, + HttpClient::Https(c) => c.perform(request).await, + } + } + } + + async fn response_to_string(response: hyper::Response) -> Result { + let mut response_body = String::new(); + hyper::body::aggregate(response.into_body()) + .await + .map_err(|e| Error::client_internal(e.to_string()))? + .reader() + .read_to_string(&mut response_body) + .map_err(Error::io)?; + + Ok(response_body) + } +}