diff --git a/.changelog/unreleased/bug-fixes/1348-valset-deserialization-fixes.md b/.changelog/unreleased/bug-fixes/1348-valset-deserialization-fixes.md new file mode 100644 index 000000000..615074d16 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1348-valset-deserialization-fixes.md @@ -0,0 +1,3 @@ +- `[tendermint]` Integer overflows are prevented when calculating the total + voting power value in `validator::Set` + ([\#1348](https://github.com/informalsystems/tendermint-rs/issues/1348)). diff --git a/.changelog/unreleased/bug-fixes/1350-pubkey-serde-fixes.md b/.changelog/unreleased/bug-fixes/1350-pubkey-serde-fixes.md new file mode 100644 index 000000000..725010339 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1350-pubkey-serde-fixes.md @@ -0,0 +1,4 @@ +- `[tendermint-proto]` `Serialize` and `Deserialize` impls for + `v*::crypto::PublicKey` are corrected to match the JSON schema used by + other implementations + ([\#1350](https://github.com/informalsystems/tendermint-rs/pull/1350)). diff --git a/.changelog/unreleased/improvements/1348-valset-deserialization-fixes.md b/.changelog/unreleased/improvements/1348-valset-deserialization-fixes.md new file mode 100644 index 000000000..cf91ae114 --- /dev/null +++ b/.changelog/unreleased/improvements/1348-valset-deserialization-fixes.md @@ -0,0 +1,12 @@ +- `[tendermint]` Improve and validate deserialization of `validator::Set` + ([\#1348](https://github.com/informalsystems/tendermint-rs/issues/1348)). + The `total_voting_power` field no longer has to be present in the format + processed by `Deserialize`. If it is present, it is validated against the + sum of the `voting_power` values of the listed validators. The sum value + is also checked against the protocol-defined maximum. +- `[tendermint-proto]` In the `Deserialize` impls derived for + `v*::types::ValidatorSet`, the `total_voting_power` field value is retrieved + when present. +- `[tendermint-proto]` Add serialziation helper module + `serializers::from_str_allow_null`. Use it to allow the `proposed_priority` + field value of null in the deserialization of `v*::types::Validator`. diff --git a/proto/src/prost/v0_34/tendermint.crypto.rs b/proto/src/prost/v0_34/tendermint.crypto.rs index a6273013b..cfd024d19 100644 --- a/proto/src/prost/v0_34/tendermint.crypto.rs +++ b/proto/src/prost/v0_34/tendermint.crypto.rs @@ -56,7 +56,6 @@ pub struct ProofOps { pub ops: ::prost::alloc::vec::Vec, } /// PublicKey defines the keys available for use with Validators -#[derive(::serde::Deserialize, ::serde::Serialize)] #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublicKey { diff --git a/proto/src/prost/v0_34/tendermint.types.rs b/proto/src/prost/v0_34/tendermint.types.rs index c6e153fb3..d33c44a08 100644 --- a/proto/src/prost/v0_34/tendermint.types.rs +++ b/proto/src/prost/v0_34/tendermint.types.rs @@ -7,7 +7,8 @@ pub struct ValidatorSet { #[prost(message, optional, tag = "2")] pub proposer: ::core::option::Option, #[prost(int64, tag = "3")] - #[serde(skip)] + #[serde(with = "crate::serializers::from_str", default)] + #[serde(skip_serializing)] pub total_voting_power: i64, } #[derive(::serde::Deserialize, ::serde::Serialize)] @@ -23,7 +24,8 @@ pub struct Validator { #[serde(alias = "power", with = "crate::serializers::from_str")] pub voting_power: i64, #[prost(int64, tag = "4")] - #[serde(with = "crate::serializers::from_str", default)] + #[serde(with = "crate::serializers::from_str_allow_null")] + #[serde(default)] pub proposer_priority: i64, } #[allow(clippy::derive_partial_eq_without_eq)] diff --git a/proto/src/prost/v0_37/tendermint.crypto.rs b/proto/src/prost/v0_37/tendermint.crypto.rs index a6273013b..cfd024d19 100644 --- a/proto/src/prost/v0_37/tendermint.crypto.rs +++ b/proto/src/prost/v0_37/tendermint.crypto.rs @@ -56,7 +56,6 @@ pub struct ProofOps { pub ops: ::prost::alloc::vec::Vec, } /// PublicKey defines the keys available for use with Validators -#[derive(::serde::Deserialize, ::serde::Serialize)] #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublicKey { diff --git a/proto/src/prost/v0_37/tendermint.types.rs b/proto/src/prost/v0_37/tendermint.types.rs index 9ec028f2b..7d2145ef6 100644 --- a/proto/src/prost/v0_37/tendermint.types.rs +++ b/proto/src/prost/v0_37/tendermint.types.rs @@ -7,7 +7,8 @@ pub struct ValidatorSet { #[prost(message, optional, tag = "2")] pub proposer: ::core::option::Option, #[prost(int64, tag = "3")] - #[serde(skip)] + #[serde(with = "crate::serializers::from_str", default)] + #[serde(skip_serializing)] pub total_voting_power: i64, } #[derive(::serde::Deserialize, ::serde::Serialize)] @@ -23,7 +24,8 @@ pub struct Validator { #[serde(alias = "power", with = "crate::serializers::from_str")] pub voting_power: i64, #[prost(int64, tag = "4")] - #[serde(with = "crate::serializers::from_str", default)] + #[serde(with = "crate::serializers::from_str_allow_null")] + #[serde(default)] pub proposer_priority: i64, } #[allow(clippy::derive_partial_eq_without_eq)] diff --git a/proto/src/prost/v0_38/tendermint.crypto.rs b/proto/src/prost/v0_38/tendermint.crypto.rs index a6273013b..cfd024d19 100644 --- a/proto/src/prost/v0_38/tendermint.crypto.rs +++ b/proto/src/prost/v0_38/tendermint.crypto.rs @@ -56,7 +56,6 @@ pub struct ProofOps { pub ops: ::prost::alloc::vec::Vec, } /// PublicKey defines the keys available for use with Validators -#[derive(::serde::Deserialize, ::serde::Serialize)] #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct PublicKey { diff --git a/proto/src/prost/v0_38/tendermint.types.rs b/proto/src/prost/v0_38/tendermint.types.rs index 2d9bca00f..52a3a5d2e 100644 --- a/proto/src/prost/v0_38/tendermint.types.rs +++ b/proto/src/prost/v0_38/tendermint.types.rs @@ -104,7 +104,8 @@ pub struct ValidatorSet { #[prost(message, optional, tag = "2")] pub proposer: ::core::option::Option, #[prost(int64, tag = "3")] - #[serde(skip)] + #[serde(with = "crate::serializers::from_str", default)] + #[serde(skip_serializing)] pub total_voting_power: i64, } #[derive(::serde::Deserialize, ::serde::Serialize)] @@ -120,7 +121,8 @@ pub struct Validator { #[serde(alias = "power", with = "crate::serializers::from_str")] pub voting_power: i64, #[prost(int64, tag = "4")] - #[serde(with = "crate::serializers::from_str", default)] + #[serde(with = "crate::serializers::from_str_allow_null")] + #[serde(default)] pub proposer_priority: i64, } #[allow(clippy::derive_partial_eq_without_eq)] diff --git a/proto/src/serializers.rs b/proto/src/serializers.rs index 1f9ffd94a..c4db03f49 100644 --- a/proto/src/serializers.rs +++ b/proto/src/serializers.rs @@ -57,6 +57,7 @@ pub mod allow_null; pub mod bytes; pub mod evidence; pub mod from_str; +pub mod from_str_allow_null; pub mod nullable; pub mod optional; pub mod optional_from_str; @@ -64,3 +65,5 @@ pub mod part_set_header_total; pub mod time_duration; pub mod timestamp; pub mod txs; + +mod public_key; diff --git a/proto/src/serializers/from_str_allow_null.rs b/proto/src/serializers/from_str_allow_null.rs new file mode 100644 index 000000000..aaa5598b2 --- /dev/null +++ b/proto/src/serializers/from_str_allow_null.rs @@ -0,0 +1,42 @@ +//! Combines [`from_str`] and [`allow_null`]. +//! +//! Use this module to serialize and deserialize any `T` where `T` implements +//! [`FromStr`] and [`Display`] to convert from or into a string. +//! The serialized form is that of `Option`, +//! and a nil is deserialized to the `Default` value. For JSON, this means both +//! quoted string values and `null` are accepted. A value is always serialized +//! as `Some`. +//! Note that this can be used for all primitive data types. +//! +//! [`from_str`]: super::from_str +//! [`allow_null`]: super::allow_null + +use alloc::borrow::Cow; +use core::fmt::Display; +use core::str::FromStr; + +use serde::{de::Error as _, Deserialize, Deserializer, Serializer}; + +use crate::prelude::*; + +/// Deserialize a nullable string into T +pub fn deserialize<'de, D, T>(deserializer: D) -> Result +where + D: Deserializer<'de>, + T: FromStr + Default, + ::Err: Display, +{ + match >>::deserialize(deserializer)? { + Some(s) => s.parse::().map_err(D::Error::custom), + None => Ok(T::default()), + } +} + +/// Serialize from T into string +pub fn serialize(value: &T, serializer: S) -> Result +where + S: Serializer, + T: Display, +{ + serializer.serialize_some(&value.to_string()) +} diff --git a/proto/src/serializers/public_key.rs b/proto/src/serializers/public_key.rs new file mode 100644 index 000000000..1c1c47d72 --- /dev/null +++ b/proto/src/serializers/public_key.rs @@ -0,0 +1,71 @@ +mod v0_34 { + use crate::v0_34::crypto::{public_key, PublicKey}; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + impl<'de> Deserialize<'de> for PublicKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let sum = Option::::deserialize(deserializer)?; + Ok(Self { sum }) + } + } + + impl Serialize for PublicKey { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.sum.serialize(serializer) + } + } +} + +mod v0_37 { + use crate::v0_37::crypto::{public_key, PublicKey}; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + impl<'de> Deserialize<'de> for PublicKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let sum = Option::::deserialize(deserializer)?; + Ok(Self { sum }) + } + } + + impl Serialize for PublicKey { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.sum.serialize(serializer) + } + } +} + +mod v0_38 { + use crate::v0_38::crypto::{public_key, PublicKey}; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + impl<'de> Deserialize<'de> for PublicKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let sum = Option::::deserialize(deserializer)?; + Ok(Self { sum }) + } + } + + impl Serialize for PublicKey { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.sum.serialize(serializer) + } + } +} diff --git a/tendermint/src/error.rs b/tendermint/src/error.rs index 6ee216c9a..8fcf6294e 100644 --- a/tendermint/src/error.rs +++ b/tendermint/src/error.rs @@ -16,7 +16,7 @@ define_error! { InvalidKey { detail: String } - |e| { format_args!("invalid key: {e}") }, + |e| { format_args!("invalid key: {}", e.detail) }, Length |_| { format_args!("length error") }, @@ -229,6 +229,12 @@ define_error! { NegativeProofIndex [ DisplayOnly ] |_| { "negative item index in proof" }, + + TotalVotingPowerMismatch + |_| { "total voting power in validator set does not match the sum of participants' powers" }, + + TotalVotingPowerOverflow + |_| { "total voting power in validator set exceeds the allowed maximum" }, } } diff --git a/tendermint/src/validator.rs b/tendermint/src/validator.rs index 96499732f..7db1a7cef 100644 --- a/tendermint/src/validator.rs +++ b/tendermint/src/validator.rs @@ -1,7 +1,9 @@ //! Tendermint validators use serde::{Deserialize, Serialize}; -use tendermint_proto::v0_37::types::SimpleValidator as RawSimpleValidator; +use tendermint_proto::v0_38::types::{ + SimpleValidator as RawSimpleValidator, ValidatorSet as RawValidatorSet, +}; use tendermint_proto::Protobuf; use crate::{ @@ -17,6 +19,7 @@ use crate::{ /// Validator set contains a vector of validators #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(try_from = "RawValidatorSet")] pub struct Set { validators: Vec, proposer: Option, @@ -24,23 +27,47 @@ pub struct Set { } impl Set { + pub const MAX_TOTAL_VOTING_POWER: u64 = (i64::MAX / 8) as u64; + /// Constructor - pub fn new(mut validators: Vec, proposer: Option) -> Set { - Self::sort_validators(&mut validators); + pub fn new(validators: Vec, proposer: Option) -> Set { + Self::try_from_parts(validators, proposer, 0).unwrap() + } + fn try_from_parts( + mut validators: Vec, + proposer: Option, + unvalidated_total_voting_power: i64, + ) -> Result { // Compute the total voting power let total_voting_power = validators .iter() .map(|v| v.power.value()) - .sum::() - .try_into() - .unwrap(); + .fold(0u64, |acc, v| acc.saturating_add(v)); + + if total_voting_power > Self::MAX_TOTAL_VOTING_POWER { + return Err(Error::total_voting_power_overflow()); + } + + // The conversion cannot fail as we have validated against a smaller limit. + let total_voting_power: vote::Power = total_voting_power.try_into().unwrap(); + + // If the given total voting power is not the default value, + // validate it against the sum of voting powers of the participants. + if unvalidated_total_voting_power != 0 { + let given_val: vote::Power = unvalidated_total_voting_power.try_into()?; + if given_val != total_voting_power { + return Err(Error::total_voting_power_mismatch()); + } + } + + Self::sort_validators(&mut validators); - Set { + Ok(Set { validators, proposer, total_voting_power, - } + }) } /// Convenience constructor for cases where there is no proposer @@ -266,9 +293,8 @@ tendermint_pb_modules! { .collect::, _>>()?; let proposer = value.proposer.map(TryInto::try_into).transpose()?; - let validator_set = Self::new(validators, proposer); - Ok(validator_set) + Self::try_from_parts(validators, proposer, value.total_voting_power) } } @@ -481,4 +507,218 @@ mod tests { assert_eq!(u64::from(update1.power), 573929); assert_eq!(update1, update2); } + + #[test] + fn validator_set_deserialize_all_fields() { + const VSET: &str = r#"{ + "validators": [ + { + "address": "01F527D77D3FFCC4FCFF2DDC2952EEA5414F2A33", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "OAaNq3DX/15fGJP2MI6bujt1GRpvjwrqIevChirJsbc=" + }, + "voting_power": "50", + "proposer_priority": "-150" + }, + { + "address": "026CC7B6F3E62F789DBECEC59766888B5464737D", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "+vlsKpn6ojn+UoTZl+w+fxeqm6xvUfBokTcKfcG3au4=" + }, + "voting_power": "42", + "proposer_priority": "50" + } + ], + "proposer": { + "address": "01F527D77D3FFCC4FCFF2DDC2952EEA5414F2A33", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "OAaNq3DX/15fGJP2MI6bujt1GRpvjwrqIevChirJsbc=" + }, + "voting_power": "50", + "proposer_priority": "-150" + }, + "total_voting_power": "92" + }"#; + + let vset = serde_json::from_str::(VSET).unwrap(); + assert_eq!(vset.total_voting_power().value(), 92); + } + + #[test] + fn validator_set_deserialize_no_total_voting_power() { + const VSET: &str = r#"{ + "validators": [ + { + "address": "01F527D77D3FFCC4FCFF2DDC2952EEA5414F2A33", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "OAaNq3DX/15fGJP2MI6bujt1GRpvjwrqIevChirJsbc=" + }, + "voting_power": "50", + "proposer_priority": "-150" + }, + { + "address": "026CC7B6F3E62F789DBECEC59766888B5464737D", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "+vlsKpn6ojn+UoTZl+w+fxeqm6xvUfBokTcKfcG3au4=" + }, + "voting_power": "42", + "proposer_priority": "50" + } + ], + "proposer": { + "address": "01F527D77D3FFCC4FCFF2DDC2952EEA5414F2A33", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "OAaNq3DX/15fGJP2MI6bujt1GRpvjwrqIevChirJsbc=" + }, + "voting_power": "50", + "proposer_priority": "-150" + } + }"#; + + let vset = serde_json::from_str::(VSET).unwrap(); + assert_eq!(vset.total_voting_power().value(), 92); + } + + #[test] + fn validator_set_deserialize_total_voting_power_mismatch() { + const VSET: &str = r#"{ + "validators": [ + { + "address": "01F527D77D3FFCC4FCFF2DDC2952EEA5414F2A33", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "OAaNq3DX/15fGJP2MI6bujt1GRpvjwrqIevChirJsbc=" + }, + "voting_power": "50", + "proposer_priority": "-150" + }, + { + "address": "026CC7B6F3E62F789DBECEC59766888B5464737D", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "+vlsKpn6ojn+UoTZl+w+fxeqm6xvUfBokTcKfcG3au4=" + }, + "voting_power": "42", + "proposer_priority": "50" + } + ], + "proposer": { + "address": "01F527D77D3FFCC4FCFF2DDC2952EEA5414F2A33", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "OAaNq3DX/15fGJP2MI6bujt1GRpvjwrqIevChirJsbc=" + }, + "voting_power": "50", + "proposer_priority": "-150" + }, + "total_voting_power": "100" + }"#; + + let err = serde_json::from_str::(VSET).unwrap_err(); + assert!( + err.to_string() + .contains("total voting power in validator set does not match the sum of participants' powers"), + "{err}" + ); + } + + #[test] + fn validator_set_deserialize_total_voting_power_exceeds_limit() { + const VSET: &str = r#"{ + "validators": [ + { + "address": "01F527D77D3FFCC4FCFF2DDC2952EEA5414F2A33", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "OAaNq3DX/15fGJP2MI6bujt1GRpvjwrqIevChirJsbc=" + }, + "voting_power": "576460752303423488", + "proposer_priority": "-150" + }, + { + "address": "026CC7B6F3E62F789DBECEC59766888B5464737D", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "+vlsKpn6ojn+UoTZl+w+fxeqm6xvUfBokTcKfcG3au4=" + }, + "voting_power": "576460752303423488", + "proposer_priority": "50" + } + ], + "proposer": { + "address": "01F527D77D3FFCC4FCFF2DDC2952EEA5414F2A33", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "OAaNq3DX/15fGJP2MI6bujt1GRpvjwrqIevChirJsbc=" + }, + "voting_power": "50", + "proposer_priority": "-150" + }, + "total_voting_power": "92" + }"#; + + let err = serde_json::from_str::(VSET).unwrap_err(); + assert!( + err.to_string() + .contains("total voting power in validator set exceeds the allowed maximum"), + "{err}" + ); + } + + #[test] + fn validator_set_deserialize_total_voting_power_overflow() { + const VSET: &str = r#"{ + "validators": [ + { + "address": "01F527D77D3FFCC4FCFF2DDC2952EEA5414F2A33", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "OAaNq3DX/15fGJP2MI6bujt1GRpvjwrqIevChirJsbc=" + }, + "voting_power": "6148914691236517205", + "proposer_priority": "-150" + }, + { + "address": "026CC7B6F3E62F789DBECEC59766888B5464737D", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "+vlsKpn6ojn+UoTZl+w+fxeqm6xvUfBokTcKfcG3au4=" + }, + "voting_power": "6148914691236517205", + "proposer_priority": "50" + }, + { + "address": "044EB1BB5D4C1CDB90029648439AEB10431FF295", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "Wc790fkCDAi7LvZ4UIBAIJSNI+Rp2aU80/8l+idZ/wI=" + }, + "voting_power": "6148914691236517206", + "proposer_priority": "50" + } + ], + "proposer": { + "address": "01F527D77D3FFCC4FCFF2DDC2952EEA5414F2A33", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "OAaNq3DX/15fGJP2MI6bujt1GRpvjwrqIevChirJsbc=" + }, + "voting_power": "50", + "proposer_priority": "-150" + } + }"#; + + let err = serde_json::from_str::(VSET).unwrap_err(); + assert!( + err.to_string() + .contains("total voting power in validator set exceeds the allowed maximum"), + "{err}" + ); + } } diff --git a/tools/proto-compiler/src/constants.rs b/tools/proto-compiler/src/constants.rs index a6e9b6873..4c150e6b9 100644 --- a/tools/proto-compiler/src/constants.rs +++ b/tools/proto-compiler/src/constants.rs @@ -41,13 +41,14 @@ const TYPE_TAG: &str = r#"#[serde(tag = "type", content = "value")]"#; /// Predefined custom attributes for field annotations const QUOTED: &str = r#"#[serde(with = "crate::serializers::from_str")]"#; const QUOTED_WITH_DEFAULT: &str = r#"#[serde(with = "crate::serializers::from_str", default)]"#; +const QUOTED_ALLOW_NULL: &str = r#"#[serde(with = "crate::serializers::from_str_allow_null")]"#; const DEFAULT: &str = r#"#[serde(default)]"#; const HEXSTRING: &str = r#"#[serde(with = "crate::serializers::bytes::hexstring")]"#; const BASE64STRING: &str = r#"#[serde(with = "crate::serializers::bytes::base64string")]"#; const VEC_BASE64STRING: &str = r#"#[serde(with = "crate::serializers::bytes::vec_base64string")]"#; const OPTIONAL: &str = r#"#[serde(with = "crate::serializers::optional")]"#; const BYTES_SKIP_IF_EMPTY: &str = r#"#[serde(skip_serializing_if = "bytes::Bytes::is_empty")]"#; -const SKIP: &str = "#[serde(skip)]"; +const SKIP_SERIALIZING: &str = "#[serde(skip_serializing)]"; const RENAME_ALL_PASCALCASE: &str = r#"#[serde(rename_all = "PascalCase")]"#; const NULLABLEVECARRAY: &str = r#"#[serde(with = "crate::serializers::txs")]"#; const NULLABLE: &str = r#"#[serde(with = "crate::serializers::nullable")]"#; @@ -98,7 +99,7 @@ pub static CUSTOM_TYPE_ATTRIBUTES: &[(&str, &str)] = &[ (".tendermint.types.Commit", SERIALIZED), (".tendermint.types.CommitSig", SERIALIZED), (".tendermint.types.ValidatorSet", SERIALIZED), - (".tendermint.crypto.PublicKey", SERIALIZED), + (".tendermint.crypto.PublicKey.sum", SERIALIZED), (".tendermint.crypto.PublicKey.sum", TYPE_TAG), (".tendermint.abci.ResponseInfo", SERIALIZED), (".tendermint.types.CanonicalBlockID", SERIALIZED), @@ -197,9 +198,17 @@ pub static CUSTOM_FIELD_ATTRIBUTES: &[(&str, &str)] = &[ ), // https://github.com/tendermint/tendermint/issues/5549 ( ".tendermint.types.Validator.proposer_priority", + QUOTED_ALLOW_NULL, + ), // null occurs in some LightBlock data + (".tendermint.types.Validator.proposer_priority", DEFAULT), // Default is for /genesis deserialization + ( + ".tendermint.types.ValidatorSet.total_voting_power", QUOTED_WITH_DEFAULT, - ), // Default is for /genesis deserialization - (".tendermint.types.ValidatorSet.total_voting_power", SKIP), + ), + ( + ".tendermint.types.ValidatorSet.total_voting_power", + SKIP_SERIALIZING, + ), (".tendermint.types.BlockMeta.block_size", QUOTED), (".tendermint.types.BlockMeta.num_txs", QUOTED), (".tendermint.crypto.PublicKey.sum.ed25519", RENAME_EDPUBKEY),