use crate::merkle::simple_hash_from_byte_vectors;
use crate::{account, block, chain, AppHash, Error, Hash, Time};
use serde::{Deserialize, Serialize};
use std::convert::{TryFrom, TryInto};
use tendermint_proto::types::Header as RawHeader;
use tendermint_proto::version::Consensus as RawConsensusVersion;
use tendermint_proto::Protobuf;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(try_from = "RawHeader", into = "RawHeader")]
pub struct Header {
pub version: Version,
pub chain_id: chain::Id,
pub height: block::Height,
pub time: Time,
pub last_block_id: Option<block::Id>,
pub last_commit_hash: Option<Hash>,
pub data_hash: Option<Hash>,
pub validators_hash: Hash,
pub next_validators_hash: Hash,
pub consensus_hash: Hash,
pub app_hash: AppHash,
pub last_results_hash: Option<Hash>,
pub evidence_hash: Option<Hash>,
pub proposer_address: account::Id,
}
impl Protobuf<RawHeader> for Header {}
impl TryFrom<RawHeader> for Header {
type Error = Error;
fn try_from(value: RawHeader) -> Result<Self, Self::Error> {
let last_block_id = value
.last_block_id
.map(TryInto::try_into)
.transpose()?
.filter(|l| l != &block::Id::default());
let last_commit_hash = if value.last_commit_hash.is_empty() {
None
} else {
Some(value.last_commit_hash.try_into()?)
};
let last_results_hash = if value.last_results_hash.is_empty() {
None
} else {
Some(value.last_results_hash.try_into()?)
};
let height: block::Height = value.height.try_into()?;
if last_block_id.is_some() && height.value() == 1 {
return Err(Error::invalid_first_header());
}
Ok(Header {
version: value.version.ok_or_else(Error::missing_version)?.into(),
chain_id: value.chain_id.try_into()?,
height,
time: value
.time
.ok_or_else(Error::missing_timestamp)?
.try_into()?,
last_block_id,
last_commit_hash,
data_hash: if value.data_hash.is_empty() {
None
} else {
Some(value.data_hash.try_into()?)
},
validators_hash: value.validators_hash.try_into()?,
next_validators_hash: value.next_validators_hash.try_into()?,
consensus_hash: value.consensus_hash.try_into()?,
app_hash: value.app_hash.try_into()?,
last_results_hash,
evidence_hash: if value.evidence_hash.is_empty() {
None
} else {
Some(value.evidence_hash.try_into()?)
}, proposer_address: value.proposer_address.try_into()?,
})
}
}
impl From<Header> for RawHeader {
fn from(value: Header) -> Self {
RawHeader {
version: Some(value.version.into()),
chain_id: value.chain_id.into(),
height: value.height.into(),
time: Some(value.time.into()),
last_block_id: value.last_block_id.map(Into::into),
last_commit_hash: value.last_commit_hash.unwrap_or_default().into(),
data_hash: value.data_hash.unwrap_or_default().into(),
validators_hash: value.validators_hash.into(),
next_validators_hash: value.next_validators_hash.into(),
consensus_hash: value.consensus_hash.into(),
app_hash: value.app_hash.into(),
last_results_hash: value.last_results_hash.unwrap_or_default().into(),
evidence_hash: value.evidence_hash.unwrap_or_default().into(),
proposer_address: value.proposer_address.into(),
}
}
}
impl Header {
pub fn hash(&self) -> Hash {
let fields_bytes = vec![
self.version.encode_vec().unwrap(),
self.chain_id.encode_vec().unwrap(),
self.height.encode_vec().unwrap(),
self.time.encode_vec().unwrap(),
self.last_block_id.unwrap_or_default().encode_vec().unwrap(),
self.last_commit_hash
.unwrap_or_default()
.encode_vec()
.unwrap(),
self.data_hash.unwrap_or_default().encode_vec().unwrap(),
self.validators_hash.encode_vec().unwrap(),
self.next_validators_hash.encode_vec().unwrap(),
self.consensus_hash.encode_vec().unwrap(),
self.app_hash.encode_vec().unwrap(),
self.last_results_hash
.unwrap_or_default()
.encode_vec()
.unwrap(),
self.evidence_hash.unwrap_or_default().encode_vec().unwrap(),
self.proposer_address.encode_vec().unwrap(),
];
Hash::Sha256(simple_hash_from_byte_vectors(fields_bytes))
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Version {
pub block: u64,
pub app: u64,
}
impl Protobuf<RawConsensusVersion> for Version {}
impl From<RawConsensusVersion> for Version {
fn from(value: RawConsensusVersion) -> Self {
Version {
block: value.block,
app: value.app,
}
}
}
impl From<Version> for RawConsensusVersion {
fn from(value: Version) -> Self {
RawConsensusVersion {
block: value.block,
app: value.app,
}
}
}
#[cfg(test)]
mod tests {
use super::Header;
use crate::hash::Algorithm;
use crate::test::test_serialization_roundtrip;
use crate::Hash;
#[test]
fn serialization_roundtrip() {
let json_data = include_str!("../../tests/support/serialization/block/header.json");
test_serialization_roundtrip::<Header>(json_data);
}
#[test]
fn header_hashing() {
let expected_hash = Hash::from_hex_upper(
Algorithm::Sha256,
"F30A71F2409FB15AACAEDB6CC122DFA2525BEE9CAE521721B06BFDCA291B8D56",
)
.unwrap();
let header: Header = serde_json::from_str(include_str!(
"../../tests/support/serialization/block/header_with_known_hash.json"
))
.unwrap();
assert_eq!(expected_hash, header.hash());
}
}