From 8b1d3dcec57272e8e4f8a7a4122cce3e732a5f46 Mon Sep 17 00:00:00 2001 From: yihuang Date: Wed, 13 Nov 2019 13:59:26 +0800 Subject: [PATCH] Represent optional hash and block id as Option and correctly compute header hash when block id is empty. Represent empty Hash with Option --- tendermint/src/amino_types/block_id.rs | 12 +-- tendermint/src/amino_types/vote.rs | 12 +-- tendermint/src/block/header.rs | 96 +++++++++++++++++------- tendermint/src/block/id.rs | 2 +- tendermint/src/genesis.rs | 5 +- tendermint/src/hash.rs | 17 ++--- tendermint/src/rpc/endpoint/abci_info.rs | 5 +- tendermint/src/rpc/endpoint/status.rs | 8 +- tendermint/src/serializers.rs | 15 ++++ tendermint/src/validator.rs | 2 +- 10 files changed, 110 insertions(+), 64 deletions(-) diff --git a/tendermint/src/amino_types/block_id.rs b/tendermint/src/amino_types/block_id.rs index 88eebbf3c..f3701c0bc 100644 --- a/tendermint/src/amino_types/block_id.rs +++ b/tendermint/src/amino_types/block_id.rs @@ -34,11 +34,11 @@ impl block::ParseId for BlockId { impl From<&block::Id> for BlockId { fn from(bid: &block::Id) -> Self { - let bid_hash = bid.hash.as_bytes().unwrap().to_vec(); - match &bid.parts { - Some(parts) => BlockId::new(bid_hash, Some(PartsSetHeader::from(parts))), - None => BlockId::new(bid_hash, None), - } + let bid_hash = bid.hash.as_bytes(); + BlockId::new( + bid_hash.to_vec(), + bid.parts.as_ref().map(PartsSetHeader::from), + ) } } @@ -89,7 +89,7 @@ impl PartsSetHeader { impl From<&parts::Header> for PartsSetHeader { fn from(parts: &parts::Header) -> Self { - PartsSetHeader::new(parts.total as i64, parts.hash.as_bytes().unwrap().to_vec()) + PartsSetHeader::new(parts.total as i64, parts.hash.as_bytes().to_vec()) } } diff --git a/tendermint/src/amino_types/vote.rs b/tendermint/src/amino_types/vote.rs index e3d766f14..398da516c 100644 --- a/tendermint/src/amino_types/vote.rs +++ b/tendermint/src/amino_types/vote.rs @@ -11,7 +11,7 @@ use crate::{ block::{self, ParseId}, chain, consensus, error::Error, - vote, Hash, + vote, }; use bytes::BufMut; use prost::{error::EncodeError, Message}; @@ -58,14 +58,8 @@ impl From<&vote::Vote> for Vote { height: vote.height.value() as i64, // TODO potential overflow :-/ round: vote.round as i64, block_id: Some(BlockId { - hash: match vote.block_id.hash { - Hash::Sha256(h) => h.to_vec(), - _ => vec![], - }, - parts_header: match &vote.block_id.parts { - Some(parts) => Some(PartsSetHeader::from(parts)), - None => None, - }, + hash: vote.block_id.hash.as_bytes().to_vec(), + parts_header: vote.block_id.parts.as_ref().map(PartsSetHeader::from), }), timestamp: Some(TimeMsg::from(vote.timestamp)), validator_address: vote.validator_address.as_bytes().to_vec(), diff --git a/tendermint/src/block/header.rs b/tendermint/src/block/header.rs index 9907b7735..76e92d933 100644 --- a/tendermint/src/block/header.rs +++ b/tendermint/src/block/header.rs @@ -1,11 +1,10 @@ //! Block headers -use crate::merkle::simple_hash_from_byte_slices; -use crate::{account, amino_types, block, chain, lite, Hash, Time}; use amino_types::{message::AminoMessage, BlockId, ConsensusVersion, TimeMsg}; -use { - crate::serializers, - serde::{Deserialize, Serialize}, -}; +use serde::{de::Error as _, Deserialize, Deserializer, Serialize}; +use std::str::FromStr; + +use crate::merkle::simple_hash_from_byte_slices; +use crate::{account, amino_types, block, chain, lite, serializers, Hash, Time}; /// Block `Header` values contain metadata about the block and about the /// consensus, as well as commitments to the data in the current block, the @@ -41,13 +40,16 @@ pub struct Header { pub total_txs: u64, /// Previous block info - pub last_block_id: block::Id, + #[serde(deserialize_with = "parse_non_empty_block_id")] + pub last_block_id: Option, /// Commit from validators from the last block - pub last_commit_hash: Hash, + #[serde(deserialize_with = "serializers::parse_non_empty_hash")] + pub last_commit_hash: Option, /// Merkle root of transaction hashes - pub data_hash: Hash, + #[serde(deserialize_with = "serializers::parse_non_empty_hash")] + pub data_hash: Option, /// Validators for the current block pub validators_hash: Hash, @@ -59,13 +61,16 @@ pub struct Header { pub consensus_hash: Hash, /// State after txs from the previous block - pub app_hash: Hash, + #[serde(deserialize_with = "serializers::parse_non_empty_hash")] + pub app_hash: Option, /// Root hash of all results from the txs from the previous block - pub last_results_hash: Hash, + #[serde(deserialize_with = "serializers::parse_non_empty_hash")] + pub last_results_hash: Option, /// Hash of evidence included in the block - pub evidence_hash: Hash, + #[serde(deserialize_with = "serializers::parse_non_empty_hash")] + pub evidence_hash: Option, /// Original proposer of the block pub proposer_address: account::Id, @@ -103,21 +108,62 @@ impl lite::Header for Header { byteslices.push(AminoMessage::bytes_vec(&TimeMsg::from(self.time))); byteslices.push(encode_varint(self.num_txs)); byteslices.push(encode_varint(self.total_txs)); - byteslices.push(AminoMessage::bytes_vec(&BlockId::from(&self.last_block_id))); - byteslices.push(encode_hash(self.last_commit_hash)); - byteslices.push(encode_hash(self.data_hash)); - byteslices.push(encode_hash(self.validators_hash)); - byteslices.push(encode_hash(self.next_validators_hash)); - byteslices.push(encode_hash(self.consensus_hash)); - byteslices.push(encode_hash(self.app_hash)); - byteslices.push(encode_hash(self.last_results_hash)); - byteslices.push(encode_hash(self.evidence_hash)); + byteslices.push( + self.last_block_id + .as_ref() + .map_or(vec![], |id| AminoMessage::bytes_vec(&BlockId::from(id))), + ); + byteslices.push(self.last_commit_hash.as_ref().map_or(vec![], encode_hash)); + byteslices.push(self.data_hash.as_ref().map_or(vec![], encode_hash)); + byteslices.push(encode_hash(&self.validators_hash)); + byteslices.push(encode_hash(&self.next_validators_hash)); + byteslices.push(encode_hash(&self.consensus_hash)); + byteslices.push(self.app_hash.as_ref().map_or(vec![], encode_hash)); + byteslices.push(self.last_results_hash.as_ref().map_or(vec![], encode_hash)); + byteslices.push(self.evidence_hash.as_ref().map_or(vec![], encode_hash)); byteslices.push(bytes_enc(self.proposer_address.as_bytes())); Hash::Sha256(simple_hash_from_byte_slices(byteslices)) } } +pub(crate) fn parse_non_empty_block_id<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Serialize, Deserialize)] + struct Parts { + #[serde(deserialize_with = "serializers::parse_u64")] + total: u64, + hash: String, + } + #[derive(Serialize, 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)))?, + }) + }, + })) + } +} + /// `Version` contains the protocol version for the blockchain and the /// application. /// @@ -146,12 +192,8 @@ fn bytes_enc(bytes: &[u8]) -> Vec { chain_id_enc } -fn encode_hash(hash: Hash) -> Vec { - let mut hash_enc = vec![]; - if let Some(last_commit_hash_bytes) = hash.as_bytes() { - hash_enc = bytes_enc(last_commit_hash_bytes); - } - hash_enc +fn encode_hash(hash: &Hash) -> Vec { + bytes_enc(hash.as_bytes()) } fn encode_varint(val: u64) -> Vec { diff --git a/tendermint/src/block/id.rs b/tendermint/src/block/id.rs index 7bc37c74d..37ceff027 100644 --- a/tendermint/src/block/id.rs +++ b/tendermint/src/block/id.rs @@ -84,7 +84,7 @@ mod tests { fn parses_hex_strings() { let id = Id::from_str(EXAMPLE_SHA256_ID).unwrap(); assert_eq!( - id.hash.as_bytes().unwrap(), + id.hash.as_bytes(), b"\x26\xC0\xA4\x1F\x32\x43\xC6\xBC\xD7\xAD\x2D\xFF\x8A\x8D\x83\xA7\ \x1D\x29\xD3\x07\xB5\x32\x6C\x22\x7F\x73\x4A\x1A\x51\x2F\xE4\x7D" .as_ref() diff --git a/tendermint/src/genesis.rs b/tendermint/src/genesis.rs index 30d6f353d..14f4fc4b6 100644 --- a/tendermint/src/genesis.rs +++ b/tendermint/src/genesis.rs @@ -1,6 +1,6 @@ //! Genesis data -use crate::{chain, consensus, Hash, Time}; +use crate::{chain, consensus, serializers, Hash, Time}; use serde::{Deserialize, Serialize}; /// Genesis data @@ -16,7 +16,8 @@ pub struct Genesis { pub consensus_params: consensus::Params, /// App hash - pub app_hash: Hash, + #[serde(deserialize_with = "serializers::parse_non_empty_hash")] + pub app_hash: Option, /// App state pub app_state: AppState, diff --git a/tendermint/src/hash.rs b/tendermint/src/hash.rs index 349fb9f95..236ee4114 100644 --- a/tendermint/src/hash.rs +++ b/tendermint/src/hash.rs @@ -23,9 +23,6 @@ pub enum Algorithm { pub enum Hash { /// SHA-256 hashes Sha256([u8; SHA256_HASH_SIZE]), - - /// NULL (i.e. all-zero) hashes - Null, } impl Hash { @@ -57,18 +54,16 @@ impl Hash { } /// Return the digest algorithm used to produce this hash - pub fn algorithm(self) -> Option { + pub fn algorithm(self) -> Algorithm { match self { - Hash::Sha256(_) => Some(Algorithm::Sha256), - Hash::Null => None, + Hash::Sha256(_) => Algorithm::Sha256, } } /// Borrow the `Hash` as a byte slice - pub fn as_bytes(&self) -> Option<&[u8]> { + pub fn as_bytes(&self) -> &[u8] { match self { - Hash::Sha256(ref h) => Some(h.as_ref()), - Hash::Null => None, + Hash::Sha256(ref h) => h.as_ref(), } } } @@ -77,7 +72,6 @@ impl Debug for Hash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Hash::Sha256(_) => write!(f, "Hash::Sha256({})", self), - Hash::Null => write!(f, "Hash::Null"), } } } @@ -86,7 +80,6 @@ impl Display for Hash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let hex = match self { Hash::Sha256(ref h) => Hex::upper_case().encode_to_string(h).unwrap(), - Hash::Null => "".to_owned(), }; write!(f, "{}", hex) @@ -106,7 +99,7 @@ impl<'de> Deserialize<'de> for Hash { let hex = String::deserialize(deserializer)?; if hex.is_empty() { - Ok(Hash::Null) + Err(D::Error::custom("empty hash")) } else { Ok(Self::from_str(&hex).map_err(|e| D::Error::custom(format!("{}", e)))?) } diff --git a/tendermint/src/rpc/endpoint/abci_info.rs b/tendermint/src/rpc/endpoint/abci_info.rs index c3f5b068d..b88be5fb8 100644 --- a/tendermint/src/rpc/endpoint/abci_info.rs +++ b/tendermint/src/rpc/endpoint/abci_info.rs @@ -61,8 +61,7 @@ pub(crate) fn serialize_app_hash(hash: &Hash, serializer: S) -> Result, /// Latest app hash - pub latest_app_hash: Hash, + #[serde(deserialize_with = "serializers::parse_non_empty_hash")] + pub latest_app_hash: Option, /// Latest block height pub latest_block_height: block::Height, diff --git a/tendermint/src/serializers.rs b/tendermint/src/serializers.rs index d5dbd3bff..1bd8af5c2 100644 --- a/tendermint/src/serializers.rs +++ b/tendermint/src/serializers.rs @@ -1,6 +1,8 @@ //! Serde serializers +use crate::Hash; use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; +use std::str::FromStr; use std::time::Duration; /// Parse `i64` from a JSON string @@ -61,3 +63,16 @@ where { 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)))?, + )), + } +} diff --git a/tendermint/src/validator.rs b/tendermint/src/validator.rs index 8fbb79f1f..2a7c8dac9 100644 --- a/tendermint/src/validator.rs +++ b/tendermint/src/validator.rs @@ -247,7 +247,7 @@ mod tests { let val_set = Set::new(vec![v1, v2, v3]); let hash = val_set.hash(); - assert_eq!(hash_expect, &hash.as_bytes().unwrap().to_vec()); + assert_eq!(hash_expect, &hash.as_bytes().to_vec()); let not_in_set = make_validator( "EB6B732C5BD86B5FA3F3BC3DB688DA0ED182A7411F81C2D405506B298FC19E52",