use crate::utils::codec::{Decode, Encode};
use crate::{
Hashable, PrimitiveError, PrimitiveResult, Validate,
types::{AccountId, BlockNumber, Hash, Timestamp},
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)]
pub struct Header {
pub parent_hash: Hash,
pub number: BlockNumber,
pub timestamp: Timestamp,
pub state_root: Hash,
pub extrinsics_root: Hash,
pub digest: Vec<Vec<u8>>,
pub author: AccountId,
pub epoch: u32,
pub slot: u32,
}
impl Header {
pub fn new(
parent_hash: Hash,
number: BlockNumber,
timestamp: Timestamp,
state_root: Hash,
extrinsics_root: Hash,
author: AccountId,
epoch: u32,
slot: u32,
) -> Self {
Self {
parent_hash,
number,
timestamp,
state_root,
extrinsics_root,
digest: Vec::new(),
author,
epoch,
slot,
}
}
pub fn is_genesis(&self) -> bool {
self.number == BlockNumber(0)
}
pub fn encoded_size(&self) -> usize {
self.encode().len()
}
pub fn verify_basic(&self) -> PrimitiveResult<()> {
if self.is_genesis() && !self.parent_hash.is_zero() {
return Err(PrimitiveError::InvalidHeader(
"Genesis block must have zero parent hash".to_string(),
));
}
if !self.is_genesis() && self.parent_hash.is_zero() {
return Err(PrimitiveError::InvalidHeader(
"Non-genesis block cannot have zero parent hash".to_string(),
));
}
if self.timestamp == Timestamp(0) {
return Err(PrimitiveError::InvalidHeader(
"Header timestamp cannot be zero".to_string(),
));
}
if self.digest.len() > 100 {
return Err(PrimitiveError::InvalidHeader(
"Too many digest items".to_string(),
));
}
Ok(())
}
pub fn verify_against_parent(&self, parent: &Header) -> PrimitiveResult<()> {
if self.parent_hash != parent.hash() {
return Err(PrimitiveError::InvalidHeader(
"Parent hash mismatch".to_string(),
));
}
if self.number != parent.number + 1 {
return Err(PrimitiveError::InvalidHeader(
"Block number is not sequential".to_string(),
));
}
if self.timestamp <= parent.timestamp {
return Err(PrimitiveError::InvalidHeader(
"Block timestamp must advance".to_string(),
));
}
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64;
if self.timestamp > Timestamp(now + 60_000) {
return Err(PrimitiveError::InvalidHeader(
"Block timestamp too far in the future".to_string(),
));
}
Ok(())
}
}
impl Validate for Header {
fn validate(&self) -> PrimitiveResult<()> {
self.verify_basic()
}
}
impl Hashable for Header {
fn hash(&self) -> Hash {
use crate::crypto::hashing::{Blake3Hasher, Hasher};
let hasher = Blake3Hasher::new();
hasher.hash(&self.encode())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)]
pub struct HeaderDigest {
pub engine_id: [u8; 4],
pub data: Vec<u8>,
}
impl HeaderDigest {
pub fn new(engine_id: [u8; 4], data: Vec<u8>) -> Self {
Self { engine_id, data }
}
}
#[cfg(test)]
mod tests {
use super::*;
fn dummy_account() -> AccountId {
AccountId::new([1u8; 32])
}
#[test]
fn test_header_creation() {
let header = Header::new(
Hash::zero(),
BlockNumber(1),
Timestamp(1000),
Hash::zero(),
Hash::zero(),
dummy_account(),
0,
1,
);
assert_eq!(header.number, BlockNumber(1));
assert_eq!(header.timestamp, Timestamp(1000));
assert!(!header.is_genesis());
}
#[test]
fn test_genesis_header() {
let genesis = Header::new(
Hash::zero(),
BlockNumber(0),
Timestamp(1000),
Hash::zero(),
Hash::zero(),
dummy_account(),
0,
0,
);
assert!(genesis.is_genesis());
assert!(genesis.verify_basic().is_ok());
}
#[test]
fn test_header_validation() {
let parent_hash = Hash::from([1u8; 32]);
let header = Header::new(
parent_hash,
BlockNumber(1),
Timestamp(1000),
Hash::zero(),
Hash::zero(),
dummy_account(),
0,
1,
);
assert!(header.verify_basic().is_ok());
let invalid_header = Header::new(
Hash::zero(),
BlockNumber(1),
Timestamp(1000),
Hash::zero(),
Hash::zero(),
dummy_account(),
0,
1,
);
assert!(invalid_header.verify_basic().is_err());
let zero_time_header = Header::new(
parent_hash,
BlockNumber(1),
Timestamp(0),
Hash::zero(),
Hash::zero(),
dummy_account(),
0,
0,
);
assert!(zero_time_header.verify_basic().is_err());
}
#[test]
fn test_header_parent_verification() {
let parent = Header::new(
Hash::zero(),
BlockNumber(0),
Timestamp(1000),
Hash::zero(),
Hash::zero(),
dummy_account(),
0,
0,
);
let child = Header::new(
parent.hash(),
BlockNumber(1),
Timestamp(2000),
Hash::zero(),
Hash::zero(),
dummy_account(),
0,
1,
);
assert!(child.verify_against_parent(&parent).is_ok());
let wrong_parent = Header::new(
Hash::from([1u8; 32]),
BlockNumber(1),
Timestamp(2000),
Hash::zero(),
Hash::zero(),
dummy_account(),
0,
1,
);
assert!(wrong_parent.verify_against_parent(&parent).is_err());
let wrong_number = Header::new(
parent.hash(),
BlockNumber(3),
Timestamp(2000),
Hash::zero(),
Hash::zero(),
dummy_account(),
0,
3,
);
assert!(wrong_number.verify_against_parent(&parent).is_err());
let same_time = Header::new(
parent.hash(),
BlockNumber(1),
Timestamp(1000),
Hash::zero(),
Hash::zero(),
dummy_account(),
0,
1,
);
assert!(same_time.verify_against_parent(&parent).is_err());
}
#[test]
fn test_header_digest() {
let digest = HeaderDigest::new(*b"TEST", vec![1, 2, 3]);
assert_eq!(digest.engine_id, *b"TEST");
assert_eq!(digest.data, vec![1, 2, 3]);
}
}