diff --git a/CHANGELOG.md b/CHANGELOG.md index 7991316f6..54bfda900 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,25 @@ -## Unreleased +## v0.19.0 + +This release primarily aims to enhance RPC and Light Client functionality, +thereby improving [`ibc-rs`] and fixing an important bug affecting the Light +Client ([#831]). + +The RPC now supports TLS 1.2+ connections (through the use of [`rustls`]), +allowing for secure HTTP and WebSocket connections, as well as HTTP/HTTPS +proxies. This implies that the Light Client now also supports these types of +connections. + +We additionally introduce two new crates: + +* `tendermint-abci` - A lightweight, minimal framework for building Tendermint + [ABCI] applications in Rust. +* `tendermint-light-client-js` - Exposes the Light Client's `verify` method to + JavaScript/WASM. This implies that, for now, you need to bring your own + networking functionality to practically make use of the Light Client's + verification mechanisms. + +Various relatively minor breaking API changes were introduced, and are listed +below. ### BREAKING CHANGES @@ -51,6 +72,9 @@ ### IMPROVEMENTS * `[tendermint]` IPv6 support has been added for `net::Address` ([#5]) +* `[tendermint-rpc]` Add `wait_until_healthy` utility method for RPC clients + to poll the `/health` endpoint of a node until it either returns successfully + or times out ([#855]) ### BUG FIXES @@ -69,6 +93,10 @@ [#835]: https://github.com/informalsystems/tendermint-rs/issues/835 [#836]: https://github.com/informalsystems/tendermint-rs/issues/836 [#839]: https://github.com/informalsystems/tendermint-rs/pull/839 +[#855]: https://github.com/informalsystems/tendermint-rs/pull/855 +[ABCI]: https://docs.tendermint.com/master/spec/abci/ +[`ibc-rs`]: https://github.com/informalsystems/ibc-rs +[`rustls`]: https://github.com/ctz/rustls ## v0.18.1 diff --git a/abci/Cargo.toml b/abci/Cargo.toml index f930db86a..72fdeacf8 100644 --- a/abci/Cargo.toml +++ b/abci/Cargo.toml @@ -1,8 +1,12 @@ [package] -name = "tendermint-abci" -version = "0.18.1" -authors = ["Thane Thomson "] -edition = "2018" +name = "tendermint-abci" +version = "0.19.0" +authors = ["Thane Thomson "] +edition = "2018" +license = "Apache-2.0" +readme = "README.md" +keywords = ["abci", "blockchain", "bft", "consensus", "tendermint"] +repository = "https://github.com/informalsystems/tendermint-rs" description = """ tendermint-abci provides a simple framework with which to build low-level applications on top of Tendermint. @@ -25,7 +29,7 @@ binary = [ "structopt", "tracing-subscriber" ] bytes = "1.0" eyre = "0.6" prost = "0.7" -tendermint-proto = { version = "0.18.1", path = "../proto" } +tendermint-proto = { version = "0.19.0", path = "../proto" } thiserror = "1.0" tracing = "0.1" diff --git a/light-client-js/Cargo.toml b/light-client-js/Cargo.toml index 2c2eb6e8c..6b38efa20 100644 --- a/light-client-js/Cargo.toml +++ b/light-client-js/Cargo.toml @@ -1,17 +1,19 @@ [package] -name = "tendermint-light-client-js" -version = "0.18.1" -authors = [ +name = "tendermint-light-client-js" +version = "0.19.0" +authors = [ "Romain Ruetschi ", "Thane Thomson " ] -edition = "2018" -license = "Apache-2.0" +edition = "2018" +license = "Apache-2.0" +readme = "README.md" +keywords = ["blockchain", "bft", "consensus", "light-client", "tendermint"] +repository = "https://github.com/informalsystems/tendermint-rs" description = """ tendermint-light-client-js provides a lightweight, WASM-based interface to the Tendermint Light Client's verification functionality. """ -repository = "https://github.com/informalsystems/tendermint-rs" [lib] crate-type = ["cdylib", "rlib"] @@ -24,8 +26,8 @@ serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0" # TODO(thane): Remove once https://github.com/rustwasm/wasm-bindgen/issues/2508 is resolved syn = "=1.0.65" -tendermint = { version = "0.18.1", path = "../tendermint" } -tendermint-light-client = { version = "0.18.1", path = "../light-client", default-features = false } +tendermint = { version = "0.19.0", path = "../tendermint" } +tendermint-light-client = { version = "0.19.0", path = "../light-client", default-features = false } wasm-bindgen = { version = "0.2.63", features = [ "serde-serialize" ] } # The `console_error_panic_hook` crate provides better debugging of panics by diff --git a/light-client-js/src/lib.rs b/light-client-js/src/lib.rs index ea5245e67..e93de1ea7 100644 --- a/light-client-js/src/lib.rs +++ b/light-client-js/src/lib.rs @@ -1,3 +1,14 @@ +//! Tendermint Light Client JavaScript/WASM interface. +//! +//! This crate exposes some of the [`tendermint-light-client`] crate's +//! functionality to be used from the JavaScript ecosystem. +//! +//! For a detailed example, please see the [`verifier-web` example] in the +//! repository. +//! +//! [`tendermint-light-client`]: https://github.com/informalsystems/tendermint-rs/tree/master/light-client +//! [`verifier-web` example]: https://github.com/informalsystems/tendermint-rs/tree/master/light-client-js/examples/verifier-web + mod utils; use serde::{Deserialize, Serialize}; diff --git a/light-client/Cargo.toml b/light-client/Cargo.toml index 406172653..378a70af0 100644 --- a/light-client/Cargo.toml +++ b/light-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tendermint-light-client" -version = "0.18.1" +version = "0.19.0" edition = "2018" license = "Apache-2.0" readme = "README.md" @@ -36,8 +36,8 @@ lightstore-sled = ["sled"] unstable = [] [dependencies] -tendermint = { version = "0.18.1", path = "../tendermint" } -tendermint-rpc = { version = "0.18.1", path = "../rpc", default-features = false } +tendermint = { version = "0.19.0", path = "../tendermint" } +tendermint-rpc = { version = "0.19.0", path = "../rpc", default-features = false } anomaly = { version = "0.2.0", features = ["serializer"] } contracts = "0.4.0" diff --git a/light-client/src/lib.rs b/light-client/src/lib.rs index 924d0d0ff..aa2c8aa88 100644 --- a/light-client/src/lib.rs +++ b/light-client/src/lib.rs @@ -10,7 +10,7 @@ nonstandard_style )] #![doc( - html_root_url = "https://docs.rs/tendermint-light-client/0.18.1", + html_root_url = "https://docs.rs/tendermint-light-client/0.19.0", html_logo_url = "https://raw.githubusercontent.com/informalsystems/tendermint-rs/master/img/logo-tendermint-rs_3961x4001.png" )] #![cfg_attr(docsrs, feature(doc_cfg))] diff --git a/light-node/Cargo.toml b/light-node/Cargo.toml index 29dbe1474..53b2c40ff 100644 --- a/light-node/Cargo.toml +++ b/light-node/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tendermint-light-node" -version = "0.18.1" +version = "0.19.0" edition = "2018" license = "Apache-2.0" repository = "https://github.com/informalsystems/tendermint-rs" @@ -38,10 +38,10 @@ serde = { version = "1", features = ["serde_derive"] } serde_json = "1.0" thiserror = "1.0" -tendermint = { version = "0.18.1", path = "../tendermint" } -tendermint-light-client = { version = "0.18.1", path = "../light-client", features = ["lightstore-sled"] } -tendermint-proto = { version = "0.18.1", path = "../proto" } -tendermint-rpc = { version = "0.18.1", path = "../rpc", features = ["http-client"] } +tendermint = { version = "0.19.0", path = "../tendermint" } +tendermint-light-client = { version = "0.19.0", path = "../light-client", features = ["lightstore-sled"] } +tendermint-proto = { version = "0.19.0", path = "../proto" } +tendermint-rpc = { version = "0.19.0", path = "../rpc", features = ["http-client"] } [dependencies.abscissa_core] version = "0.5.0" diff --git a/light-node/src/lib.rs b/light-node/src/lib.rs index 67dfeb6bc..59a98abb4 100644 --- a/light-node/src/lib.rs +++ b/light-node/src/lib.rs @@ -15,7 +15,7 @@ unused_qualifications )] #![doc( - html_root_url = "https://docs.rs/tendermint-light-node/0.18.1", + html_root_url = "https://docs.rs/tendermint-light-node/0.19.0", html_logo_url = "https://raw.githubusercontent.com/informalsystems/tendermint-rs/master/img/logo-tendermint-rs_3961x4001.png" )] diff --git a/p2p/Cargo.toml b/p2p/Cargo.toml index 5ef012d1c..e40084928 100644 --- a/p2p/Cargo.toml +++ b/p2p/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tendermint-p2p" -version = "0.18.1" +version = "0.19.0" edition = "2018" license = "Apache-2.0" repository = "https://github.com/informalsystems/tendermint-rs" @@ -31,8 +31,8 @@ x25519-dalek = "1.1" zeroize = "1" # path dependencies -tendermint = { path = "../tendermint", version = "0.18.1" } -tendermint-proto = { path = "../proto", version = "0.18.1" } +tendermint = { path = "../tendermint", version = "0.19.0" } +tendermint-proto = { path = "../proto", version = "0.19.0" } # optional dependencies prost-amino = { version = "0.6", optional = true } diff --git a/p2p/src/lib.rs b/p2p/src/lib.rs index 8ad619d42..a9cd97cad 100644 --- a/p2p/src/lib.rs +++ b/p2p/src/lib.rs @@ -10,7 +10,7 @@ nonstandard_style )] #![doc( - html_root_url = "https://docs.rs/tendermint-p2p/0.1.0", + html_root_url = "https://docs.rs/tendermint-p2p/0.19.0", html_logo_url = "https://raw.githubusercontent.com/informalsystems/tendermint-rs/master/img/logo-tendermint-rs_3961x4001.png" )] diff --git a/pbt-gen/Cargo.toml b/pbt-gen/Cargo.toml index d1c0e99c2..d7dd8f719 100644 --- a/pbt-gen/Cargo.toml +++ b/pbt-gen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tendermint-pbt-gen" -version = "0.1.0" +version = "0.19.0" authors = ["Shon Feder "] edition = "2018" description = """ @@ -11,12 +11,9 @@ description = """ # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] - default = ["time"] - time = ["chrono"] [dependencies] - chrono = { version = "0.4", features = ["serde"], optional = true} proptest = "0.10.1" diff --git a/proto/Cargo.toml b/proto/Cargo.toml index 2c65fd68c..ae266f74c 100644 --- a/proto/Cargo.toml +++ b/proto/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tendermint-proto" -version = "0.18.1" +version = "0.19.0" authors = ["Greg Szabo "] edition = "2018" license = "Apache-2.0" diff --git a/proto/src/lib.rs b/proto/src/lib.rs index 4fec5983d..9f5a95dae 100644 --- a/proto/src/lib.rs +++ b/proto/src/lib.rs @@ -3,7 +3,7 @@ #![deny(warnings, trivial_casts, trivial_numeric_casts, unused_import_braces)] #![allow(clippy::large_enum_variant)] #![forbid(unsafe_code)] -#![doc(html_root_url = "https://docs.rs/tendermint-proto/0.18.1")] +#![doc(html_root_url = "https://docs.rs/tendermint-proto/0.19.0")] /// Built-in prost_types with slight customization to enable JSON-encoding #[allow(warnings)] diff --git a/release.sh b/release.sh index 629207888..a2175ddf6 100755 --- a/release.sh +++ b/release.sh @@ -36,7 +36,7 @@ set -e # A space-separated list of all the crates we want to publish, in the order in # which they must be published. It's important to respect this order, since # each subsequent crate depends on one or more of the preceding ones. -DEFAULT_CRATES="tendermint-proto tendermint tendermint-rpc tendermint-p2p tendermint-light-client tendermint-light-node tendermint-testgen" +DEFAULT_CRATES="tendermint-proto tendermint tendermint-abci tendermint-rpc tendermint-p2p tendermint-light-client tendermint-light-client-js tendermint-light-node tendermint-testgen" # Allows us to override the crates we want to publish. CRATES=${*:-${DEFAULT_CRATES}} diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 22b03f079..8cf6a2227 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tendermint-rpc" -version = "0.18.1" +version = "0.19.0" edition = "2018" license = "Apache-2.0" homepage = "https://www.tendermint.com/" @@ -64,15 +64,12 @@ websocket-client = [ bytes = "1.0" chrono = "0.4" getrandom = "0.1" -# TODO(thane): Use a released version once support for inverted patterns is released. -# See https://github.com/kevinmehall/rust-peg/pull/245 -peg = { git = "https://github.com/kevinmehall/rust-peg.git", rev = "ba6019539b2cf80289190cbb9537c94113b6b7d1" } pin-project = "1.0.1" serde = { version = "1", features = [ "derive" ] } serde_bytes = "0.11" serde_json = "1" -tendermint = { version = "0.18.1", path = "../tendermint" } -tendermint-proto = { version = "0.18.1", path = "../proto" } +tendermint = { version = "0.19.0", path = "../tendermint" } +tendermint-proto = { version = "0.19.0", path = "../proto" } thiserror = "1" uuid = { version = "0.8", default-features = false } subtle-encoding = { version = "0.5", features = ["bech32-preview"] } diff --git a/rpc/src/client.rs b/rpc/src/client.rs index cb4070f55..bcab4946e 100644 --- a/rpc/src/client.rs +++ b/rpc/src/client.rs @@ -14,14 +14,17 @@ pub use transport::websocket::{WebSocketClient, WebSocketClientDriver, WebSocket use crate::endpoint::validators::DEFAULT_VALIDATORS_PER_PAGE; use crate::endpoint::*; +use crate::error::Error; use crate::paging::Paging; use crate::query::Query; use crate::{Order, Result, SimpleRequest}; use async_trait::async_trait; +use std::time::Duration; use tendermint::abci::{self, Transaction}; use tendermint::block::Height; use tendermint::evidence::Evidence; use tendermint::Genesis; +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. @@ -221,6 +224,31 @@ pub trait Client { .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<()> + 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::client_internal_error(format!( + "timed out waiting for healthy response after {}ms", + timeout.as_millis() + ))); + } + + attempts_remaining -= 1; + time::sleep(poll_interval).await; + } + + Ok(()) + } + /// Perform a request against the RPC endpoint async fn perform(&self, request: R) -> Result where diff --git a/rpc/src/client/bin/main.rs b/rpc/src/client/bin/main.rs index 4c6f06b3d..058897ee0 100644 --- a/rpc/src/client/bin/main.rs +++ b/rpc/src/client/bin/main.rs @@ -1,14 +1,10 @@ //! CLI for performing simple interactions against a Tendermint node's RPC. -use futures::StreamExt; use std::str::FromStr; -use std::time::Duration; use structopt::StructOpt; use tendermint::abci::{Path, Transaction}; -use tendermint_rpc::query::Query; use tendermint_rpc::{ - Client, Error, HttpClient, Order, Paging, Result, Scheme, Subscription, SubscriptionClient, - Url, WebSocketClient, + Client, Error, HttpClient, Paging, Result, Scheme, SubscriptionClient, Url, WebSocketClient, }; use tracing::level_filters::LevelFilter; use tracing::{error, info, warn}; @@ -45,18 +41,7 @@ struct Opt { enum Request { #[structopt(flatten)] ClientRequest(ClientRequest), - /// Subscribe to receive events produced by a specific query. - Subscribe { - /// The query against which events will be matched. - query: Query, - /// The maximum number of events to receive before terminating. - #[structopt(long)] - max_events: Option, - /// The maximum amount of time (in seconds) to listen for events before - /// terminating. - #[structopt(long)] - max_time: Option, - }, + // TODO(thane): Implement subscription functionality } #[derive(Debug, StructOpt)] @@ -127,19 +112,7 @@ enum ClientRequest { NetInfo, /// Get Tendermint status (node info, public key, latest block hash, etc.). Status, - /// Search for transactions with their results. - TxSearch { - /// The query against which transactions should be matched. - query: Query, - #[structopt(long, default_value = "1")] - page: u32, - #[structopt(long, default_value = "10")] - per_page: u8, - #[structopt(long, default_value = "asc")] - order: Order, - #[structopt(long)] - prove: bool, - }, + // TODO(thane): Implement txsearch endpoint. /// Get the validators at the given height. Validators { /// The height at which to query the validators. @@ -235,7 +208,6 @@ async fn http_request(url: Url, proxy_url: Option, req: Request) -> Result< match req { Request::ClientRequest(r) => client_request(&client, r).await, - _ => Err(Error::invalid_params("HTTP/S clients do not support subscription capabilities (please use the WebSocket client instead)")) } } @@ -246,11 +218,6 @@ async fn websocket_request(url: Url, req: Request) -> Result<()> { let result = match req { Request::ClientRequest(r) => client_request(&client, r).await, - Request::Subscribe { - query, - max_events, - max_time, - } => subscription_client_request(&client, query, max_events, max_time).await, }; client.close()?; @@ -322,17 +289,6 @@ where ClientRequest::Health => serde_json::to_string_pretty(&client.health().await?)?, ClientRequest::NetInfo => serde_json::to_string_pretty(&client.net_info().await?)?, ClientRequest::Status => serde_json::to_string_pretty(&client.status().await?)?, - ClientRequest::TxSearch { - query, - page, - per_page, - order, - prove, - } => serde_json::to_string_pretty( - &client - .tx_search(query, prove, page, per_page, order) - .await?, - )?, ClientRequest::Validators { height, all, @@ -356,73 +312,3 @@ where println!("{}", result); Ok(()) } - -async fn subscription_client_request( - client: &C, - query: Query, - max_events: Option, - max_time: Option, -) -> Result<()> -where - C: SubscriptionClient, -{ - info!("Creating subscription for query: {}", query); - let subs = client.subscribe(query).await?; - match max_time { - Some(secs) => recv_events_with_timeout(subs, max_events, secs).await, - None => recv_events(subs, max_events).await, - } -} - -async fn recv_events_with_timeout( - mut subs: Subscription, - max_events: Option, - timeout_secs: u32, -) -> Result<()> { - let timeout = tokio::time::sleep(Duration::from_secs(timeout_secs as u64)); - let mut event_count = 0u64; - tokio::pin!(timeout); - loop { - tokio::select! { - result_opt = subs.next() => { - let result = match result_opt { - Some(r) => r, - None => { - info!("The server terminated the subscription"); - return Ok(()); - } - }; - let event = result?; - println!("{}", serde_json::to_string_pretty(&event)?); - event_count += 1; - if let Some(me) = max_events { - if event_count >= (me as u64) { - info!("Reached maximum number of events: {}", me); - return Ok(()); - } - } - } - _ = &mut timeout => { - info!("Reached event receive timeout of {} seconds", timeout_secs); - return Ok(()) - } - } - } -} - -async fn recv_events(mut subs: Subscription, max_events: Option) -> Result<()> { - let mut event_count = 0u64; - while let Some(result) = subs.next().await { - let event = result?; - println!("{}", serde_json::to_string_pretty(&event)?); - event_count += 1; - if let Some(me) = max_events { - if event_count >= (me as u64) { - info!("Reached maximum number of events: {}", me); - return Ok(()); - } - } - } - info!("The server terminated the subscription"); - Ok(()) -} diff --git a/rpc/src/query.rs b/rpc/src/query.rs index 61d34b0bc..892e3e148 100644 --- a/rpc/src/query.rs +++ b/rpc/src/query.rs @@ -8,7 +8,7 @@ #![allow(clippy::redundant_closure_call, clippy::unit_arg)] use crate::{Error, Result}; -use chrono::{Date, DateTime, FixedOffset, NaiveDate, Utc}; +use chrono::{Date, DateTime, FixedOffset, Utc}; use std::fmt; use std::str::FromStr; @@ -36,21 +36,6 @@ use std::str::FromStr; /// assert_eq!("tm.event = 'Tx' AND tx.height >= 100", query.to_string()); /// ``` /// -/// ### Query parsing -/// -/// ```rust -/// use tendermint_rpc::query::{Query, EventType}; -/// -/// let query: Query = "tm.event = 'NewBlock'".parse().unwrap(); -/// assert_eq!(query, Query::from(EventType::NewBlock)); -/// -/// let query: Query = "tm.event = 'Tx' AND tx.hash = 'XYZ'".parse().unwrap(); -/// assert_eq!(query, Query::from(EventType::Tx).and_eq("tx.hash", "XYZ")); -/// -/// let query: Query = "tm.event = 'Tx' AND tx.height >= 100".parse().unwrap(); -/// assert_eq!(query, Query::from(EventType::Tx).and_gte("tx.height", 100_u64)); -/// ``` -/// /// [subscribe endpoint documentation]: https://docs.tendermint.com/master/rpc/#/Websocket/subscribe #[derive(Debug, Clone, PartialEq)] pub struct Query { @@ -206,172 +191,6 @@ impl fmt::Display for Query { } } -peg::parser! { - grammar query_parser() for str { - // Some or no whitespace. - rule _() = quiet!{[' ']*} - - // At least some whitespace. - rule __() = quiet!{[' ']+} - - rule string() -> &'input str - = "'" s:$([^'\'']*) "'" { s } - - rule unsigned() -> u64 - = s:$(['0'..='9']+) {? - u64::from_str(s) - .map_err(|_| "failed to parse as an unsigned integer") - } - - rule signed() -> i64 - = s:$("-" ['1'..='9'] ['0'..='9']*) {? - i64::from_str(s) - .map_err(|_| "failed to parse as a signed integer") - } - - rule year() -> &'input str - = $(['0'..='9']*<4>) - - rule month() -> &'input str - = $(['0' | '1'] ['0'..='9']) - - rule day() -> &'input str - = $(['0'..='3'] ['0'..='9']) - - rule date() -> &'input str - = $(year() "-" month() "-" day()) - - rule hour() -> &'input str - = $(['0'..='2'] ['0'..='9']) - - rule min_sec() -> &'input str - = $(['0'..='5'] ['0'..='9']) - - rule nanosec() -> &'input str - = $("." ['0'..='9']+) - - rule time() -> &'input str - = $(hour() ":" min_sec() ":" min_sec() nanosec()? "Z") - - rule datetime() -> &'input str - = dt:$(date() "T" time()) { dt } - - rule float() -> f64 - = s:$("-"? ['0'..='9']+ "." ['0'..='9']+) {? - f64::from_str(s) - .map_err(|_| "failed to parse as a 64-bit floating point number") - } - - rule string_op() -> Operand - = s:string() { Operand::String(s.to_owned()) } - - rule unsigned_op() -> Operand - = u:unsigned() { Operand::Unsigned(u) } - - rule signed_op() -> Operand - = s:signed() { Operand::Signed(s) } - - rule datetime_op() -> Operand - = "TIME" __ dt:datetime() {? - DateTime::parse_from_rfc3339(dt) - .map(|dt| Operand::DateTime(dt.with_timezone(&Utc))) - .map_err(|_| "failed to parse as RFC3339-compatible date/time") - } - - rule date_op() -> Operand - = "DATE" __ dt:date() {? - let naive_date = NaiveDate::parse_from_str(dt, "%Y-%m-%d") - .map_err(|_| "failed to parse as RFC3339-compatible date")?; - Ok(Operand::Date(Date::from_utc(naive_date, Utc))) - } - - rule float_op() -> Operand - = f:float() { Operand::Float(f) } - - rule tag() -> &'input str - = $(['a'..='z' | 'A'..='Z'] ['a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '.']*) - - rule operand() -> Operand - = datetime_op() / date_op() / string_op() / float_op() / signed_op() / unsigned_op() - - rule eq() -> Condition - = t:tag() _ "=" _ op:operand() { Condition::Eq(t.to_owned(), op) } - - rule lte() -> Condition - = t:tag() _ "<=" _ op:operand() { Condition::Lte(t.to_owned(), op) } - - rule lt() -> Condition - = t:tag() _ "<" _ op:operand() { Condition::Lt(t.to_owned(), op) } - - rule gte() -> Condition - = t:tag() _ ">=" _ op:operand() { Condition::Gte(t.to_owned(), op) } - - rule gt() -> Condition - = t:tag() _ ">" _ op:operand() { Condition::Gt(t.to_owned(), op) } - - rule contains() -> Condition - = t:tag() __ "CONTAINS" __ op:string() { Condition::Contains(t.to_owned(), op.to_owned()) } - - rule exists() -> Condition - = t:tag() __ "EXISTS" { Condition::Exists(t.to_owned()) } - - rule event_type() -> Term - = "tm.event" _ "=" _ "'" et:$("NewBlock" / "Tx") "'" { - Term::EventType(EventType::from_str(et).unwrap()) - } - - rule condition() -> Term - = c:(eq() / lte() / lt() / gte() / gt() / contains() / exists()) { Term::Condition(c) } - - rule term() -> Term - = event_type() / condition() - - pub rule query() -> Vec - = t:term() ** ( __ "AND" __ ) { t } - } -} - -/// A term in a query is either an event type or a general condition. -/// Exclusively used for query parsing. -#[derive(Debug)] -pub enum Term { - EventType(EventType), - Condition(Condition), -} - -// Separate a list of terms into lists of each type of term. -fn separate_terms(terms: Vec) -> (Vec, Vec) { - terms - .into_iter() - .fold((Vec::new(), Vec::new()), |mut v, t| { - match t { - Term::EventType(et) => v.0.push(et), - Term::Condition(c) => v.1.push(c), - } - v - }) -} - -impl FromStr for Query { - type Err = Error; - - fn from_str(s: &str) -> Result { - let (event_types, conditions) = separate_terms( - query_parser::query(s) - .map_err(|e| Error::invalid_params(&format!("failed to parse query: {}", e)))?, - ); - if event_types.len() > 1 { - return Err(Error::invalid_params( - "tm.event can only be used once in a query", - )); - } - Ok(Query { - event_type: event_types.first().cloned(), - conditions, - }) - } -} - fn join(f: &mut fmt::Formatter<'_>, separator: S, iterable: I) -> fmt::Result where S: fmt::Display, @@ -604,7 +423,7 @@ fn escape(s: &str) -> String { #[cfg(test)] mod test { use super::*; - use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; + use chrono::NaiveDate; #[test] fn empty_query() { @@ -702,246 +521,4 @@ mod test { query.to_string() ); } - - #[test] - fn query_event_type_parsing() { - // Test the empty query (that matches all possible events) - let query = Query::from_str("").unwrap(); - assert_eq!(query, Query::default()); - - // With just one event type - let query = Query::from_str("tm.event='Tx'").unwrap(); - assert_eq!(query.event_type, Some(EventType::Tx)); - assert!(query.conditions.is_empty()); - let query = Query::from_str("tm.event='NewBlock'").unwrap(); - assert_eq!(query.event_type, Some(EventType::NewBlock)); - assert!(query.conditions.is_empty()); - - // One event type, with whitespace - let query = Query::from_str("tm.event = 'NewBlock'").unwrap(); - assert_eq!(query.event_type, Some(EventType::NewBlock)); - assert!(query.conditions.is_empty()); - - // Two event types are not allowed - assert!(Query::from_str("tm.event='Tx' AND tm.event='NewBlock'").is_err()); - } - - #[test] - fn query_string_term_parsing() { - // Query with string term - let query = Query::from_str("tm.event='Tx' AND transfer.sender='AddrA'").unwrap(); - assert_eq!(query.event_type, Some(EventType::Tx)); - assert_eq!( - query.conditions, - vec![Condition::Eq( - "transfer.sender".to_owned(), - Operand::String("AddrA".to_owned()), - )] - ); - // Query with string term, with extra whitespace - let query = Query::from_str("tm.event = 'Tx' AND transfer.sender = 'AddrA'").unwrap(); - assert_eq!(query.event_type, Some(EventType::Tx)); - assert_eq!( - query.conditions, - vec![Condition::Eq( - "transfer.sender".to_owned(), - Operand::String("AddrA".to_owned()), - )] - ); - } - - #[test] - fn query_unsigned_term_parsing() { - let query = Query::from_str("tm.event = 'Tx' AND tx.height = 10").unwrap(); - assert_eq!(query.event_type, Some(EventType::Tx)); - assert_eq!( - query.conditions, - vec![Condition::Eq("tx.height".to_owned(), Operand::Unsigned(10))] - ); - - let query = Query::from_str("tm.event = 'Tx' AND tx.height <= 100").unwrap(); - assert_eq!(query.event_type, Some(EventType::Tx)); - assert_eq!( - query.conditions, - vec![Condition::Lte( - "tx.height".to_owned(), - Operand::Unsigned(100) - )] - ); - } - - #[test] - fn query_signed_term_parsing() { - let query = Query::from_str("tm.event = 'Tx' AND some.value = -1").unwrap(); - assert_eq!(query.event_type, Some(EventType::Tx)); - assert_eq!( - query.conditions, - vec![Condition::Eq("some.value".to_owned(), Operand::Signed(-1))] - ); - - let query = Query::from_str("tm.event = 'Tx' AND some.value <= -100").unwrap(); - assert_eq!(query.event_type, Some(EventType::Tx)); - assert_eq!( - query.conditions, - vec![Condition::Lte( - "some.value".to_owned(), - Operand::Signed(-100) - )] - ); - } - - #[test] - fn query_date_parsing() { - let query = Query::from_str("tm.event = 'Tx' AND some.date <= DATE 2022-02-03").unwrap(); - assert_eq!(query.event_type, Some(EventType::Tx)); - assert_eq!( - query.conditions, - vec![Condition::Lte( - "some.date".to_owned(), - Operand::Date(Date::from_utc(NaiveDate::from_ymd(2022, 2, 3), Utc)) - )] - ); - } - - #[test] - fn query_datetime_parsing() { - let query = - Query::from_str("tm.event = 'Tx' AND some.datetime = TIME 2021-02-26T17:05:02.1495Z") - .unwrap(); - assert_eq!(query.event_type, Some(EventType::Tx)); - assert_eq!( - query.conditions, - vec![Condition::Eq( - "some.datetime".to_owned(), - Operand::DateTime(DateTime::from_utc( - NaiveDateTime::new( - NaiveDate::from_ymd(2021, 2, 26), - NaiveTime::from_hms_nano(17, 5, 2, 149500000) - ), - Utc - )) - )] - ) - } - - #[test] - fn query_float_parsing() { - // Positive floating point number - let query = Query::from_str("short.pi = 3.14159").unwrap(); - assert_eq!(query.conditions.len(), 1); - match &query.conditions[0] { - Condition::Eq(tag, op) => { - assert_eq!(tag, "short.pi"); - match op { - Operand::Float(f) => { - assert!(floats_eq(*f, std::f64::consts::PI, 5)); - } - _ => panic!("unexpected operand: {:?}", op), - } - } - c => panic!("unexpected condition: {:?}", c), - } - - // Negative floating point number - let query = Query::from_str("short.pi = -3.14159").unwrap(); - assert_eq!(query.conditions.len(), 1); - match &query.conditions[0] { - Condition::Eq(tag, op) => { - assert_eq!(tag, "short.pi"); - match op { - Operand::Float(f) => { - assert!(floats_eq(*f, -std::f64::consts::PI, 5)); - } - _ => panic!("unexpected operand: {:?}", op), - } - } - c => panic!("unexpected condition: {:?}", c), - } - } - - // From https://stackoverflow.com/a/41447964/1156132 - fn floats_eq(a: f64, b: f64, precision: u8) -> bool { - let factor = 10.0f64.powi(precision as i32); - let a = (a * factor).trunc(); - let b = (b * factor).trunc(); - a == b - } - - #[test] - fn query_conditions() { - let query = Query::from_str("some.field = 'string'").unwrap(); - assert_eq!( - query, - Query { - event_type: None, - conditions: vec![Condition::Eq( - "some.field".to_owned(), - Operand::String("string".to_owned()) - )] - } - ); - - let query = Query::from_str("some.field < 5").unwrap(); - assert_eq!( - query, - Query { - event_type: None, - conditions: vec![Condition::Lt("some.field".to_owned(), Operand::Unsigned(5),)] - } - ); - - let query = Query::from_str("some.field <= 5").unwrap(); - assert_eq!( - query, - Query { - event_type: None, - conditions: vec![Condition::Lte( - "some.field".to_owned(), - Operand::Unsigned(5), - )] - } - ); - - let query = Query::from_str("some.field > 5").unwrap(); - assert_eq!( - query, - Query { - event_type: None, - conditions: vec![Condition::Gt("some.field".to_owned(), Operand::Unsigned(5),)] - } - ); - - let query = Query::from_str("some.field >= 5").unwrap(); - assert_eq!( - query, - Query { - event_type: None, - conditions: vec![Condition::Gte( - "some.field".to_owned(), - Operand::Unsigned(5), - )] - } - ); - - let query = Query::from_str("some.field CONTAINS 'inner'").unwrap(); - assert_eq!( - query, - Query { - event_type: None, - conditions: vec![Condition::Contains( - "some.field".to_owned(), - "inner".to_owned() - )] - } - ); - - let query = Query::from_str("some.field EXISTS").unwrap(); - assert_eq!( - query, - Query { - event_type: None, - conditions: vec![Condition::Exists("some.field".to_owned())] - } - ); - } } diff --git a/tendermint/Cargo.toml b/tendermint/Cargo.toml index c14e4fcf1..5aa5a8b15 100644 --- a/tendermint/Cargo.toml +++ b/tendermint/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tendermint" -version = "0.18.1" # Also update `html_root_url` in lib.rs and +version = "0.19.0" # Also update `html_root_url` in lib.rs and # depending crates (rpc, light-node, ..) when bumping this license = "Apache-2.0" homepage = "https://www.tendermint.com/" @@ -55,7 +55,7 @@ signature = "1.2" subtle = "2" subtle-encoding = { version = "0.5", features = ["bech32-preview"] } thiserror = "1" -tendermint-proto = { version = "0.18.1", path = "../proto" } +tendermint-proto = { version = "0.19.0", path = "../proto" } toml = { version = "0.5" } url = { version = "2.2" } zeroize = { version = "1.1", features = ["zeroize_derive"] } @@ -67,6 +67,5 @@ ripemd160 = { version = "0.9", optional = true } secp256k1 = ["k256", "ripemd160"] [dev-dependencies] - proptest = "0.10.1" tendermint-pbt-gen = { path = "../pbt-gen" } diff --git a/tendermint/src/lib.rs b/tendermint/src/lib.rs index 4e455e272..38514cfe3 100644 --- a/tendermint/src/lib.rs +++ b/tendermint/src/lib.rs @@ -15,7 +15,7 @@ )] #![forbid(unsafe_code)] #![doc( - html_root_url = "https://docs.rs/tendermint/0.18.1", + html_root_url = "https://docs.rs/tendermint/0.19.0", html_logo_url = "https://raw.githubusercontent.com/informalsystems/tendermint-rs/master/img/logo-tendermint-rs_3961x4001.png" )] diff --git a/testgen/Cargo.toml b/testgen/Cargo.toml index c49df1dd1..d3eabd831 100644 --- a/testgen/Cargo.toml +++ b/testgen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tendermint-testgen" -version = "0.18.1" +version = "0.19.0" authors = ["Andrey Kuprianov ", "Shivani Joshi "] edition = "2018" readme = "README.md" @@ -14,7 +14,7 @@ description = """ """ [dependencies] -tendermint = { version = "0.18.1", path = "../tendermint" } +tendermint = { version = "0.19.0", path = "../tendermint" } serde = { version = "1", features = ["derive"] } serde_json = "1" ed25519-dalek = "1" diff --git a/tools/abci-test/Cargo.toml b/tools/abci-test/Cargo.toml index 1907ad648..71d1e5118 100644 --- a/tools/abci-test/Cargo.toml +++ b/tools/abci-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "abci-test" -version = "0.18.1" +version = "0.19.0" authors = ["Thane Thomson "] edition = "2018" description = """ @@ -13,8 +13,8 @@ description = """ [dependencies] futures = "0.3" structopt = "0.3" -tendermint = { version = "0.18.1", path = "../../tendermint" } -tendermint-rpc = { version = "0.18.1", path = "../../rpc", features = [ "websocket-client" ] } +tendermint = { version = "0.19.0", path = "../../tendermint" } +tendermint-rpc = { version = "0.19.0", path = "../../rpc", features = [ "websocket-client" ] } tracing = "0.1" tracing-subscriber = "0.2" tokio = { version = "1", features = ["full"] } diff --git a/tools/kvstore-test/Cargo.toml b/tools/kvstore-test/Cargo.toml index 40760bc2f..e28a2fe4c 100644 --- a/tools/kvstore-test/Cargo.toml +++ b/tools/kvstore-test/Cargo.toml @@ -10,9 +10,9 @@ edition = "2018" [dev-dependencies] futures = "0.3" -tendermint = { version = "0.18.1", path = "../../tendermint" } -tendermint-light-client = { version = "0.18.1", path = "../../light-client", features = ["unstable"] } -tendermint-rpc = { version = "0.18.1", path = "../../rpc", features = [ "http-client", "websocket-client" ] } +tendermint = { version = "0.19.0", path = "../../tendermint" } +tendermint-light-client = { version = "0.19.0", path = "../../light-client", features = ["unstable"] } +tendermint-rpc = { version = "0.19.0", path = "../../rpc", features = [ "http-client", "websocket-client" ] } tokio = { version = "1.0", features = [ "rt-multi-thread", "macros" ] } tracing = "0.1" tracing-subscriber = "0.2" diff --git a/tools/rpc-probe/Cargo.toml b/tools/rpc-probe/Cargo.toml index fdbd44776..f34a1ca64 100644 --- a/tools/rpc-probe/Cargo.toml +++ b/tools/rpc-probe/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tendermint-rpc-probe" -version = "0.18.1" +version = "0.19.0" authors = ["Thane Thomson "] edition = "2018" license = "Apache-2.0"