use std::time::{SystemTime, UNIX_EPOCH};
use chia_sha2::Sha256;
use chia_streamable_macro::Streamable;
use serde::{Deserialize, Serialize};
use crate::constants::{
DFSP_ACTIVATION_HEIGHT, EMPTY_ROOT, MAX_BLOCK_SIZE, MAX_COST_PER_BLOCK,
MAX_FUTURE_TIMESTAMP_SECONDS, ZERO_HASH,
};
use crate::error::BlockError;
use crate::primitives::{Bytes32, Cost, VERSION_V1, VERSION_V2};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Streamable)]
pub struct L2BlockHeader {
pub version: u16,
pub height: u64,
pub epoch: u64,
pub parent_hash: Bytes32,
pub state_root: Bytes32,
pub spends_root: Bytes32,
pub additions_root: Bytes32,
pub removals_root: Bytes32,
pub receipts_root: Bytes32,
pub l1_height: u32,
pub l1_hash: Bytes32,
pub timestamp: u64,
pub proposer_index: u32,
pub spend_bundle_count: u32,
pub total_cost: Cost,
pub total_fees: u64,
pub additions_count: u32,
pub removals_count: u32,
pub block_size: u32,
pub filter_hash: Bytes32,
pub extension_data: Bytes32,
#[serde(default)]
pub l1_collateral_coin_id: Option<Bytes32>,
#[serde(default)]
pub l1_reserve_coin_id: Option<Bytes32>,
#[serde(default)]
pub l1_prev_epoch_finalizer_coin_id: Option<Bytes32>,
#[serde(default)]
pub l1_curr_epoch_finalizer_coin_id: Option<Bytes32>,
#[serde(default)]
pub l1_network_coin_id: Option<Bytes32>,
pub slash_proposal_count: u32,
pub slash_proposals_root: Bytes32,
pub collateral_registry_root: Bytes32,
pub cid_state_root: Bytes32,
pub node_registry_root: Bytes32,
pub namespace_update_root: Bytes32,
pub dfsp_finalize_commitment_root: Bytes32,
}
impl L2BlockHeader {
#[inline]
pub fn protocol_version_for_height_with_activation(
height: u64,
dfsp_activation_height: u64,
) -> u16 {
if dfsp_activation_height == u64::MAX {
VERSION_V1
} else if height >= dfsp_activation_height {
VERSION_V2
} else {
VERSION_V1
}
}
#[inline]
pub fn protocol_version_for_height(height: u64) -> u16 {
Self::protocol_version_for_height_with_activation(height, DFSP_ACTIVATION_HEIGHT)
}
fn unix_secs_wall_clock() -> Result<u64, BlockError> {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.map_err(|_| {
BlockError::InvalidData(
"system clock before UNIX epoch; cannot validate header timestamp".into(),
)
})
}
pub fn validate_with_dfsp_activation_at_unix(
&self,
dfsp_activation_height: u64,
now_secs: u64,
) -> Result<(), BlockError> {
let expected =
Self::protocol_version_for_height_with_activation(self.height, dfsp_activation_height);
if self.version != expected {
return Err(BlockError::InvalidVersion {
expected,
actual: self.version,
});
}
if self.height < dfsp_activation_height {
let dfsp_roots = [
self.collateral_registry_root,
self.cid_state_root,
self.node_registry_root,
self.namespace_update_root,
self.dfsp_finalize_commitment_root,
];
for root in &dfsp_roots {
if *root != EMPTY_ROOT {
return Err(BlockError::InvalidData(
"DFSP root must be EMPTY_ROOT before activation".into(),
));
}
}
}
if self.total_cost > MAX_COST_PER_BLOCK {
return Err(BlockError::CostExceeded {
cost: self.total_cost,
max: MAX_COST_PER_BLOCK,
});
}
if self.block_size > MAX_BLOCK_SIZE {
return Err(BlockError::TooLarge {
size: self.block_size,
max: MAX_BLOCK_SIZE,
});
}
let max_allowed = now_secs.saturating_add(MAX_FUTURE_TIMESTAMP_SECONDS);
if self.timestamp > max_allowed {
return Err(BlockError::TimestampTooFarInFuture {
timestamp: self.timestamp,
max_allowed,
});
}
Ok(())
}
pub fn validate_with_dfsp_activation(
&self,
dfsp_activation_height: u64,
) -> Result<(), BlockError> {
let now_secs = Self::unix_secs_wall_clock()?;
self.validate_with_dfsp_activation_at_unix(dfsp_activation_height, now_secs)
}
pub fn validate(&self) -> Result<(), BlockError> {
self.validate_with_dfsp_activation(DFSP_ACTIVATION_HEIGHT)?;
Ok(())
}
pub const HASH_PREIMAGE_LEN: usize = 710;
pub fn hash_preimage_bytes(&self) -> [u8; Self::HASH_PREIMAGE_LEN] {
fn put(buf: &mut [u8; L2BlockHeader::HASH_PREIMAGE_LEN], i: &mut usize, bytes: &[u8]) {
buf[*i..*i + bytes.len()].copy_from_slice(bytes);
*i += bytes.len();
}
fn put_opt(
buf: &mut [u8; L2BlockHeader::HASH_PREIMAGE_LEN],
i: &mut usize,
o: &Option<Bytes32>,
) {
let slice = match o {
Some(b) => b.as_ref(),
None => ZERO_HASH.as_ref(),
};
buf[*i..*i + 32].copy_from_slice(slice);
*i += 32;
}
let mut buf = [0u8; Self::HASH_PREIMAGE_LEN];
let mut i = 0usize;
put(&mut buf, &mut i, &self.version.to_le_bytes());
put(&mut buf, &mut i, &self.height.to_le_bytes());
put(&mut buf, &mut i, &self.epoch.to_le_bytes());
put(&mut buf, &mut i, self.parent_hash.as_ref());
put(&mut buf, &mut i, self.state_root.as_ref());
put(&mut buf, &mut i, self.spends_root.as_ref());
put(&mut buf, &mut i, self.additions_root.as_ref());
put(&mut buf, &mut i, self.removals_root.as_ref());
put(&mut buf, &mut i, self.receipts_root.as_ref());
put(&mut buf, &mut i, &self.l1_height.to_le_bytes());
put(&mut buf, &mut i, self.l1_hash.as_ref());
put(&mut buf, &mut i, &self.timestamp.to_le_bytes());
put(&mut buf, &mut i, &self.proposer_index.to_le_bytes());
put(&mut buf, &mut i, &self.spend_bundle_count.to_le_bytes());
put(&mut buf, &mut i, &self.total_cost.to_le_bytes());
put(&mut buf, &mut i, &self.total_fees.to_le_bytes());
put(&mut buf, &mut i, &self.additions_count.to_le_bytes());
put(&mut buf, &mut i, &self.removals_count.to_le_bytes());
put(&mut buf, &mut i, &self.block_size.to_le_bytes());
put(&mut buf, &mut i, self.filter_hash.as_ref());
put(&mut buf, &mut i, self.extension_data.as_ref());
put_opt(&mut buf, &mut i, &self.l1_collateral_coin_id);
put_opt(&mut buf, &mut i, &self.l1_reserve_coin_id);
put_opt(&mut buf, &mut i, &self.l1_prev_epoch_finalizer_coin_id);
put_opt(&mut buf, &mut i, &self.l1_curr_epoch_finalizer_coin_id);
put_opt(&mut buf, &mut i, &self.l1_network_coin_id);
put(&mut buf, &mut i, &self.slash_proposal_count.to_le_bytes());
put(&mut buf, &mut i, self.slash_proposals_root.as_ref());
put(&mut buf, &mut i, self.collateral_registry_root.as_ref());
put(&mut buf, &mut i, self.cid_state_root.as_ref());
put(&mut buf, &mut i, self.node_registry_root.as_ref());
put(&mut buf, &mut i, self.namespace_update_root.as_ref());
put(
&mut buf,
&mut i,
self.dfsp_finalize_commitment_root.as_ref(),
);
debug_assert_eq!(i, Self::HASH_PREIMAGE_LEN);
buf
}
pub fn hash(&self) -> Bytes32 {
let mut hasher = Sha256::new();
hasher.update(self.hash_preimage_bytes());
Bytes32::new(hasher.finalize())
}
#[must_use]
pub fn to_bytes(&self) -> Vec<u8> {
bincode::serialize(self).expect("L2BlockHeader serialization should never fail")
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, BlockError> {
bincode::deserialize(bytes).map_err(|e| BlockError::InvalidData(e.to_string()))
}
#[allow(clippy::too_many_arguments)]
pub fn new(
height: u64,
epoch: u64,
parent_hash: Bytes32,
state_root: Bytes32,
spends_root: Bytes32,
additions_root: Bytes32,
removals_root: Bytes32,
receipts_root: Bytes32,
l1_height: u32,
l1_hash: Bytes32,
proposer_index: u32,
spend_bundle_count: u32,
total_cost: Cost,
total_fees: u64,
additions_count: u32,
removals_count: u32,
block_size: u32,
filter_hash: Bytes32,
) -> Self {
Self::with_l1_anchors(
height,
epoch,
parent_hash,
state_root,
spends_root,
additions_root,
removals_root,
receipts_root,
l1_height,
l1_hash,
0,
proposer_index,
spend_bundle_count,
total_cost,
total_fees,
additions_count,
removals_count,
block_size,
filter_hash,
ZERO_HASH,
None,
None,
None,
None,
None,
0,
EMPTY_ROOT,
EMPTY_ROOT,
EMPTY_ROOT,
EMPTY_ROOT,
EMPTY_ROOT,
EMPTY_ROOT,
)
}
#[allow(clippy::too_many_arguments)]
pub fn new_with_collateral(
height: u64,
epoch: u64,
parent_hash: Bytes32,
state_root: Bytes32,
spends_root: Bytes32,
additions_root: Bytes32,
removals_root: Bytes32,
receipts_root: Bytes32,
l1_height: u32,
l1_hash: Bytes32,
proposer_index: u32,
spend_bundle_count: u32,
total_cost: Cost,
total_fees: u64,
additions_count: u32,
removals_count: u32,
block_size: u32,
filter_hash: Bytes32,
l1_collateral_coin_id: Bytes32,
) -> Self {
Self::with_l1_anchors(
height,
epoch,
parent_hash,
state_root,
spends_root,
additions_root,
removals_root,
receipts_root,
l1_height,
l1_hash,
0,
proposer_index,
spend_bundle_count,
total_cost,
total_fees,
additions_count,
removals_count,
block_size,
filter_hash,
ZERO_HASH,
Some(l1_collateral_coin_id),
None,
None,
None,
None,
0,
EMPTY_ROOT,
EMPTY_ROOT,
EMPTY_ROOT,
EMPTY_ROOT,
EMPTY_ROOT,
EMPTY_ROOT,
)
}
#[allow(clippy::too_many_arguments)]
pub fn new_with_l1_proofs(
height: u64,
epoch: u64,
parent_hash: Bytes32,
state_root: Bytes32,
spends_root: Bytes32,
additions_root: Bytes32,
removals_root: Bytes32,
receipts_root: Bytes32,
l1_height: u32,
l1_hash: Bytes32,
proposer_index: u32,
spend_bundle_count: u32,
total_cost: Cost,
total_fees: u64,
additions_count: u32,
removals_count: u32,
block_size: u32,
filter_hash: Bytes32,
l1_collateral_coin_id: Bytes32,
l1_reserve_coin_id: Bytes32,
l1_prev_epoch_finalizer_coin_id: Bytes32,
l1_curr_epoch_finalizer_coin_id: Bytes32,
l1_network_coin_id: Bytes32,
) -> Self {
Self::with_l1_anchors(
height,
epoch,
parent_hash,
state_root,
spends_root,
additions_root,
removals_root,
receipts_root,
l1_height,
l1_hash,
0,
proposer_index,
spend_bundle_count,
total_cost,
total_fees,
additions_count,
removals_count,
block_size,
filter_hash,
ZERO_HASH,
Some(l1_collateral_coin_id),
Some(l1_reserve_coin_id),
Some(l1_prev_epoch_finalizer_coin_id),
Some(l1_curr_epoch_finalizer_coin_id),
Some(l1_network_coin_id),
0,
EMPTY_ROOT,
EMPTY_ROOT,
EMPTY_ROOT,
EMPTY_ROOT,
EMPTY_ROOT,
EMPTY_ROOT,
)
}
pub fn genesis(network_id: Bytes32, l1_height: u32, l1_hash: Bytes32) -> Self {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system clock before UNIX epoch; genesis header requires wall-clock time")
.as_secs();
let height = 0u64;
Self::with_l1_anchors(
height, 0, network_id, EMPTY_ROOT, EMPTY_ROOT, EMPTY_ROOT, EMPTY_ROOT, EMPTY_ROOT,
l1_height, l1_hash, timestamp, 0, 0, 0, 0, 0, 0, 0, EMPTY_ROOT, ZERO_HASH, None, None,
None, None, None, 0, EMPTY_ROOT, EMPTY_ROOT, EMPTY_ROOT, EMPTY_ROOT, EMPTY_ROOT,
EMPTY_ROOT,
)
}
#[allow(clippy::too_many_arguments)]
fn with_l1_anchors(
height: u64,
epoch: u64,
parent_hash: Bytes32,
state_root: Bytes32,
spends_root: Bytes32,
additions_root: Bytes32,
removals_root: Bytes32,
receipts_root: Bytes32,
l1_height: u32,
l1_hash: Bytes32,
timestamp: u64,
proposer_index: u32,
spend_bundle_count: u32,
total_cost: Cost,
total_fees: u64,
additions_count: u32,
removals_count: u32,
block_size: u32,
filter_hash: Bytes32,
extension_data: Bytes32,
l1_collateral_coin_id: Option<Bytes32>,
l1_reserve_coin_id: Option<Bytes32>,
l1_prev_epoch_finalizer_coin_id: Option<Bytes32>,
l1_curr_epoch_finalizer_coin_id: Option<Bytes32>,
l1_network_coin_id: Option<Bytes32>,
slash_proposal_count: u32,
slash_proposals_root: Bytes32,
collateral_registry_root: Bytes32,
cid_state_root: Bytes32,
node_registry_root: Bytes32,
namespace_update_root: Bytes32,
dfsp_finalize_commitment_root: Bytes32,
) -> Self {
Self {
version: Self::protocol_version_for_height(height),
height,
epoch,
parent_hash,
state_root,
spends_root,
additions_root,
removals_root,
receipts_root,
l1_height,
l1_hash,
timestamp,
proposer_index,
spend_bundle_count,
total_cost,
total_fees,
additions_count,
removals_count,
block_size,
filter_hash,
extension_data,
l1_collateral_coin_id,
l1_reserve_coin_id,
l1_prev_epoch_finalizer_coin_id,
l1_curr_epoch_finalizer_coin_id,
l1_network_coin_id,
slash_proposal_count,
slash_proposals_root,
collateral_registry_root,
cid_state_root,
node_registry_root,
namespace_update_root,
dfsp_finalize_commitment_root,
}
}
}