use candid::CandidType;
use ex3_serde::bincode;
use ex3_timestamp::TimeInNs;
use ic_stable_structures::{storable::Bound, Storable};
use serde::{Deserialize, Serialize};
use serde_bytes::ByteBuf;
use std::ops::Range;
use crate::range::CandidRange;
use crate::{BlockHash, BlockHeight, CandidBlockHeight, CandidPackageId, MerkleRoot, PackageId};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct BlockHeader {
pub version: u8,
pub height: BlockHeight,
pub p_id: PackageId,
pub pre_block_hash: BlockHash,
pub tx_root: MerkleRoot,
pub state_changed_root: MerkleRoot,
pub data_integrity_root: MerkleRoot,
pub rejected_tx_root: MerkleRoot,
}
impl Ord for BlockHeader {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.height.cmp(&other.height)
}
}
impl PartialOrd for BlockHeader {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(CandidType, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct CandidBlockHeader {
pub version: u8,
pub height: CandidBlockHeight,
pub p_id: CandidPackageId,
#[serde(with = "serde_bytes")]
pub pre_block_hash: BlockHash,
#[serde(with = "serde_bytes")]
pub tx_root: MerkleRoot,
#[serde(with = "serde_bytes")]
pub state_changed_root: MerkleRoot,
#[serde(with = "serde_bytes")]
pub data_integrity_root: MerkleRoot,
#[serde(with = "serde_bytes")]
pub rejected_tx_root: MerkleRoot,
}
impl Storable for BlockHeader {
fn to_bytes(&self) -> std::borrow::Cow<[u8]> {
bincode::serialize(self).unwrap().into()
}
fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self {
bincode::deserialize(bytes.as_ref()).unwrap()
}
const BOUND: Bound = Bound::Bounded {
max_size: 225,
is_fixed_size: false,
};
}
impl From<CandidBlockHeader> for BlockHeader {
fn from(candid_block_header: CandidBlockHeader) -> Self {
BlockHeader {
version: candid_block_header.version,
height: candid_block_header.height.into(),
p_id: candid_block_header.p_id.into(),
pre_block_hash: candid_block_header.pre_block_hash,
tx_root: candid_block_header.tx_root,
state_changed_root: candid_block_header.state_changed_root,
data_integrity_root: candid_block_header.data_integrity_root,
rejected_tx_root: candid_block_header.rejected_tx_root,
}
}
}
impl From<BlockHeader> for CandidBlockHeader {
fn from(block_header: BlockHeader) -> Self {
CandidBlockHeader {
version: block_header.version,
height: block_header.height.into(),
p_id: block_header.p_id.into(),
pre_block_hash: block_header.pre_block_hash,
tx_root: block_header.tx_root,
state_changed_root: block_header.state_changed_root,
data_integrity_root: block_header.data_integrity_root,
rejected_tx_root: block_header.rejected_tx_root,
}
}
}
impl BlockHeader {
pub fn hash(&self) -> BlockHash {
let block_header_bytes = bincode::serialize(&self).unwrap();
ex3_crypto::sha256(block_header_bytes.as_slice())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EncodedBlockHeader(pub ByteBuf);
impl From<BlockHeader> for EncodedBlockHeader {
fn from(head: BlockHeader) -> Self {
let bytes = bincode::serialize(&head).unwrap();
EncodedBlockHeader(ByteBuf::from(bytes))
}
}
impl From<EncodedBlockHeader> for BlockHeader {
fn from(encoded_head: EncodedBlockHeader) -> Self {
bincode::deserialize(encoded_head.0.as_slice()).expect("Decode block head should success")
}
}
impl std::ops::Deref for EncodedBlockHeader {
type Target = ByteBuf;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct BlockHeaderExt {
pub height: BlockHeight,
pub validate_on_chain: bool,
pub sharding_reported: bool,
pub has_local_body: bool,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct ConsensusBlockHeader {
pub head: BlockHeader,
pub timestamp: TimeInNs,
}
impl Storable for ConsensusBlockHeader {
fn to_bytes(&self) -> std::borrow::Cow<[u8]> {
let mut bytes = self.head.to_bytes().into_owned();
bytes.extend_from_slice(self.timestamp.0.to_le_bytes().as_slice());
bytes.into()
}
fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self {
let mut bytes: Vec<u8> = bytes.into();
let timestamp_bytes = bytes
.drain(bytes.len() - 8..)
.collect::<Vec<u8>>()
.try_into()
.expect("ConsensusBlockHeader timestamp should have 8 bytes");
ConsensusBlockHeader {
head: BlockHeader::from_bytes(bytes.into()),
timestamp: TimeInNs(u64::from_le_bytes(timestamp_bytes)),
}
}
const BOUND: Bound = Bound::Bounded {
max_size: 233,
is_fixed_size: false,
};
}
#[derive(CandidType, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct CandidConsensusBlockHeader {
pub head: CandidBlockHeader,
pub timestamp: TimeInNs,
}
impl From<CandidConsensusBlockHeader> for ConsensusBlockHeader {
fn from(candid_consensus_block_header: CandidConsensusBlockHeader) -> Self {
ConsensusBlockHeader {
head: candid_consensus_block_header.head.into(),
timestamp: candid_consensus_block_header.timestamp,
}
}
}
impl From<ConsensusBlockHeader> for CandidConsensusBlockHeader {
fn from(consensus_block_header: ConsensusBlockHeader) -> Self {
CandidConsensusBlockHeader {
head: consensus_block_header.head.into(),
timestamp: consensus_block_header.timestamp,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_storable_for_block_header() {
let block_header = BlockHeader {
version: 0,
height: BlockHeight::from(u128::MAX),
p_id: PackageId::from(u128::MAX),
pre_block_hash: [1; 32],
tx_root: [2; 32],
state_changed_root: [3; 32],
data_integrity_root: [4; 32],
rejected_tx_root: [5; 32],
};
let bytes = block_header.to_bytes();
assert!(
bytes.len() <= 225,
"block head bytes length should be less than 225, but got {}",
bytes.len()
);
let block_header2 = BlockHeader::from_bytes(bytes);
assert_eq!(block_header, block_header2);
}
#[test]
fn test_bincode_for_bytes() {
let bytes = vec![1; 255];
let bytes2 = ByteBuf::from(bytes.clone());
let s1 = bincode::serialize(&bytes).unwrap();
let s2 = bincode::serialize(&bytes2).unwrap();
assert_eq!(
s1.len(),
s2.len(),
"bincode serialize for bytes and ByteBuf should have same length, but got {} and {}",
s1.len(),
s2.len()
);
}
}