diff --git a/CHANGES.md b/CHANGES.md index 1995ee28f..b0fc3e905 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,8 @@ +## Pending + +- Serialization refactor [#247] +- CommitSig enum implemented [#246][#247] + ## [0.13.0] (2020-04-20) Dependencies: diff --git a/tendermint/Cargo.toml b/tendermint/Cargo.toml index cc2a4889d..d4ac80dec 100644 --- a/tendermint/Cargo.toml +++ b/tendermint/Cargo.toml @@ -45,6 +45,7 @@ prost-amino-derive = "0.5" serde = { version = "1", features = ["derive"] } serde_json = { version = "1" } serde_bytes = "0.11" +serde_repr = "0.1" sha2 = { version = "0.8", default-features = false } signatory = { version = "0.19", features = ["ed25519", "ecdsa"] } signatory-dalek = "0.19" diff --git a/tendermint/src/abci/proof.rs b/tendermint/src/abci/proof.rs index bfd688a8c..ca7139294 100644 --- a/tendermint/src/abci/proof.rs +++ b/tendermint/src/abci/proof.rs @@ -19,18 +19,10 @@ pub struct ProofOp { #[serde(alias = "type")] pub field_type: String, /// Key of the ProofOp - #[serde( - default, - serialize_with = "serializers::serialize_base64", - deserialize_with = "serializers::parse_base64" - )] + #[serde(default, with = "serializers::bytes::base64string")] pub key: Vec, /// Actual data - #[serde( - default, - serialize_with = "serializers::serialize_base64", - deserialize_with = "serializers::parse_base64" - )] + #[serde(default, with = "serializers::bytes::base64string")] pub data: Vec, } diff --git a/tendermint/src/block.rs b/tendermint/src/block.rs index 7b5e7587e..1b84cfbc7 100644 --- a/tendermint/src/block.rs +++ b/tendermint/src/block.rs @@ -49,10 +49,7 @@ where #[derive(Deserialize)] struct TmpCommit { pub height: Height, - #[serde( - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(with = "serializers::primitives::string")] pub round: u64, #[serde(deserialize_with = "serializers::parse_non_empty_block_id")] pub block_id: Option, diff --git a/tendermint/src/block/commit.rs b/tendermint/src/block/commit.rs index 7d3134bf6..61ea332ab 100644 --- a/tendermint/src/block/commit.rs +++ b/tendermint/src/block/commit.rs @@ -17,10 +17,7 @@ pub struct Commit { pub height: Height, /// Round - #[serde( - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(with = "serializers::primitives::string")] pub round: u64, /// Block ID diff --git a/tendermint/src/block/commit_sig.rs b/tendermint/src/block/commit_sig.rs index 0f8ead365..5ffaf3640 100644 --- a/tendermint/src/block/commit_sig.rs +++ b/tendermint/src/block/commit_sig.rs @@ -1,78 +1,100 @@ //! CommitSig within Commit -use crate::serializers; +use crate::serializers::BlockIDFlag; +use crate::serializers::RawCommitSig; use crate::{account, Signature, Time}; -use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Serialize}; +use std::convert::TryFrom; -/// BlockIDFlag is used to indicate the validator has voted either for nil, a particular BlockID or was absent. -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum BlockIDFlag { - /// BlockIDFlagAbsent - no vote was received from a validator. - BlockIDFlagAbsent = 1, - /// BlockIDFlagCommit - voted for the Commit.BlockID. - BlockIDFlagCommit = 2, - /// BlockIDFlagNil - voted for nil. - BlockIDFlagNil = 3, +/// CommitSig represents a signature of a validator. +/// It's a part of the Commit and can be used to reconstruct the vote set given the validator set. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(try_from = "RawCommitSig")] +pub enum CommitSig { + /// no vote was received from a validator. + BlockIDFlagAbsent { + /// Validator address + validator_address: account::Id, + }, + /// voted for the Commit.BlockID. + BlockIDFlagCommit { + /// Validator address + validator_address: account::Id, + /// Timestamp of vote + timestamp: Time, + /// Signature of vote + signature: Signature, + }, + /// voted for nil. + BlockIDFlagNil { + /// Validator address + validator_address: account::Id, + /// Timestamp of vote + timestamp: Time, + /// Signature of vote + signature: Signature, + }, } -impl BlockIDFlag { - /// Deserialize this type from a byte - pub fn from_u8(byte: u8) -> Option { - match byte { - 1 => Some(BlockIDFlag::BlockIDFlagAbsent), - 2 => Some(BlockIDFlag::BlockIDFlagCommit), - 3 => Some(BlockIDFlag::BlockIDFlagNil), - _ => None, +/// CommitSig implementation +impl CommitSig { + /// Helper: Extract validator address, since it's always present (according to ADR-025) + pub fn validator_address(&self) -> &account::Id { + match &self { + CommitSig::BlockIDFlagAbsent { validator_address } => validator_address, + CommitSig::BlockIDFlagCommit { + validator_address, .. + } => validator_address, + CommitSig::BlockIDFlagNil { + validator_address, .. + } => validator_address, } } - - /// Serialize this type as a byte - pub fn to_u8(self) -> u8 { - self as u8 - } - - /// Serialize this type as a 32-bit unsigned integer - pub fn to_u32(self) -> u32 { - self as u32 - } } -impl Serialize for BlockIDFlag { - fn serialize(&self, serializer: S) -> Result { - self.to_u8().serialize(serializer) - } -} +impl TryFrom for CommitSig { + type Error = &'static str; -impl<'de> Deserialize<'de> for BlockIDFlag { - fn deserialize>(deserializer: D) -> Result { - let byte = u8::deserialize(deserializer)?; - BlockIDFlag::from_u8(byte) - .ok_or_else(|| D::Error::custom(format!("invalid block ID flag: {}", byte))) - } -} - -/// CommitSig represents a signature of a validator. -/// It's a part of the Commit and can be used to reconstruct the vote set given the validator set. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -pub struct CommitSig { - /// Block ID FLag - pub block_id_flag: BlockIDFlag, - - /// Validator address - #[serde(deserialize_with = "serializers::parse_non_empty_id")] - pub validator_address: Option, - - /// Timestamp - pub timestamp: Time, - - /// Signature - #[serde(deserialize_with = "serializers::parse_non_empty_signature")] - pub signature: Option, -} - -impl CommitSig { - /// Checks if a validator's vote is absent - pub fn is_absent(&self) -> bool { - self.block_id_flag == BlockIDFlag::BlockIDFlagAbsent + fn try_from(value: RawCommitSig) -> Result { + // Validate CommitSig (strict) + match value.block_id_flag { + BlockIDFlag::BlockIDFlagAbsent => { + if value.timestamp.is_some() { + return Err("timestamp is present for BlockIDFlagAbsent CommitSig"); + } + if value.signature.is_some() { + return Err("signature is present for BlockIDFlagAbsent CommitSig"); + } + Ok(CommitSig::BlockIDFlagAbsent { + validator_address: value.validator_address, + }) + } + BlockIDFlag::BlockIDFlagCommit => { + if value.timestamp.is_none() { + Err("timestamp is null for BlockIDFlagCommit CommitSig") + } else if value.signature.is_none() { + Err("signature is null for BlockIDFlagCommit CommitSig") + } else { + Ok(CommitSig::BlockIDFlagCommit { + validator_address: value.validator_address, + timestamp: value.timestamp.unwrap(), + signature: value.signature.unwrap(), + }) + } + } + BlockIDFlag::BlockIDFlagNil => { + if value.timestamp.is_none() { + Err("timestamp is null for BlockIDFlagNil CommitSig") + } else if value.signature.is_none() { + Err("signature is null for BlockIDFlagNil CommitSig") + } else { + Ok(CommitSig::BlockIDFlagNil { + validator_address: value.validator_address, + timestamp: value.timestamp.unwrap(), + signature: value.signature.unwrap(), + }) + } + } + } } } diff --git a/tendermint/src/block/header.rs b/tendermint/src/block/header.rs index c1c51cce8..18abcbcd1 100644 --- a/tendermint/src/block/header.rs +++ b/tendermint/src/block/header.rs @@ -45,10 +45,7 @@ pub struct Header { pub consensus_hash: Hash, /// State after txs from the previous block - #[serde( - serialize_with = "serializers::serialize_hex", - deserialize_with = "serializers::parse_hex" - )] + #[serde(with = "serializers::bytes::hexstring")] pub app_hash: Vec, /// Root hash of all results from the txs from the previous block @@ -70,17 +67,11 @@ pub struct Header { #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] pub struct Version { /// Block version - #[serde( - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(with = "serializers::primitives::string")] pub block: u64, /// App version - #[serde( - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(with = "serializers::primitives::string")] pub app: u64, } diff --git a/tendermint/src/block/parts.rs b/tendermint/src/block/parts.rs index 61e2d2170..6d147be17 100644 --- a/tendermint/src/block/parts.rs +++ b/tendermint/src/block/parts.rs @@ -10,10 +10,7 @@ use { #[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)] pub struct Header { /// Number of parts in this block - #[serde( - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(with = "serializers::primitives::string")] pub total: u64, /// Hash of the parts set header, diff --git a/tendermint/src/block/size.rs b/tendermint/src/block/size.rs index 74e585cc6..52148f1df 100644 --- a/tendermint/src/block/size.rs +++ b/tendermint/src/block/size.rs @@ -9,16 +9,10 @@ use { #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] pub struct Size { /// Maximum number of bytes in a block - #[serde( - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(with = "serializers::primitives::string")] pub max_bytes: u64, /// Maximum amount of gas which can be spent on a block - #[serde( - serialize_with = "serializers::serialize_i64", - deserialize_with = "serializers::parse_i64" - )] + #[serde(with = "serializers::primitives::string")] pub max_gas: i64, } diff --git a/tendermint/src/channel.rs b/tendermint/src/channel.rs index 880b92c15..485060cd2 100644 --- a/tendermint/src/channel.rs +++ b/tendermint/src/channel.rs @@ -15,35 +15,19 @@ pub struct Channel { pub id: Id, /// Capacity of the send queue - #[serde( - rename = "SendQueueCapacity", - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(rename = "SendQueueCapacity", with = "serializers::primitives::string")] pub send_queue_capacity: u64, /// Size of the send queue - #[serde( - rename = "SendQueueSize", - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(rename = "SendQueueSize", with = "serializers::primitives::string")] pub send_queue_size: u64, /// Priority value - #[serde( - rename = "Priority", - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(rename = "Priority", with = "serializers::primitives::string")] pub priority: u64, /// Amount of data recently sent - #[serde( - rename = "RecentlySent", - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(rename = "RecentlySent", with = "serializers::primitives::string")] pub recently_sent: u64, } diff --git a/tendermint/src/consensus/state.rs b/tendermint/src/consensus/state.rs index 37f18d499..1c691fd7e 100644 --- a/tendermint/src/consensus/state.rs +++ b/tendermint/src/consensus/state.rs @@ -18,10 +18,7 @@ pub struct State { pub height: block::Height, /// Current consensus round - #[serde( - serialize_with = "serializers::serialize_i64", - deserialize_with = "serializers::parse_i64" - )] + #[serde(with = "serializers::primitives::string")] pub round: i64, /// Current consensus step diff --git a/tendermint/src/evidence.rs b/tendermint/src/evidence.rs index 86b2eafc8..56d4814d0 100644 --- a/tendermint/src/evidence.rs +++ b/tendermint/src/evidence.rs @@ -70,10 +70,7 @@ impl AsRef<[Evidence]> for Data { #[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq)] pub struct Params { /// Maximum allowed age for evidence to be collected - #[serde( - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(with = "serializers::primitives::string")] pub max_age_num_blocks: u64, /// Max age duration @@ -84,13 +81,7 @@ pub struct Params { /// essentially, to keep the usages look cleaner /// i.e. you can avoid using serde annotations everywhere #[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] -pub struct Duration( - #[serde( - serialize_with = "serializers::serialize_duration", - deserialize_with = "serializers::parse_duration" - )] - std::time::Duration, -); +pub struct Duration(#[serde(with = "serializers::timeduration::string")] std::time::Duration); impl From for std::time::Duration { fn from(d: Duration) -> std::time::Duration { diff --git a/tendermint/src/lib.rs b/tendermint/src/lib.rs index 3d83625a3..4fb19d018 100644 --- a/tendermint/src/lib.rs +++ b/tendermint/src/lib.rs @@ -39,7 +39,7 @@ pub mod node; pub mod private_key; pub mod public_key; pub mod rpc; -mod serializers; +pub mod serializers; pub mod signature; pub mod time; mod timeout; diff --git a/tendermint/src/lite/types.rs b/tendermint/src/lite/types.rs index 2f0024ed8..00ba64d3a 100644 --- a/tendermint/src/lite/types.rs +++ b/tendermint/src/lite/types.rs @@ -90,15 +90,9 @@ pub trait TrustThreshold: Copy + Clone + Debug + Serialize + DeserializeOwned { /// [`TrustThreshold`] which can be passed into all relevant methods. #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct TrustThresholdFraction { - #[serde( - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(with = "serializers::primitives::string")] numerator: u64, - #[serde( - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(with = "serializers::primitives::string")] denominator: u64, } diff --git a/tendermint/src/lite_impl/signed_header.rs b/tendermint/src/lite_impl/signed_header.rs index 33e94ddd3..e6639c394 100644 --- a/tendermint/src/lite_impl/signed_header.rs +++ b/tendermint/src/lite_impl/signed_header.rs @@ -1,9 +1,10 @@ //! [`lite::SignedHeader`] implementation for [`block::signed_header::SignedHeader`]. +use crate::block::CommitSig; use crate::lite::error::{Error, Kind}; use crate::lite::ValidatorSet; use crate::validator::Set; -use crate::{block, block::BlockIDFlag, hash, lite, vote}; +use crate::{block, hash, lite, vote}; use anomaly::fail; use std::convert::TryFrom; @@ -59,8 +60,18 @@ impl lite::Commit for block::signed_header::SignedHeader { ); } + // TODO: this last check is only necessary if we do full verification (2/3) + // returns ImplementationSpecific error if it detects a signer + // that is not present in the validator set: for commit_sig in self.commit.signatures.iter() { - commit_sig.validate(vals)?; + if vals.validator(*commit_sig.validator_address()) == None { + fail!( + Kind::ImplementationSpecific, + "Found a faulty signer ({}) not present in the validator set ({})", + commit_sig.validator_address(), + vals.hash() + ); + } } Ok(()) @@ -72,87 +83,43 @@ impl lite::Commit for block::signed_header::SignedHeader { fn non_absent_votes(commit: &block::Commit) -> Vec { let mut votes: Vec = Default::default(); for (i, commit_sig) in commit.signatures.iter().enumerate() { - if commit_sig.is_absent() { - continue; - } - - if let Some(val_addr) = commit_sig.validator_address { - if let Some(sig) = commit_sig.signature.clone() { - let vote = vote::Vote { - vote_type: vote::Type::Precommit, - height: commit.height, - round: commit.round, - block_id: Option::from(commit.block_id.clone()), - timestamp: commit_sig.timestamp, - validator_address: val_addr, - validator_index: u64::try_from(i) - .expect("usize to u64 conversion failed for validator index"), - signature: sig, - }; - votes.push(vote); + let extracted_validator_address; + let extracted_timestamp; + let extracted_signature; + match commit_sig { + CommitSig::BlockIDFlagAbsent { .. } => continue, + CommitSig::BlockIDFlagCommit { + validator_address, + timestamp, + signature, + } => { + extracted_validator_address = validator_address; + extracted_timestamp = timestamp; + extracted_signature = signature; } - } - } - votes -} - -// TODO: consider moving this into commit_sig.rs instead and making it pub -impl block::commit_sig::CommitSig { - fn validate(&self, vals: &Set) -> Result<(), Error> { - match self.block_id_flag { - BlockIDFlag::BlockIDFlagAbsent => { - if self.validator_address.is_some() { - fail!( - Kind::ImplementationSpecific, - "validator address is present for absent CommitSig {:#?}", - self - ); - } - if self.signature.is_some() { - fail!( - Kind::ImplementationSpecific, - "signature is present for absent CommitSig {:#?}", - self - ); - } - // TODO: deal with Time - // see https://github.com/informalsystems/tendermint-rs/pull/196#discussion_r401027989 - } - BlockIDFlag::BlockIDFlagCommit | BlockIDFlag::BlockIDFlagNil => { - if self.validator_address.is_none() { - fail!( - Kind::ImplementationSpecific, - "missing validator address for non-absent CommitSig {:#?}", - self - ); - } - if self.signature.is_none() { - fail!( - Kind::ImplementationSpecific, - "missing signature for non-absent CommitSig {:#?}", - self - ); - } - // TODO: this last check is only necessary if we do full verification (2/3) but the - // above checks should actually happen always (even if we skip forward) - // - // returns ImplementationSpecific error if it detects a signer - // that is not present in the validator set: - if let Some(val_addr) = self.validator_address { - if vals.validator(val_addr) == None { - fail!( - Kind::ImplementationSpecific, - "Found a faulty signer ({}) not present in the validator set ({})", - val_addr, - vals.hash() - ); - } - } + CommitSig::BlockIDFlagNil { + validator_address, + timestamp, + signature, + } => { + extracted_validator_address = validator_address; + extracted_timestamp = timestamp; + extracted_signature = signature; } } - - Ok(()) + votes.push(vote::Vote { + vote_type: vote::Type::Precommit, + height: commit.height, + round: commit.round, + block_id: Option::from(commit.block_id.clone()), + timestamp: *extracted_timestamp, + validator_address: *extracted_validator_address, + validator_index: u64::try_from(i) + .expect("usize to u64 conversion failed for validator index"), + signature: extracted_signature.clone(), + }) } + votes } impl block::signed_header::SignedHeader { diff --git a/tendermint/src/node/info.rs b/tendermint/src/node/info.rs index 11cc44b62..f17109fe5 100644 --- a/tendermint/src/node/info.rs +++ b/tendermint/src/node/info.rs @@ -36,24 +36,15 @@ pub struct Info { #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct ProtocolVersionInfo { /// P2P protocol version - #[serde( - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(with = "serializers::primitives::string")] pub p2p: u64, /// Block version - #[serde( - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(with = "serializers::primitives::string")] pub block: u64, /// App version - #[serde( - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(with = "serializers::primitives::string")] pub app: u64, } diff --git a/tendermint/src/rpc/endpoint/abci_info.rs b/tendermint/src/rpc/endpoint/abci_info.rs index ce0a621c6..102a03832 100644 --- a/tendermint/src/rpc/endpoint/abci_info.rs +++ b/tendermint/src/rpc/endpoint/abci_info.rs @@ -36,10 +36,7 @@ pub struct AbciInfo { pub version: String, /// App version - #[serde( - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(with = "serializers::primitives::string")] pub app_version: u64, /// Last block height diff --git a/tendermint/src/rpc/endpoint/abci_query.rs b/tendermint/src/rpc/endpoint/abci_query.rs index f23fbd325..f7be4a996 100644 --- a/tendermint/src/rpc/endpoint/abci_query.rs +++ b/tendermint/src/rpc/endpoint/abci_query.rs @@ -14,10 +14,7 @@ pub struct Request { path: Option, /// Data to query - #[serde( - serialize_with = "serializers::serialize_hex", - deserialize_with = "serializers::parse_hex" - )] + #[serde(with = "serializers::bytes::hexstring")] data: Vec, /// Block height @@ -75,27 +72,16 @@ pub struct AbciQuery { pub info: String, /// Index - #[serde( - serialize_with = "serializers::serialize_i64", - deserialize_with = "serializers::parse_i64" - )] + #[serde(with = "serializers::primitives::string")] pub index: i64, /// Key - #[serde( - default, - serialize_with = "serializers::serialize_base64", - deserialize_with = "serializers::parse_base64" - )] + #[serde(default, with = "serializers::bytes::base64string")] pub key: Vec, - /// Value (might be explicit null) - #[serde( - default, - serialize_with = "serializers::serialize_option_base64", - deserialize_with = "serializers::parse_option_base64" - )] - pub value: Option>, + /// Value + #[serde(default, with = "serializers::bytes::base64string")] + pub value: Vec, /// Proof (might be explicit null) pub proof: Option, diff --git a/tendermint/src/rpc/endpoint/net_info.rs b/tendermint/src/rpc/endpoint/net_info.rs index 6b770ac08..fd6324e4c 100644 --- a/tendermint/src/rpc/endpoint/net_info.rs +++ b/tendermint/src/rpc/endpoint/net_info.rs @@ -30,10 +30,7 @@ pub struct Response { pub listeners: Vec, /// Number of connected peers - #[serde( - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(with = "serializers::primitives::string")] pub n_peers: u64, /// Peer information @@ -72,11 +69,7 @@ pub struct PeerInfo { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct ConnectionStatus { /// Duration of this connection - #[serde( - rename = "Duration", - serialize_with = "serializers::serialize_duration", - deserialize_with = "serializers::parse_duration" - )] + #[serde(rename = "Duration", with = "serializers::timeduration::string")] pub duration: Duration, /// Send monitor @@ -104,83 +97,43 @@ pub struct Monitor { pub start: Time, /// Duration of this monitor - #[serde( - rename = "Duration", - serialize_with = "serializers::serialize_duration", - deserialize_with = "serializers::parse_duration" - )] + #[serde(rename = "Duration", with = "serializers::timeduration::string")] pub duration: Duration, /// Idle duration for this monitor - #[serde( - rename = "Idle", - serialize_with = "serializers::serialize_duration", - deserialize_with = "serializers::parse_duration" - )] + #[serde(rename = "Idle", with = "serializers::timeduration::string")] pub idle: Duration, /// Bytes - #[serde( - rename = "Bytes", - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(rename = "Bytes", with = "serializers::primitives::string")] bytes: u64, /// Samples - #[serde( - rename = "Samples", - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(rename = "Samples", with = "serializers::primitives::string")] samples: u64, /// Instant rate - #[serde( - rename = "InstRate", - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(rename = "InstRate", with = "serializers::primitives::string")] inst_rate: u64, /// Current rate - #[serde( - rename = "CurRate", - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(rename = "CurRate", with = "serializers::primitives::string")] cur_rate: u64, /// Average rate - #[serde( - rename = "AvgRate", - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(rename = "AvgRate", with = "serializers::primitives::string")] avg_rate: u64, /// Peak rate - #[serde( - rename = "PeakRate", - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(rename = "PeakRate", with = "serializers::primitives::string")] peak_rate: u64, /// Bytes remaining - #[serde( - rename = "BytesRem", - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(rename = "BytesRem", with = "serializers::primitives::string")] bytes_rem: u64, /// Time remaining - #[serde( - rename = "TimeRem", - serialize_with = "serializers::serialize_u64", - deserialize_with = "serializers::parse_u64" - )] + #[serde(rename = "TimeRem", with = "serializers::primitives::string")] time_rem: u64, /// Progress diff --git a/tendermint/src/serializers.rs b/tendermint/src/serializers.rs index af2a8be48..e589b3639 100644 --- a/tendermint/src/serializers.rs +++ b/tendermint/src/serializers.rs @@ -1,221 +1,61 @@ //! Serde serializers - -use crate::account::{Id, LENGTH}; -use crate::{block, Hash, Signature}; -use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; -use std::str::FromStr; -use std::time::Duration; -use subtle_encoding::{base64, hex}; - -/// Parse `i64` from a JSON string -pub(crate) fn parse_i64<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - String::deserialize(deserializer)? - .parse::() - .map_err(|e| D::Error::custom(format!("{}", e))) -} - -/// Serialize `i64` as a JSON string -#[allow(clippy::trivially_copy_pass_by_ref)] -pub(crate) fn serialize_i64(value: &i64, serializer: S) -> Result -where - S: Serializer, -{ - format!("{}", value).serialize(serializer) -} - -/// Parse `u64` from a JSON string -pub(crate) fn parse_u64<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - String::deserialize(deserializer)? - .parse::() - .map_err(|e| D::Error::custom(format!("{}", e))) -} - -/// Serialize `u64` as a JSON string -#[allow(clippy::trivially_copy_pass_by_ref)] -pub(crate) fn serialize_u64(value: &u64, serializer: S) -> Result -where - S: Serializer, -{ - format!("{}", value).serialize(serializer) -} - -/// Parse `Duration` from a JSON string containing a nanosecond count -pub(crate) fn parse_duration<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - // TODO(tarcieri): handle 64-bit overflow? - let nanos = String::deserialize(deserializer)? - .parse::() - .map_err(|e| D::Error::custom(format!("{}", e)))?; - - Ok(Duration::from_nanos(nanos)) -} - -/// Serialize `Duration` as a JSON string containing a nanosecond count -pub(crate) fn serialize_duration(duration: &Duration, serializer: S) -> Result -where - S: Serializer, -{ - format!("{}", duration.as_nanos()).serialize(serializer) -} - -pub(crate) fn parse_non_empty_hash<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let o: Option = Option::deserialize(deserializer)?; - match o.filter(|s| !s.is_empty()) { - None => Ok(None), - Some(s) => Ok(Some( - Hash::from_str(&s).map_err(|err| D::Error::custom(format!("{}", err)))?, - )), - } -} - -pub(crate) fn serialize_hex(bytes: T, serializer: S) -> Result -where - S: Serializer, - T: AsRef<[u8]>, -{ - use serde::ser::Error; - let hex_bytes = hex::encode(bytes.as_ref()); - let hex_string = String::from_utf8(hex_bytes).map_err(Error::custom)?; - serializer.serialize_str(&hex_string) -} - -// decode upper or lowercase hex -pub(crate) fn parse_hex<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - use serde::de::Error; - let string = String::deserialize(deserializer)?; - hex::decode_upper(&string) - .or_else(|_| hex::decode(&string)) - .map_err(Error::custom) -} - -pub(crate) fn serialize_base64(bytes: T, serializer: S) -> Result -where - S: Serializer, - T: AsRef<[u8]>, -{ - use serde::ser::Error; - let base64_bytes = base64::encode(bytes.as_ref()); - let base64_string = String::from_utf8(base64_bytes).map_err(Error::custom)?; - serializer.serialize_str(&base64_string) -} - -pub(crate) fn parse_base64<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - use serde::de::Error; - let string = String::deserialize(deserializer)?; - base64::decode(&string).map_err(Error::custom) -} - -#[allow(dead_code)] -pub(crate) fn serialize_option_base64( - maybe_bytes: &Option>, - serializer: S, -) -> Result -where - S: Serializer, -{ - #[derive(Serialize)] - struct Wrapper<'a>(#[serde(serialize_with = "serialize_base64")] &'a Vec); - - match maybe_bytes { - Some(bytes) => Wrapper(bytes).serialize(serializer), - None => maybe_bytes.serialize(serializer), - } -} - -#[allow(dead_code)] -pub(crate) fn parse_option_base64<'de, D>(deserializer: D) -> Result>, D::Error> -where - D: Deserializer<'de>, -{ - #[derive(Deserialize)] - struct Wrapper(#[serde(deserialize_with = "parse_base64")] Vec); - - let v = Option::deserialize(deserializer)?; - Ok(v.map(|Wrapper(a)| a)) -} - -/// Parse empty block id as None. -pub(crate) fn parse_non_empty_block_id<'de, D>( - deserializer: D, -) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - #[derive(Deserialize)] - struct Parts { - #[serde(deserialize_with = "parse_u64")] - total: u64, - hash: String, - } - #[derive(Deserialize)] - struct BlockId { - hash: String, - parts: Parts, - } - let tmp_id = BlockId::deserialize(deserializer)?; - if tmp_id.hash.is_empty() { - Ok(None) - } else { - Ok(Some(block::Id { - hash: Hash::from_str(&tmp_id.hash) - .map_err(|err| D::Error::custom(format!("{}", err)))?, - parts: if tmp_id.parts.hash.is_empty() { - None - } else { - Some(block::parts::Header { - total: tmp_id.parts.total, - hash: Hash::from_str(&tmp_id.parts.hash) - .map_err(|err| D::Error::custom(format!("{}", err)))?, - }) - }, - })) - } -} - -pub(crate) fn parse_non_empty_id<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let s = String::deserialize(deserializer)?; - if s.is_empty() { - Ok(None) - } else { - // TODO: how can we avoid rewriting code here? - match Id::from_str(&s).map_err(|_| { - D::Error::custom(format!( - "expected {}-character hex string, got {:?}", - LENGTH * 2, - s - )) - }) { - Ok(id) => Ok(Option::from(id)), - Err(_) => Ok(None), - } - } -} - -pub(crate) fn parse_non_empty_signature<'de, D>( - deserializer: D, -) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - Deserialize::deserialize(deserializer).map(|x: Option<_>| x.unwrap_or(None)) -} +//! +//! Serializers and deserializers for a transparent developer experience. +//! +//! Serializers for the 'with' annotation are presented in a +//! serializers:::: format. +//! +//! Serializers for the 'try_from' annotation are presented in a +//! serializers::Raw format. +//! +//! This example shows how to serialize Vec into different types of strings: +//! ``` +//! use serde::{Serialize, Deserialize}; +//! use tendermint::serializers; +//! +//! #[derive(Serialize, Deserialize)] +//! struct ByteTypes { +//! +//! #[serde(with="serializers::bytes::hexstring")] +//! hexbytes: Vec, +//! +//! #[serde(with="serializers::bytes::base64string")] +//! base64bytes: Vec, +//! +//! #[serde(with="serializers::bytes::string")] +//! bytes: Vec, +//! +//! } +//! ``` +//! +//! Available serializer modules for the 'with' annotation: +//! i64 <-> string #[serde(with="serializers::primitives::string")] +//! u64 <-> string #[serde(with="serializers::primitives::string")] +//! std::time::Duration <-> nanoseconds as string #[serde(with="serializers::timeduration::string")] +//! Vec <-> HexString #[serde(with="serializers::bytes::hexstring")] +//! Vec <-> Base64String #[serde(with="serializers::bytes::base64string")] +//! Vec <-> string #[serde(with="serializers::bytes::string")] +//! +//! Notes: +//! * Any type that has the "FromStr" trait can be serialized into a string with serializers::primitives::string. +//! * serializers::bytes::* deserializes a null value into an empty vec![]. +//! +//! Available serializer types for the 'try_from' annotation: +//! block::CommitSig <-> RawCommitSig #[serde(try_from="serializers::RawCommitSig")] +//! +//! Notes: +//! * The Raw... types are implemented based on the Tendermint specifications +//! + +pub mod bytes; +pub mod primitives; +pub mod timeduration; + +mod raw_commit_sig; +pub use raw_commit_sig::BlockIDFlag; +pub use raw_commit_sig::RawCommitSig; + +/// Todo: refactor and remove +mod other; +pub use other::parse_non_empty_block_id; +pub use other::parse_non_empty_hash; diff --git a/tendermint/src/serializers/bytes.rs b/tendermint/src/serializers/bytes.rs new file mode 100644 index 000000000..ede2b9e10 --- /dev/null +++ b/tendermint/src/serializers/bytes.rs @@ -0,0 +1,140 @@ +//! Serialize/deserialize bytes (Vec) type +use serde::{ + de::{Error, Visitor}, + Deserialize, Deserializer, +}; +use std::fmt; +use subtle_encoding::{base64, hex}; + +/// ByteStringType defines the options what an incoming string can represent. +enum ByteStringType { + Hex, + Base64, + Regular, +} + +/// The Visitor struct to decode the incoming string. +struct BytesVisitor { + string_type: ByteStringType, +} + +/// The Visitor implementation +impl<'de> Visitor<'de> for BytesVisitor { + type Value = Vec; + + /// Description of expected input + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + match self.string_type { + ByteStringType::Hex => { + formatter.write_str("Hex-encoded byte-array in a String or null") + } + ByteStringType::Base64 => { + formatter.write_str("Base64-encoded byte-array in a String or null") + } + ByteStringType::Regular => formatter.write_str("Byte-array in a String or null"), + } + } + + /// If incoming is 'null', return an empty array. + fn visit_none(self) -> Result + where + E: Error, + { + Ok(vec![]) + } + + /// Decode the incoming string based on what string type it is. + fn visit_some(self, deserializer: D) -> Result>::Error> + where + D: Deserializer<'de>, + { + let string = String::deserialize(deserializer)?; + + match self.string_type { + ByteStringType::Hex => hex::decode_upper(&string) + .or_else(|_| hex::decode(&string)) + .map_err(Error::custom), + ByteStringType::Base64 => base64::decode(&string).map_err(Error::custom), + ByteStringType::Regular => Ok(string.as_bytes().to_vec()), + } + } +} + +/// Serialize into hexstring, deserialize from hexstring +pub mod hexstring { + use serde::{ser::Error, Deserializer, Serializer}; + use subtle_encoding::hex; + + /// Deserialize hexstring into Vec + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + deserializer.deserialize_option(super::BytesVisitor { + string_type: super::ByteStringType::Hex, + }) + } + + /// Serialize from T into hexstring + pub fn serialize(value: &T, serializer: S) -> Result + where + S: Serializer, + T: AsRef<[u8]>, + { + let hex_bytes = hex::encode(value.as_ref()); + let hex_string = String::from_utf8(hex_bytes).map_err(Error::custom)?; + serializer.serialize_str(&hex_string) + } +} + +/// Serialize into base64string, deserialize from base64string +pub mod base64string { + use serde::{ser::Error, Deserializer, Serializer}; + use subtle_encoding::base64; + + /// Deserialize base64string into Vec + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + deserializer.deserialize_option(super::BytesVisitor { + string_type: super::ByteStringType::Base64, + }) + } + + /// Serialize from T into base64string + pub fn serialize(value: &T, serializer: S) -> Result + where + S: Serializer, + T: AsRef<[u8]>, + { + let base64_bytes = base64::encode(value.as_ref()); + let base64_string = String::from_utf8(base64_bytes).map_err(Error::custom)?; + serializer.serialize_str(&base64_string) + } +} + +/// Serialize into string, deserialize from string +pub mod string { + use serde::{ser::Error, Deserializer, Serializer}; + + /// Deserialize string into Vec + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + deserializer.deserialize_option(super::BytesVisitor { + string_type: super::ByteStringType::Regular, + }) + } + + /// Serialize from T into string + pub fn serialize(value: &T, serializer: S) -> Result + where + S: Serializer, + T: AsRef<[u8]>, + { + let string = String::from_utf8(value.as_ref().to_vec()).map_err(Error::custom)?; + serializer.serialize_str(&string) + } +} diff --git a/tendermint/src/serializers/other.rs b/tendermint/src/serializers/other.rs new file mode 100644 index 000000000..3cd5fb315 --- /dev/null +++ b/tendermint/src/serializers/other.rs @@ -0,0 +1,56 @@ +use crate::{block, Hash}; +use serde::{de::Error as _, Deserialize, Deserializer}; +use std::str::FromStr; + +// Todo: Continue refactoring the "Option"-based serializers below. +// Most of them are not needed if the structs are defined well. + +/// Deserialize Option +pub fn parse_non_empty_hash<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let o: Option = Option::deserialize(deserializer)?; + match o.filter(|s| !s.is_empty()) { + None => Ok(None), + Some(s) => Ok(Some( + Hash::from_str(&s).map_err(|err| D::Error::custom(format!("{}", err)))?, + )), + } +} + +/// Deserialize Option +pub fn parse_non_empty_block_id<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + struct Parts { + #[serde(with = "super::primitives::string")] + total: u64, + hash: String, + } + #[derive(Deserialize)] + struct BlockId { + hash: String, + parts: Parts, + } + let tmp_id = BlockId::deserialize(deserializer)?; + if tmp_id.hash.is_empty() { + Ok(None) + } else { + Ok(Some(block::Id { + hash: Hash::from_str(&tmp_id.hash) + .map_err(|err| D::Error::custom(format!("{}", err)))?, + parts: if tmp_id.parts.hash.is_empty() { + None + } else { + Some(block::parts::Header { + total: tmp_id.parts.total, + hash: Hash::from_str(&tmp_id.parts.hash) + .map_err(|err| D::Error::custom(format!("{}", err)))?, + }) + }, + })) + } +} diff --git a/tendermint/src/serializers/primitives.rs b/tendermint/src/serializers/primitives.rs new file mode 100644 index 000000000..b652a62d7 --- /dev/null +++ b/tendermint/src/serializers/primitives.rs @@ -0,0 +1,27 @@ +//! Serialize/deserialize primitive types (i64, u64, etc) + +/// Serialize into string, deserialize from string +pub mod string { + use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; + + /// Deserialize string into T + pub fn deserialize<'de, D, T>(deserializer: D) -> Result + where + D: Deserializer<'de>, + T: std::str::FromStr, + ::Err: std::fmt::Display, + { + String::deserialize(deserializer)? + .parse::() + .map_err(|e| D::Error::custom(format!("{}", e))) + } + + /// Serialize from T into string + pub fn serialize(value: &T, serializer: S) -> Result + where + S: Serializer, + T: std::fmt::Display, + { + format!("{}", value).serialize(serializer) + } +} diff --git a/tendermint/src/serializers/raw_commit_sig.rs b/tendermint/src/serializers/raw_commit_sig.rs new file mode 100644 index 000000000..c04548d64 --- /dev/null +++ b/tendermint/src/serializers/raw_commit_sig.rs @@ -0,0 +1,40 @@ +//! RawCommitSig type for deserialization +use crate::{account, Signature, Time}; +use serde::{Deserialize, Deserializer}; +use serde_repr::{Deserialize_repr, Serialize_repr}; + +// Implements decision: https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-025-commit.md#decision + +/// indicate which BlockID the signature is for +#[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug)] +#[repr(u8)] +pub enum BlockIDFlag { + /// vote is not included in the Commit.Precommits + BlockIDFlagAbsent = 1, + /// voted for the Commit.BlockID + BlockIDFlagCommit = 2, + /// voted for nil + BlockIDFlagNil = 3, +} + +/// RawCommitSig struct for interim deserialization of JSON object +#[derive(Deserialize)] +pub struct RawCommitSig { + /// indicate which BlockID the signature is for + pub block_id_flag: BlockIDFlag, + /// Validator Address + pub validator_address: account::Id, + /// Timestamp + #[serde(default)] + pub timestamp: Option