Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Light Client validator set hash calculation #834

Merged
merged 17 commits into from
Mar 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
* `[tendermint]` The `tendermint::block::CommitSig` enum's members have been
renamed to be consistent with Rust's naming conventions. For example,
`BlockIDFlagAbsent` is now renamed to `BlockIdFlagAbsent` ([#839])
* `[tendermint-rpc]` The `Client::validators` method now requires a `Paging`
parameter. Previously, this wasn't possible and, if the network had more than
30 validators (the default for the RPC endpoint), it only returned a subset
of the validators ([#831])
* `[tendermint-rpc]` The `SubscriptionClient` trait now requires a `close`
method, since it assumes that subscription clients will, in general, use
long-running connections. This should not, however, break any downstream
Expand Down Expand Up @@ -35,9 +39,17 @@
* `[tendermint-rpc]` A `tendermint-rpc` CLI has been added to simplify
interaction with RPC endpoints from the command line ([#820])

### BUG FIXES

* `[tendermint-light-client]` Due to the RPC client's `validators` method
sometimes only returning a subset of validators (for networks larger than 30
validators), validator set hash calculations were failing. Now we are at
least obtaining a full validator set ([#831])

[#794]: https://github.com/informalsystems/tendermint-rs/pull/794
[#812]: https://github.com/informalsystems/tendermint-rs/pull/812
[#820]: https://github.com/informalsystems/tendermint-rs/pull/820
[#831]: https://github.com/informalsystems/tendermint-rs/issues/831
[#839]: https://github.com/informalsystems/tendermint-rs/pull/839

## v0.18.1
Expand Down
2 changes: 2 additions & 0 deletions light-client-js/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ default = ["console_error_panic_hook"]
[dependencies]
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 }
wasm-bindgen = { version = "0.2.63", features = [ "serde-serialize" ] }
Expand Down
7 changes: 5 additions & 2 deletions light-client/src/components/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ mod prod {
use tendermint::account::Id as TMAccountId;
use tendermint::block::signed_header::SignedHeader as TMSignedHeader;
use tendermint::validator::Set as TMValidatorSet;
use tendermint_rpc::Paging;

/// Production implementation of the Io component, which fetches
/// light blocks from full nodes via RPC.
Expand Down Expand Up @@ -166,8 +167,10 @@ mod prod {
};

let client = self.rpc_client.clone();
let response = block_on(self.timeout, async move { client.validators(height).await })?
.map_err(IoError::RpcError)?;
let response = block_on(self.timeout, async move {
client.validators(height, Paging::All).await
})?
.map_err(IoError::RpcError)?;

let validator_set = match proposer_address {
Some(proposer_address) => {
Expand Down
1 change: 1 addition & 0 deletions proto/src/serializers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ pub mod evidence;
pub mod from_str;
pub mod nullable;
pub mod optional;
pub mod optional_from_str;
pub mod part_set_header_total;
pub mod time_duration;
pub mod timestamp;
Expand Down
31 changes: 31 additions & 0 deletions proto/src/serializers/optional_from_str.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//! De/serialize an optional type that must be converted from/to a string.

use serde::de::Error;
use serde::{Deserialize, Deserializer, Serializer};
use std::str::FromStr;

pub fn serialize<S, T>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: ToString,
{
match value {
Some(t) => serializer.serialize_some(&t.to_string()),
None => serializer.serialize_none(),
}
}

pub fn deserialize<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: FromStr,
T::Err: std::error::Error,
{
let s = match Option::<String>::deserialize(deserializer)? {
Some(s) => s,
None => return Ok(None),
};
Ok(Some(s.parse().map_err(|e: <T as FromStr>::Err| {
D::Error::custom(e.to_string())
})?))
}
47 changes: 45 additions & 2 deletions rpc/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ pub use transport::http::{HttpClient, HttpClientUrl};
#[cfg(feature = "websocket-client")]
pub use transport::websocket::{WebSocketClient, WebSocketClientDriver, WebSocketClientUrl};

use crate::endpoint::validators::DEFAULT_VALIDATORS_PER_PAGE;
use crate::endpoint::*;
use crate::paging::Paging;
use crate::query::Query;
use crate::{Order, Result, SimpleRequest};
use async_trait::async_trait;
Expand Down Expand Up @@ -123,12 +125,53 @@ pub trait Client {
self.perform(consensus_state::Request::new()).await
}

// TODO(thane): Simplify once validators endpoint removes pagination.
/// `/validators`: get validators a given height.
async fn validators<H>(&self, height: H) -> Result<validators::Response>
async fn validators<H>(&self, height: H, paging: Paging) -> Result<validators::Response>
where
H: Into<Height> + Send,
{
self.perform(validators::Request::new(height.into())).await
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;
}
}
}
}

/// `/commit`: get the latest block commit
Expand Down
40 changes: 34 additions & 6 deletions rpc/src/client/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use structopt::StructOpt;
use tendermint::abci::{Path, Transaction};
use tendermint_rpc::query::Query;
use tendermint_rpc::{
Client, Error, HttpClient, Order, Result, Scheme, Subscription, SubscriptionClient, Url,
WebSocketClient,
Client, Error, HttpClient, Order, Paging, Result, Scheme, Subscription, SubscriptionClient,
Url, WebSocketClient,
};
use tracing::level_filters::LevelFilter;
use tracing::{error, info, warn};
Expand Down Expand Up @@ -141,7 +141,19 @@ enum ClientRequest {
prove: bool,
},
/// Get the validators at the given height.
Validators { height: u32 },
Validators {
/// The height at which to query the validators.
height: u32,
/// Fetch all validators.
#[structopt(long)]
all: bool,
/// The page of validators to retrieve.
#[structopt(long)]
page: Option<usize>,
/// The number of validators to retrieve per page.
#[structopt(long)]
per_page: Option<u8>,
},
}

#[tokio::main]
Expand Down Expand Up @@ -321,8 +333,24 @@ where
.tx_search(query, prove, page, per_page, order)
.await?,
)?,
ClientRequest::Validators { height } => {
serde_json::to_string_pretty(&client.validators(height).await?)?
ClientRequest::Validators {
height,
all,
page,
per_page,
} => {
let paging = if all {
Paging::All
} else {
match page.zip(per_page) {
Some((page, per_page)) => Paging::Specific {
page_number: page.into(),
per_page: per_page.into(),
},
None => Paging::Default,
}
};
serde_json::to_string_pretty(&client.validators(height, paging).await?)?
}
};
println!("{}", result);
Expand All @@ -338,7 +366,7 @@ async fn subscription_client_request<C>(
where
C: SubscriptionClient,
{
info!("Creating subcription for query: {}", query);
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,
Expand Down
1 change: 0 additions & 1 deletion rpc/src/endpoint/consensus_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ const NIL_VOTE_STR: &str = "nil-Vote";

/// Get the current consensus state.
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[non_exhaustive]
pub struct Request;

impl Request {
Expand Down
61 changes: 56 additions & 5 deletions rpc/src/endpoint/validators.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,55 @@
//! `/validators` endpoint JSON-RPC wrapper

use crate::{PageNumber, PerPage};
use serde::{Deserialize, Serialize};

use tendermint::{block, validator};

/// The default number of validators to return per page.
pub const DEFAULT_VALIDATORS_PER_PAGE: u8 = 30;

/// List validators for a specific block
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Request {
pub height: block::Height,
/// The height at which to retrieve the validator set. If not specified,
/// defaults to the latest height.
pub height: Option<block::Height>,
/// The number of the page to fetch.
#[serde(with = "tendermint_proto::serializers::optional_from_str")]
pub page: Option<PageNumber>,
/// The number of validators to fetch per page.
#[serde(with = "tendermint_proto::serializers::optional_from_str")]
pub per_page: Option<PerPage>,
}

impl Request {
/// List validators for a specific block
pub fn new(height: block::Height) -> Self {
Self { height }
/// List validators for a specific block.
///
/// See the [Tendermint RPC] for the defaults for each option when set to
/// `None`.
///
/// [Tendermint RPC]: https://docs.tendermint.com/master/rpc/#/Info/validators
pub fn new(
height: Option<block::Height>,
page: Option<PageNumber>,
per_page: Option<PerPage>,
) -> Self {
Self {
height,
page,
per_page,
}
}
}

impl Default for Request {
fn default() -> Self {
// By default we get the latest validators list, page 1, maximum 30
// items per page (the RPC defaults).
Self {
height: None,
page: None,
per_page: None,
}
}
}

Expand All @@ -35,6 +71,21 @@ pub struct Response {

/// Validator list
pub validators: Vec<validator::Info>,

/// Total number of validators for this block height.
#[serde(with = "tendermint_proto::serializers::from_str")]
pub total: i32,
}

impl crate::Response for Response {}

impl Response {
/// Constructor.
pub fn new(block_height: block::Height, validators: Vec<validator::Info>, total: i32) -> Self {
Self {
block_height,
validators,
total,
}
}
}
2 changes: 2 additions & 0 deletions rpc/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::{self, Display};
use thiserror::Error;

// TODO(thane): Differentiate between RPC response errors and internal crate
// errors (e.g. domain type-related errors).
/// Tendermint RPC errors
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct Error {
Expand Down
6 changes: 4 additions & 2 deletions rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ pub mod event;
mod id;
mod method;
mod order;
mod paging;
pub mod query;
pub mod request;
pub mod response;
Expand All @@ -58,6 +59,7 @@ mod utils;
mod version;

pub use self::{
error::Error, id::Id, method::Method, order::Order, request::Request, request::SimpleRequest,
response::Response, result::Result, version::Version,
error::Error, id::Id, method::Method, order::Order, paging::PageNumber, paging::Paging,
paging::PerPage, request::Request, request::SimpleRequest, response::Response, result::Result,
version::Version,
};
Loading