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

rfc3339 direct ser/deser fix for protobuf Timestamp #666

Merged
merged 4 commits into from
Nov 10, 2020
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
deemed valid instead of invalid ([#650])
- `[light-client]` Revert a change introduced in [#652] that would enable DoS attacks,
where full nodes could spam the light client with massive commits (eg. 10k validators).
- `[tendermint]` (Since RC1) Time serialization fix (part of [#665])
- `[tendermint-proto]` (Since RC1) Timestamp serialization fix (part of [#665])

### FEATURES:

Expand All @@ -25,6 +27,7 @@
[#652]: https://github.com/informalsystems/tendermint-rs/pulls/652
[#639]: https://github.com/informalsystems/tendermint-rs/pull/639
[#660]: https://github.com/informalsystems/tendermint-rs/issues/660
[#665]: https://github.com/informalsystems/tendermint-rs/issues/665

## v0.17.0-rc1

Expand Down
2 changes: 0 additions & 2 deletions light-client/src/predicates/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@ pub enum VerificationError {
#[error("header from the future: header_time={header_time} now={now}")]
HeaderFromTheFuture {
/// Time in the header
#[serde(with = "tendermint::serializers::time")]
header_time: Time,
/// Current time
#[serde(with = "tendermint::serializers::time")]
now: Time,
},

Expand Down
2 changes: 0 additions & 2 deletions light-client/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ pub struct Initial {
pub signed_header: SignedHeader,
pub next_validator_set: ValidatorSet,
pub trusting_period: DurationStr,
#[serde(with = "tendermint::serializers::time")]
pub now: Time,
}

Expand All @@ -49,7 +48,6 @@ pub struct TestBisection<LB> {
pub primary: Provider<LB>,
pub witnesses: Vec<WitnessProvider<LB>>,
pub height_to_verify: HeightStr,
#[serde(with = "tendermint::serializers::time")]
pub now: Time,
pub expected_output: Option<String>,
pub expected_num_of_bisections: usize,
Expand Down
4 changes: 2 additions & 2 deletions light-client/tests/model_based.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ pub struct SingleStepTestCase {
pub struct BlockVerdict {
block: AnonLightBlock,
testgen_block: TestgenLightBlock,
#[serde(with = "tendermint::serializers::time")]
now: Time,
verdict: LiteVerdict,
}
Expand Down Expand Up @@ -119,7 +118,8 @@ struct HeaderVersionFuzzer {}
impl SingleStepTestFuzzer for HeaderVersionFuzzer {
// TODO: rehash the header and re-compute commit with it
// TODO: Unlike in tendermint-go, we don't assert for a particular version in rust
// TODO: Either add this check in verification or remove this test because otherwise there's no point of it
// TODO: Either add this check in verification or remove this test because otherwise there's no
// point of it
fn fuzz_input(input: &mut BlockVerdict) -> (String, bool) {
let mut rng = rand::thread_rng();

Expand Down
9 changes: 4 additions & 5 deletions proto-compiler/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const QUOTED: &str = r#"#[serde(with = "crate::serializers::from_str")]"#;
const QUOTED_WITH_DEFAULT: &str = r#"#[serde(with = "crate::serializers::from_str", default)]"#;
const HEXSTRING: &str = r#"#[serde(with = "crate::serializers::bytes::hexstring")]"#;
const BASE64STRING: &str = r#"#[serde(with = "crate::serializers::bytes::base64string")]"#;
const TIMESTAMP: &str = r#"#[serde(with = "crate::serializers::option_timestamp")]"#;
const OPTIONAL: &str = r#"#[serde(with = "crate::serializers::optional")]"#;
const VEC_SKIP_IF_EMPTY: &str =
r#"#[serde(skip_serializing_if = "Vec::is_empty", with = "serde_bytes")]"#;
const NULLABLEVECARRAY: &str = r#"#[serde(with = "crate::serializers::txs")]"#;
Expand Down Expand Up @@ -50,7 +50,6 @@ pub static CUSTOM_TYPE_ATTRIBUTES: &[(&str, &str)] = &[
(".tendermint.types.Vote", SERIALIZED),
(".tendermint.types.BlockID", SERIALIZED),
(".tendermint.types.PartSetHeader", SERIALIZED),
(".google.protobuf.Timestamp", SERIALIZED),
(".tendermint.types.LightClientAttackEvidence", SERIALIZED),
(".tendermint.types.LightBlock", SERIALIZED),
(".tendermint.types.SignedHeader", SERIALIZED),
Expand Down Expand Up @@ -100,7 +99,7 @@ pub static CUSTOM_FIELD_ATTRIBUTES: &[(&str, &str)] = &[
),
(".tendermint.types.PartSetHeader.hash", HEXSTRING),
(".tendermint.types.Header.height", QUOTED),
(".tendermint.types.Header.time", TIMESTAMP),
(".tendermint.types.Header.time", OPTIONAL),
(".tendermint.types.Header.last_commit_hash", HEXSTRING),
(".tendermint.types.Header.data_hash", HEXSTRING),
(".tendermint.types.Header.validators_hash", HEXSTRING),
Expand All @@ -115,14 +114,14 @@ pub static CUSTOM_FIELD_ATTRIBUTES: &[(&str, &str)] = &[
(".tendermint.types.Commit.height", QUOTED),
(".tendermint.types.Commit.signatures", NULLABLE),
(".tendermint.types.CommitSig.validator_address", HEXSTRING),
(".tendermint.types.CommitSig.timestamp", TIMESTAMP),
(".tendermint.types.CommitSig.timestamp", OPTIONAL),
(".tendermint.types.CommitSig.signature", BASE64STRING),
(".tendermint.types.Vote.round", QUOTED),
(".tendermint.types.Vote.height", QUOTED),
(".tendermint.types.Vote.validator_index", QUOTED),
(".tendermint.types.Vote.validator_address", HEXSTRING),
(".tendermint.types.Vote.signature", BASE64STRING),
(".tendermint.types.Vote.timestamp", TIMESTAMP),
(".tendermint.types.Vote.timestamp", OPTIONAL),
(".tendermint.types.Validator.address", HEXSTRING),
(
".tendermint.types.Validator.voting_power",
Expand Down
6 changes: 3 additions & 3 deletions proto/src/prost/tendermint.types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ pub struct Header {
#[serde(with = "crate::serializers::from_str")]
pub height: i64,
#[prost(message, optional, tag="4")]
#[serde(with = "crate::serializers::option_timestamp")]
#[serde(with = "crate::serializers::optional")]
pub time: ::std::option::Option<super::super::google::protobuf::Timestamp>,
/// prev block info
#[prost(message, optional, tag="5")]
Expand Down Expand Up @@ -155,7 +155,7 @@ pub struct Vote {
#[prost(message, optional, tag="4")]
pub block_id: ::std::option::Option<BlockId>,
#[prost(message, optional, tag="5")]
#[serde(with = "crate::serializers::option_timestamp")]
#[serde(with = "crate::serializers::optional")]
pub timestamp: ::std::option::Option<super::super::google::protobuf::Timestamp>,
#[prost(bytes, tag="6")]
#[serde(with = "crate::serializers::bytes::hexstring")]
Expand Down Expand Up @@ -197,7 +197,7 @@ pub struct CommitSig {
#[serde(with = "crate::serializers::bytes::hexstring")]
pub validator_address: std::vec::Vec<u8>,
#[prost(message, optional, tag="3")]
#[serde(with = "crate::serializers::option_timestamp")]
#[serde(with = "crate::serializers::optional")]
pub timestamp: ::std::option::Option<super::super::google::protobuf::Timestamp>,
#[prost(bytes, tag="4")]
#[serde(with = "crate::serializers::bytes::base64string")]
Expand Down
1 change: 1 addition & 0 deletions proto/src/protobuf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
/// restricting to that range, we ensure that we can convert to and from [RFC
/// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
#[derive(Clone, PartialEq, ::prost::Message, ::serde::Deserialize, ::serde::Serialize)]
#[serde(from = "crate::serializers::timestamp::Rfc3339", into = "crate::serializers::timestamp::Rfc3339")]
pub struct Timestamp {
/// Represents seconds of UTC time since Unix epoch
/// 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
Expand Down
2 changes: 1 addition & 1 deletion proto/src/serializers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ pub mod bytes;
pub mod evidence;
pub mod from_str;
pub mod nullable;
pub mod option_timestamp;
pub mod optional;
pub mod time_duration;
pub mod timestamp;
pub mod txs;
2 changes: 1 addition & 1 deletion proto/src/serializers/nullable.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Serialize/deserialize Option<T> type.
//! Serialize/deserialize `nil`able type into T, where nil turns into the default impl.
use serde::{Deserialize, Deserializer, Serialize, Serializer};

/// Deserialize Option<T>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,52 @@
//! Serialize/deserialize Option<Timestamp> type from and into string:
//! Serialize/deserialize Timestamp type from and into string:
use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer};

use crate::google::protobuf::Timestamp;
use chrono::{DateTime, Datelike, LocalResult, TimeZone, Timelike, Utc};
use serde::ser::Error;

/// Helper struct to serialize and deserialize Timestamp into an RFC3339-compatible string
/// This is required because the serde `with` attribute is only available to fields of a struct but
/// not the whole struct.
#[derive(Serialize, Deserialize)]
#[serde(transparent)]
pub struct Rfc3339(#[serde(with = "crate::serializers::timestamp")] Timestamp);
impl From<Timestamp> for Rfc3339 {
fn from(value: Timestamp) -> Self {
Rfc3339(value)
}
}
impl From<Rfc3339> for Timestamp {
fn from(value: Rfc3339) -> Self {
value.0
}
}

/// Deserialize string into Timestamp
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Timestamp>, D::Error>
pub fn deserialize<'de, D>(deserializer: D) -> Result<Timestamp, D::Error>
where
D: Deserializer<'de>,
{
let value_string = String::deserialize(deserializer)?;
let value_datetime = DateTime::parse_from_rfc3339(value_string.as_str())
.map_err(|e| D::Error::custom(format!("{}", e)))?;
Ok(Some(Timestamp {
Ok(Timestamp {
seconds: value_datetime.timestamp(),
nanos: value_datetime.timestamp_subsec_nanos() as i32,
}))
})
}

/// Serialize from Timestamp into string
pub fn serialize<S>(value: &Option<Timestamp>, serializer: S) -> Result<S::Ok, S::Error>
pub fn serialize<S>(value: &Timestamp, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let value = value
.as_ref()
.ok_or_else(|| S::Error::custom("no time found"))?;
if value.nanos < 0 {
return Err(S::Error::custom("invalid nanoseconds in time"));
}
match Utc.timestamp_opt(value.seconds, value.nanos as u32) {
LocalResult::None => Err(S::Error::custom("invalid time")),
LocalResult::Single(t) => Ok(to_rfc3999(t)),
LocalResult::Single(t) => Ok(to_rfc3339_custom(t)),
LocalResult::Ambiguous(_, _) => Err(S::Error::custom("ambiguous time")),
}?
.serialize(serializer)
Expand All @@ -41,7 +55,7 @@ where
// Due to incompatibilities between the way that `chrono` serializes timestamps
// and the way that Go does for RFC3339, we unfortunately need to define our
// own timestamp serialization mechanism.
fn to_rfc3999(t: DateTime<Utc>) -> String {
fn to_rfc3339_custom(t: DateTime<Utc>) -> String {
let nanos = format!(".{}", t.nanosecond());
format!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}{}Z",
Expand Down Expand Up @@ -95,8 +109,7 @@ mod test {
fn json_timestamp_precision() {
#[derive(Serialize, Deserialize)]
struct TimestampWrapper {
#[serde(with = "crate::serializers::option_timestamp")]
timestamp: Option<Timestamp>,
timestamp: Timestamp,
}
let test_timestamps = vec![
"2020-09-14T16:33:54.21191421Z",
Expand Down
2 changes: 1 addition & 1 deletion rpc/src/endpoint/net_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ pub struct Monitor {
pub active: bool,

/// When the monitor started
#[serde(rename = "Start", with = "serializers::time")]
#[serde(rename = "Start")]
pub start: Time,

/// Duration of this monitor
Expand Down
1 change: 0 additions & 1 deletion rpc/src/endpoint/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ pub struct SyncInfo {
pub latest_block_height: block::Height,

/// Latest block time
#[serde(with = "tendermint::serializers::time")]
pub latest_block_time: Time,

/// Are we catching up?
Expand Down
1 change: 0 additions & 1 deletion tendermint/src/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use tendermint_proto::google::protobuf::Timestamp;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Genesis<AppState = serde_json::Value> {
/// Time of genesis
#[serde(with = "crate::serializers::time")]
pub genesis_time: Time,

/// Chain ID
Expand Down
1 change: 0 additions & 1 deletion tendermint/src/serializers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,3 @@ pub use tendermint_proto::serializers::*;
pub mod apphash;
pub mod hash;
pub mod option_hash;
pub mod time;
25 changes: 0 additions & 25 deletions tendermint/src/serializers/time.rs

This file was deleted.