Skip to content

Commit

Permalink
Represent optional hash and block id as Option
Browse files Browse the repository at this point in the history
and correctly compute header hash when block id is empty.

Represent empty Hash with Option
  • Loading branch information
yihuang committed Nov 25, 2019
1 parent ef7410c commit 8b1d3dc
Show file tree
Hide file tree
Showing 10 changed files with 110 additions and 64 deletions.
12 changes: 6 additions & 6 deletions tendermint/src/amino_types/block_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
)
}
}

Expand Down Expand Up @@ -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())
}
}

Expand Down
12 changes: 3 additions & 9 deletions tendermint/src/amino_types/vote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
block::{self, ParseId},
chain, consensus,
error::Error,
vote, Hash,
vote,
};
use bytes::BufMut;
use prost::{error::EncodeError, Message};
Expand Down Expand Up @@ -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(),
Expand Down
96 changes: 69 additions & 27 deletions tendermint/src/block/header.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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<block::Id>,

/// 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<Hash>,

/// Merkle root of transaction hashes
pub data_hash: Hash,
#[serde(deserialize_with = "serializers::parse_non_empty_hash")]
pub data_hash: Option<Hash>,

/// Validators for the current block
pub validators_hash: Hash,
Expand All @@ -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<Hash>,

/// 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>,

/// Hash of evidence included in the block
pub evidence_hash: Hash,
#[serde(deserialize_with = "serializers::parse_non_empty_hash")]
pub evidence_hash: Option<Hash>,

/// Original proposer of the block
pub proposer_address: account::Id,
Expand Down Expand Up @@ -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<Option<block::Id>, 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.
///
Expand Down Expand Up @@ -146,12 +192,8 @@ fn bytes_enc(bytes: &[u8]) -> Vec<u8> {
chain_id_enc
}

fn encode_hash(hash: Hash) -> Vec<u8> {
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<u8> {
bytes_enc(hash.as_bytes())
}

fn encode_varint(val: u64) -> Vec<u8> {
Expand Down
2 changes: 1 addition & 1 deletion tendermint/src/block/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
5 changes: 3 additions & 2 deletions tendermint/src/genesis.rs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -16,7 +16,8 @@ pub struct Genesis<AppState = serde_json::Value> {
pub consensus_params: consensus::Params,

/// App hash
pub app_hash: Hash,
#[serde(deserialize_with = "serializers::parse_non_empty_hash")]
pub app_hash: Option<Hash>,

/// App state
pub app_state: AppState,
Expand Down
17 changes: 5 additions & 12 deletions tendermint/src/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -57,18 +54,16 @@ impl Hash {
}

/// Return the digest algorithm used to produce this hash
pub fn algorithm(self) -> Option<Algorithm> {
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(),
}
}
}
Expand All @@ -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"),
}
}
}
Expand All @@ -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)
Expand All @@ -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)))?)
}
Expand Down
5 changes: 2 additions & 3 deletions tendermint/src/rpc/endpoint/abci_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ pub(crate) fn serialize_app_hash<S>(hash: &Hash, serializer: S) -> Result<S::Ok,
where
S: Serializer,
{
hash.as_bytes()
.map(|bytes| String::from_utf8(base64::encode(bytes)).unwrap())
.unwrap_or_default()
String::from_utf8(base64::encode(hash.as_bytes()))
.unwrap()
.serialize(serializer)
}
8 changes: 5 additions & 3 deletions tendermint/src/rpc/endpoint/status.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! `/status` endpoint JSONRPC wrapper

use crate::{block, node, rpc, validator, Hash, Time};
use crate::{block, node, rpc, serializers, validator, Hash, Time};
use serde::{Deserialize, Serialize};

/// Node status request
Expand Down Expand Up @@ -34,10 +34,12 @@ impl rpc::Response for Response {}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct SyncInfo {
/// Latest block hash
pub latest_block_hash: Hash,
#[serde(deserialize_with = "serializers::parse_non_empty_hash")]
pub latest_block_hash: Option<Hash>,

/// Latest app hash
pub latest_app_hash: Hash,
#[serde(deserialize_with = "serializers::parse_non_empty_hash")]
pub latest_app_hash: Option<Hash>,

/// Latest block height
pub latest_block_height: block::Height,
Expand Down
15 changes: 15 additions & 0 deletions tendermint/src/serializers.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -61,3 +63,16 @@ where
{
format!("{}", duration.as_nanos()).serialize(serializer)
}

pub(crate) fn parse_non_empty_hash<'de, D>(deserializer: D) -> Result<Option<Hash>, D::Error>
where
D: Deserializer<'de>,
{
let o: Option<String> = 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)))?,
)),
}
}
2 changes: 1 addition & 1 deletion tendermint/src/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 8b1d3dc

Please sign in to comment.