use std::fmt::{self, Display};
use std::num::ParseIntError;
use std::str::FromStr;
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use data_encoding::{HEXLOWER, HEXUPPER};
use namada_macros::BorshDeserializer;
#[cfg(feature = "migrations")]
use namada_migrations::*;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use thiserror::Error;
use crate::hash::Hash;
use crate::time::DateTimeUtc;
pub const BLOCK_HASH_LENGTH: usize = 32;
pub const BLOCK_HEIGHT_LENGTH: usize = 8;
pub const CHAIN_ID_LENGTH: usize = 30;
pub const CHAIN_ID_PREFIX_MAX_LEN: usize = 19;
pub const CHAIN_ID_PREFIX_SEP: char = '.';
pub const DEFAULT_CHAIN_ID: &str = "namada-internal.00000000000000";
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(
Debug,
Clone,
Serialize,
Deserialize,
BorshSerialize,
BorshDeserialize,
BorshDeserializer,
BorshSchema,
PartialOrd,
Ord,
PartialEq,
Eq,
Hash,
)]
#[serde(transparent)]
pub struct ChainId(pub String);
impl ChainId {
pub fn as_str(&self) -> &str {
&self.0
}
pub fn from_genesis(
ChainIdPrefix(prefix): ChainIdPrefix,
genesis_bytes: impl AsRef<[u8]>,
) -> Self {
let mut hasher = Sha256::new();
hasher.update(genesis_bytes);
#[allow(clippy::arithmetic_side_effects)]
let width = CHAIN_ID_LENGTH - 1 - prefix.len();
let hash = format!("{:.width$x}", hasher.finalize(), width = width,);
let raw = format!("{}{}{}", prefix, CHAIN_ID_PREFIX_SEP, hash);
ChainId(raw)
}
pub fn validate(
&self,
genesis_bytes: impl AsRef<[u8]>,
) -> Vec<ChainIdValidationError> {
let mut errors = vec![];
match self.0.rsplit_once(CHAIN_ID_PREFIX_SEP) {
Some((prefix, hash)) => {
if prefix.len() > CHAIN_ID_PREFIX_MAX_LEN {
errors.push(ChainIdValidationError::Prefix(
ChainIdPrefixParseError::UnexpectedLen(prefix.len()),
))
}
let mut hasher = Sha256::new();
hasher.update(genesis_bytes);
#[allow(clippy::arithmetic_side_effects)]
let width = CHAIN_ID_LENGTH - 1 - prefix.len();
let expected_hash =
format!("{:.width$x}", hasher.finalize(), width = width,);
if hash != expected_hash {
errors.push(ChainIdValidationError::InvalidHash(
expected_hash,
hash.to_string(),
));
}
}
None => {
errors.push(ChainIdValidationError::MissingSeparator);
}
}
errors
}
pub fn prefix(&self) -> Option<ChainIdPrefix> {
let ChainId(chain_id) = self;
let (prefix, _) = chain_id.rsplit_once(CHAIN_ID_PREFIX_SEP)?;
Some(ChainIdPrefix(prefix.to_string()))
}
}
#[derive(
Clone,
Copy,
BorshSerialize,
BorshDeserialize,
BorshDeserializer,
BorshSchema,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Debug,
Serialize,
Deserialize,
)]
pub struct BlockHeight(pub u64);
impl Default for BlockHeight {
fn default() -> Self {
Self::sentinel()
}
}
impl Display for BlockHeight {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<BlockHeight> for u64 {
fn from(height: BlockHeight) -> Self {
height.0
}
}
impl FromStr for BlockHeight {
type Err = ParseIntError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Ok(Self(s.parse::<u64>()?))
}
}
#[derive(
Clone,
Default,
BorshSerialize,
BorshDeserialize,
BorshDeserializer,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
)]
pub struct BlockHash(pub [u8; BLOCK_HASH_LENGTH]);
impl Display for BlockHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", HEXUPPER.encode(&self.0))
}
}
impl From<Hash> for BlockHash {
fn from(hash: Hash) -> Self {
BlockHash(hash.0)
}
}
impl From<u64> for BlockHeight {
fn from(height: u64) -> Self {
BlockHeight(height)
}
}
impl From<tendermint::block::Height> for BlockHeight {
fn from(height: tendermint::block::Height) -> Self {
Self(u64::from(height))
}
}
impl TryFrom<BlockHeight> for tendermint::block::Height {
type Error = tendermint::Error;
fn try_from(height: BlockHeight) -> std::result::Result<Self, Self::Error> {
Self::try_from(height.0)
}
}
impl TryFrom<i64> for BlockHeight {
type Error = String;
fn try_from(value: i64) -> std::result::Result<Self, Self::Error> {
value
.try_into()
.map(BlockHeight)
.map_err(|e| format!("Unexpected height value {}, {}", value, e))
}
}
impl BlockHeight {
pub const fn first() -> Self {
Self(1)
}
pub const fn sentinel() -> Self {
Self(0)
}
pub fn next_height(&self) -> BlockHeight {
BlockHeight(
self.0
.checked_add(1)
.expect("Block height must not overflow"),
)
}
pub fn prev_height(&self) -> Option<BlockHeight> {
Some(BlockHeight(self.0.checked_sub(1)?))
}
#[must_use = "this returns the result of the operation, without modifying \
the original"]
pub fn checked_add(self, rhs: impl Into<BlockHeight>) -> Option<Self> {
let BlockHeight(rhs) = rhs.into();
Some(Self(self.0.checked_add(rhs)?))
}
#[must_use = "this returns the result of the operation, without modifying \
the original"]
pub fn checked_sub(self, rhs: impl Into<BlockHeight>) -> Option<Self> {
let BlockHeight(rhs) = rhs.into();
Some(Self(self.0.checked_sub(rhs)?))
}
}
impl TryFrom<&[u8]> for BlockHash {
type Error = ParseBlockHashError;
fn try_from(value: &[u8]) -> Result<Self, ParseBlockHashError> {
if value.len() != BLOCK_HASH_LENGTH {
return Err(ParseBlockHashError::ParseBlockHash(format!(
"Unexpected block hash length {}, expected {}",
value.len(),
BLOCK_HASH_LENGTH
)));
}
let mut hash = [0; 32];
hash.copy_from_slice(value);
Ok(BlockHash(hash))
}
}
#[allow(missing_docs)]
#[derive(Error, Debug)]
pub enum ParseBlockHashError {
#[error("Error parsing block hash: {0}")]
ParseBlockHash(String),
}
impl core::fmt::Debug for BlockHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let hash = HEXLOWER.encode(&self.0);
f.debug_tuple("BlockHash").field(&hash).finish()
}
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(
Clone,
Copy,
Default,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
BorshSerialize,
BorshDeserialize,
BorshDeserializer,
BorshSchema,
Serialize,
Deserialize,
)]
pub struct Epoch(pub u64);
impl Display for Epoch {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for Epoch {
type Err = ParseIntError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let raw: u64 = u64::from_str(s)?;
Ok(Self(raw))
}
}
impl Epoch {
pub fn next(&self) -> Self {
Self(self.0.checked_add(1).expect("Epoch shouldn't overflow"))
}
pub fn prev(&self) -> Option<Self> {
Some(Self(self.0.checked_sub(1)?))
}
pub fn iter_range(self, len: u64) -> impl Iterator<Item = Epoch> + Clone {
let start_ix: u64 = self.into();
let end_ix: u64 = start_ix.checked_add(len).unwrap_or(u64::MAX);
(start_ix..end_ix).map(Epoch::from)
}
pub fn iter_bounds_inclusive(
start: Self,
end: Self,
) -> impl Iterator<Item = Epoch> + Clone {
let start_ix = start.0;
let end_ix = end.0;
(start_ix..=end_ix).map(Epoch::from)
}
#[must_use = "this returns the result of the operation, without modifying \
the original"]
pub fn checked_add(self, rhs: impl Into<Epoch>) -> Option<Self> {
let Epoch(rhs) = rhs.into();
Some(Self(self.0.checked_add(rhs)?))
}
pub fn unchecked_add(self, rhs: impl Into<Epoch>) -> Self {
self.checked_add(rhs)
.expect("Epoch addition shouldn't overflow")
}
#[must_use = "this returns the result of the operation, without modifying \
the original"]
pub fn checked_sub(self, rhs: impl Into<Epoch>) -> Option<Self> {
let Epoch(rhs) = rhs.into();
Some(Self(self.0.checked_sub(rhs)?))
}
#[must_use = "this returns the result of the operation, without modifying \
the original"]
pub fn checked_div(self, rhs: impl Into<Epoch>) -> Option<Self> {
let Epoch(rhs) = rhs.into();
Some(Self(self.0.checked_div(rhs)?))
}
#[must_use = "this returns the result of the operation, without modifying \
the original"]
pub fn checked_mul(self, rhs: impl Into<Epoch>) -> Option<Self> {
let Epoch(rhs) = rhs.into();
Some(Self(self.0.checked_mul(rhs)?))
}
#[must_use = "this returns the result of the operation, without modifying \
the original"]
pub fn checked_rem(self, rhs: impl Into<Epoch>) -> Option<Self> {
let Epoch(rhs) = rhs.into();
Some(Self(self.0.checked_rem(rhs)?))
}
#[must_use = "this returns the result of the operation, without modifying \
the original"]
pub fn saturating_sub(self, rhs: Epoch) -> Self {
self.checked_sub(rhs).unwrap_or_default()
}
}
impl From<u64> for Epoch {
fn from(epoch: u64) -> Self {
Epoch(epoch)
}
}
impl From<Epoch> for u64 {
fn from(epoch: Epoch) -> Self {
epoch.0
}
}
#[derive(
Clone,
Debug,
Default,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
BorshSerialize,
BorshDeserialize,
BorshDeserializer,
)]
pub struct Epochs {
pub first_block_heights: Vec<BlockHeight>,
}
impl Epochs {
pub fn new_epoch(&mut self, block_height: BlockHeight) {
self.first_block_heights.push(block_height);
}
pub fn get_epoch(&self, block_height: BlockHeight) -> Option<Epoch> {
if let Some((_first_known_epoch_height, rest)) =
self.first_block_heights.split_first()
{
let mut epoch = Epoch::default();
for next_block_height in rest {
if block_height < *next_block_height {
return Some(epoch);
} else {
epoch = epoch.next();
}
}
return Some(epoch);
}
None
}
pub fn get_epoch_start_height(
&self,
height: BlockHeight,
) -> Option<BlockHeight> {
for start_height in self.first_block_heights.iter().rev() {
if *start_height <= height {
return Some(*start_height);
}
}
None
}
pub fn get_start_height_of_epoch(
&self,
epoch: Epoch,
) -> Option<BlockHeight> {
if epoch.0 > self.first_block_heights.len() as u64 {
return None;
}
let idx = usize::try_from(epoch.0).ok()?;
self.first_block_heights.get(idx).copied()
}
pub fn first_block_heights(&self) -> &[BlockHeight] {
&self.first_block_heights
}
}
#[derive(
Clone, Debug, BorshSerialize, BorshDeserialize, BorshDeserializer, Default,
)]
pub struct BlockHeader {
pub hash: Hash,
pub time: DateTimeUtc,
pub next_validators_hash: Hash,
}
impl BlockHeader {
pub const fn encoded_len() -> usize {
103
}
}
#[allow(missing_docs)]
#[derive(Debug, Error)]
pub enum ChainIdValidationError {
#[error(
"The prefix separator character '{CHAIN_ID_PREFIX_SEP}' is missing"
)]
MissingSeparator,
#[error("The chain ID hash is not valid, expected {0}, got {1}")]
InvalidHash(String, String),
#[error("Invalid prefix {0}")]
Prefix(ChainIdPrefixParseError),
}
impl Default for ChainId {
fn default() -> Self {
Self(DEFAULT_CHAIN_ID.to_string())
}
}
impl fmt::Display for ChainId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[allow(missing_docs)]
#[derive(Debug, Error)]
pub enum ChainIdParseError {
#[error("Chain ID must be {CHAIN_ID_LENGTH} long, got {0}")]
UnexpectedLen(usize),
#[error(
"The chain ID contains forbidden characters: {0:?}. Only alphanumeric \
characters and `-`, `_` and `.` are allowed."
)]
ForbiddenCharacters(Vec<char>),
}
impl FromStr for ChainId {
type Err = ChainIdParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let len = s.len();
if len != CHAIN_ID_LENGTH {
return Err(ChainIdParseError::UnexpectedLen(len));
}
let mut forbidden_chars = s
.chars()
.filter(|char| {
!matches!(*char as u8, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'-' | b'_' | b'.')
})
.peekable();
if forbidden_chars.peek().is_some() {
return Err(ChainIdParseError::ForbiddenCharacters(
forbidden_chars.collect(),
));
}
Ok(Self(s.to_owned()))
}
}
#[derive(
Debug,
Clone,
Serialize,
Deserialize,
BorshSerialize,
BorshDeserialize,
BorshDeserializer,
)]
#[serde(transparent)]
pub struct ChainIdPrefix(String);
impl fmt::Display for ChainIdPrefix {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl ChainIdPrefix {
pub fn as_str(&self) -> &str {
&self.0
}
pub fn temp_chain_id(&self) -> ChainId {
ChainId(self.0.clone())
}
}
#[allow(missing_docs)]
#[derive(Debug, Error)]
pub enum ChainIdPrefixParseError {
#[error(
"Chain ID prefix must at least 1 and up to {CHAIN_ID_PREFIX_MAX_LEN} \
characters long, got {0}"
)]
UnexpectedLen(usize),
#[error(
"The prefix contains forbidden characters: {0:?}. Only alphanumeric \
characters and `-`, `_` and `.` are allowed."
)]
ForbiddenCharacters(Vec<char>),
}
impl FromStr for ChainIdPrefix {
type Err = ChainIdPrefixParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let len = s.len();
if !(1..=CHAIN_ID_PREFIX_MAX_LEN).contains(&len) {
return Err(ChainIdPrefixParseError::UnexpectedLen(len));
}
let mut forbidden_chars = s
.chars()
.filter(|char| {
!matches!(*char as u8, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'-' | b'_' | b'.')
})
.peekable();
if forbidden_chars.peek().is_some() {
return Err(ChainIdPrefixParseError::ForbiddenCharacters(
forbidden_chars.collect(),
));
}
Ok(Self(s.to_owned()))
}
}
#[cfg(any(test, feature = "testing"))]
pub mod testing {
use std::ops::{Add, AddAssign, Sub};
use proptest::prelude::*;
use super::*;
use crate::time::DateTimeUtc;
impl<T> Add<T> for BlockHeight
where
T: Into<BlockHeight>,
{
type Output = BlockHeight;
fn add(self, rhs: T) -> Self::Output {
self.checked_add(rhs.into()).unwrap()
}
}
impl<T> AddAssign<T> for BlockHeight
where
T: Into<BlockHeight>,
{
fn add_assign(&mut self, rhs: T) {
*self = self.checked_add(rhs.into()).unwrap()
}
}
impl<T> Add<T> for Epoch
where
T: Into<Epoch>,
{
type Output = Epoch;
fn add(self, rhs: T) -> Self::Output {
self.checked_add(rhs.into()).unwrap()
}
}
impl<T> Sub<T> for Epoch
where
T: Into<Epoch>,
{
type Output = Epoch;
fn sub(self, rhs: T) -> Self::Output {
self.checked_sub(rhs.into()).unwrap()
}
}
prop_compose! {
pub fn arb_epoch()(epoch: u64) -> Epoch {
Epoch(epoch)
}
}
pub fn get_dummy_header() -> BlockHeader {
use crate::time::DurationSecs;
BlockHeader {
hash: Hash([0; 32]),
#[allow(
clippy::disallowed_methods,
clippy::arithmetic_side_effects
)]
time: DateTimeUtc::now() + DurationSecs(5),
next_validators_hash: Hash([0; 32]),
}
}
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use super::*;
use crate::borsh::BorshSerializeExt;
proptest! {
#[test]
fn test_any_generated_chain_id_is_valid(
prefix in proptest::string::string_regex(r#"[A-Za-z0-9\.\-_]{1,19}"#).unwrap(),
genesis_bytes in any::<Vec<u8>>(),
) {
let chain_id_prefix = ChainIdPrefix::from_str(&prefix).unwrap();
let chain_id = ChainId::from_genesis(chain_id_prefix, &genesis_bytes);
let errors = chain_id.validate(&genesis_bytes);
assert!(errors.is_empty(), "There should be no validation errors {:#?}", errors);
}
}
#[test]
fn test_predecessor_epochs_and_heights() {
let mut epochs = Epochs {
first_block_heights: vec![BlockHeight::first()],
};
println!("epochs {:#?}", epochs);
assert_eq!(
epochs.get_start_height_of_epoch(Epoch(0)),
Some(BlockHeight(1))
);
assert_eq!(epochs.get_epoch(BlockHeight(0)), Some(Epoch(0)));
epochs.new_epoch(BlockHeight(10));
println!("epochs {:#?}", epochs);
assert_eq!(
epochs.get_start_height_of_epoch(Epoch(1)),
Some(BlockHeight(10))
);
assert_eq!(epochs.get_epoch(BlockHeight(0)), Some(Epoch(0)));
assert_eq!(epochs.get_epoch_start_height(BlockHeight(0)), None);
assert_eq!(
epochs.get_epoch_start_height(BlockHeight(1)),
Some(BlockHeight(1))
);
assert_eq!(epochs.get_epoch(BlockHeight(9)), Some(Epoch(0)));
assert_eq!(
epochs.get_epoch_start_height(BlockHeight(9)),
Some(BlockHeight(1))
);
assert_eq!(epochs.get_epoch(BlockHeight(10)), Some(Epoch(1)));
assert_eq!(
epochs.get_epoch_start_height(BlockHeight(10)),
Some(BlockHeight(10))
);
assert_eq!(epochs.get_epoch(BlockHeight(11)), Some(Epoch(1)));
assert_eq!(
epochs.get_epoch_start_height(BlockHeight(11)),
Some(BlockHeight(10))
);
assert_eq!(epochs.get_epoch(BlockHeight(100)), Some(Epoch(1)));
assert_eq!(
epochs.get_epoch_start_height(BlockHeight(100)),
Some(BlockHeight(10))
);
epochs.new_epoch(BlockHeight(20));
println!("epochs {:#?}", epochs);
assert_eq!(
epochs.get_start_height_of_epoch(Epoch(2)),
Some(BlockHeight(20))
);
assert_eq!(epochs.get_epoch(BlockHeight(0)), Some(Epoch(0)));
assert_eq!(epochs.get_epoch(BlockHeight(9)), Some(Epoch(0)));
assert_eq!(epochs.get_epoch(BlockHeight(10)), Some(Epoch(1)));
assert_eq!(epochs.get_epoch(BlockHeight(11)), Some(Epoch(1)));
assert_eq!(
epochs.get_epoch_start_height(BlockHeight(11)),
Some(BlockHeight(10))
);
assert_eq!(epochs.get_epoch(BlockHeight(20)), Some(Epoch(2)));
assert_eq!(
epochs.get_epoch_start_height(BlockHeight(20)),
Some(BlockHeight(20))
);
assert_eq!(epochs.get_epoch(BlockHeight(100)), Some(Epoch(2)));
assert_eq!(
epochs.get_epoch_start_height(BlockHeight(100)),
Some(BlockHeight(20))
);
epochs.new_epoch(BlockHeight(200));
println!("epochs {:#?}", epochs);
assert_eq!(
epochs.get_start_height_of_epoch(Epoch(3)),
Some(BlockHeight(200))
);
assert_eq!(epochs.get_epoch(BlockHeight(0)), Some(Epoch(0)));
assert_eq!(epochs.get_epoch(BlockHeight(9)), Some(Epoch(0)));
assert_eq!(epochs.get_epoch(BlockHeight(10)), Some(Epoch(1)));
assert_eq!(epochs.get_epoch(BlockHeight(11)), Some(Epoch(1)));
assert_eq!(epochs.get_epoch(BlockHeight(20)), Some(Epoch(2)));
assert_eq!(epochs.get_epoch(BlockHeight(100)), Some(Epoch(2)));
assert_eq!(
epochs.get_epoch_start_height(BlockHeight(100)),
Some(BlockHeight(20))
);
assert_eq!(epochs.get_epoch(BlockHeight(200)), Some(Epoch(3)));
assert_eq!(
epochs.get_epoch_start_height(BlockHeight(200)),
Some(BlockHeight(200))
);
epochs.new_epoch(BlockHeight(300));
println!("epochs {:#?}", epochs);
assert_eq!(
epochs.get_start_height_of_epoch(Epoch(4)),
Some(BlockHeight(300))
);
assert_eq!(epochs.get_epoch(BlockHeight(20)), Some(Epoch(2)));
assert_eq!(epochs.get_epoch(BlockHeight(100)), Some(Epoch(2)));
assert_eq!(epochs.get_epoch(BlockHeight(200)), Some(Epoch(3)));
assert_eq!(epochs.get_epoch(BlockHeight(300)), Some(Epoch(4)));
epochs.new_epoch(BlockHeight(499));
println!("epochs {:#?}", epochs);
assert_eq!(
epochs.get_start_height_of_epoch(Epoch(5)),
Some(BlockHeight(499))
);
assert_eq!(epochs.get_epoch(BlockHeight(20)), Some(Epoch(2)));
assert_eq!(epochs.get_epoch(BlockHeight(100)), Some(Epoch(2)));
assert_eq!(epochs.get_epoch(BlockHeight(200)), Some(Epoch(3)));
assert_eq!(epochs.get_epoch(BlockHeight(300)), Some(Epoch(4)));
assert_eq!(epochs.get_epoch(BlockHeight(499)), Some(Epoch(5)));
epochs.new_epoch(BlockHeight(500));
println!("epochs {:#?}", epochs);
assert_eq!(
epochs.get_start_height_of_epoch(Epoch(6)),
Some(BlockHeight(500))
);
assert_eq!(epochs.get_epoch(BlockHeight(200)), Some(Epoch(3)));
assert_eq!(epochs.get_epoch(BlockHeight(300)), Some(Epoch(4)));
assert_eq!(epochs.get_epoch(BlockHeight(499)), Some(Epoch(5)));
assert_eq!(epochs.get_epoch(BlockHeight(500)), Some(Epoch(6)));
epochs.new_epoch(BlockHeight(550));
println!("epochs {:#?}", epochs);
assert_eq!(
epochs.get_start_height_of_epoch(Epoch(7)),
Some(BlockHeight(550))
);
assert_eq!(epochs.get_epoch(BlockHeight(300)), Some(Epoch(4)));
assert_eq!(epochs.get_epoch(BlockHeight(499)), Some(Epoch(5)));
assert_eq!(epochs.get_epoch(BlockHeight(500)), Some(Epoch(6)));
assert_eq!(epochs.get_epoch(BlockHeight(550)), Some(Epoch(7)));
epochs.new_epoch(BlockHeight(600));
println!("epochs {:#?}", epochs);
assert_eq!(
epochs.get_start_height_of_epoch(Epoch(7)),
Some(BlockHeight(550))
);
assert_eq!(
epochs.get_start_height_of_epoch(Epoch(8)),
Some(BlockHeight(600))
);
assert_eq!(epochs.get_epoch(BlockHeight(500)), Some(Epoch(6)));
assert_eq!(epochs.get_epoch(BlockHeight(550)), Some(Epoch(7)));
assert_eq!(epochs.get_epoch(BlockHeight(600)), Some(Epoch(8)));
for e in [9, 10, 11, 12] {
assert!(
epochs.get_start_height_of_epoch(Epoch(e)).is_none(),
"Epoch: {e}"
);
}
}
#[test]
fn test_block_header_encoded_len() {
#[allow(clippy::disallowed_methods)]
let header = BlockHeader {
hash: Hash::zero(),
time: DateTimeUtc::now(),
next_validators_hash: Hash::zero(),
};
let len = header.serialize_to_vec().len();
assert_eq!(len, BlockHeader::encoded_len())
}
}