namada_core/
chain.rs

1//! Chain related data types
2
3use std::fmt::{self, Display};
4use std::num::ParseIntError;
5use std::str::FromStr;
6
7use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
8use data_encoding::{HEXLOWER, HEXUPPER};
9use namada_macros::BorshDeserializer;
10#[cfg(feature = "migrations")]
11use namada_migrations::*;
12use serde::{Deserialize, Serialize};
13use sha2::{Digest, Sha256};
14use thiserror::Error;
15
16use crate::hash::Hash;
17use crate::time::DateTimeUtc;
18
19/// The length of the block hash
20pub const BLOCK_HASH_LENGTH: usize = 32;
21/// The length of the block height
22pub const BLOCK_HEIGHT_LENGTH: usize = 8;
23/// The length of the chain ID string
24pub const CHAIN_ID_LENGTH: usize = 30;
25/// The maximum length of chain ID prefix
26pub const CHAIN_ID_PREFIX_MAX_LEN: usize = 19;
27/// Separator between chain ID prefix and the generated hash
28pub const CHAIN_ID_PREFIX_SEP: char = '.';
29
30/// Release default chain ID. Must be [`CHAIN_ID_LENGTH`] long.
31pub const DEFAULT_CHAIN_ID: &str = "namada-internal.00000000000000";
32
33/// Chain ID
34#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
35#[derive(
36    Debug,
37    Clone,
38    Serialize,
39    Deserialize,
40    BorshSerialize,
41    BorshDeserialize,
42    BorshDeserializer,
43    BorshSchema,
44    PartialOrd,
45    Ord,
46    PartialEq,
47    Eq,
48    Hash,
49)]
50#[serde(transparent)]
51pub struct ChainId(pub String);
52
53impl ChainId {
54    /// Extracts a string slice containing the entire chain ID.
55    pub fn as_str(&self) -> &str {
56        &self.0
57    }
58
59    /// Derive the chain ID from the genesis hash and release version.
60    pub fn from_genesis(
61        ChainIdPrefix(prefix): ChainIdPrefix,
62        genesis_bytes: impl AsRef<[u8]>,
63    ) -> Self {
64        let mut hasher = Sha256::new();
65        hasher.update(genesis_bytes);
66        // less `1` for chain ID prefix separator char
67        // Cannot underflow as the `prefix.len` is checked
68        #[allow(clippy::arithmetic_side_effects)]
69        let width = CHAIN_ID_LENGTH - 1 - prefix.len();
70        // lowercase hex of the first `width` chars of the hash
71        let hash = format!("{:.width$x}", hasher.finalize(), width = width,);
72        let raw = format!("{}{}{}", prefix, CHAIN_ID_PREFIX_SEP, hash);
73        ChainId(raw)
74    }
75
76    /// Validate that chain ID is matching the expected value derived from the
77    /// genesis hash and release version.
78    pub fn validate(
79        &self,
80        genesis_bytes: impl AsRef<[u8]>,
81    ) -> Vec<ChainIdValidationError> {
82        let mut errors = vec![];
83        match self.0.rsplit_once(CHAIN_ID_PREFIX_SEP) {
84            Some((prefix, hash)) => {
85                if prefix.len() > CHAIN_ID_PREFIX_MAX_LEN {
86                    errors.push(ChainIdValidationError::Prefix(
87                        ChainIdPrefixParseError::UnexpectedLen(prefix.len()),
88                    ))
89                }
90                let mut hasher = Sha256::new();
91                hasher.update(genesis_bytes);
92                // less `1` for chain ID prefix separator char
93                // Cannot underflow as the `prefix.len` is checked
94                #[allow(clippy::arithmetic_side_effects)]
95                let width = CHAIN_ID_LENGTH - 1 - prefix.len();
96                // lowercase hex of the first `width` chars of the hash
97                let expected_hash =
98                    format!("{:.width$x}", hasher.finalize(), width = width,);
99                if hash != expected_hash {
100                    errors.push(ChainIdValidationError::InvalidHash(
101                        expected_hash,
102                        hash.to_string(),
103                    ));
104                }
105            }
106            None => {
107                errors.push(ChainIdValidationError::MissingSeparator);
108            }
109        }
110        errors
111    }
112
113    /// Find the prefix of a valid ChainId.
114    pub fn prefix(&self) -> Option<ChainIdPrefix> {
115        let ChainId(chain_id) = self;
116        let (prefix, _) = chain_id.rsplit_once(CHAIN_ID_PREFIX_SEP)?;
117        Some(ChainIdPrefix(prefix.to_string()))
118    }
119}
120
121/// Height of a block, i.e. the level. The `default` is the
122/// [`BlockHeight::sentinel`] value, which doesn't correspond to any block.
123#[derive(
124    Clone,
125    Copy,
126    BorshSerialize,
127    BorshDeserialize,
128    BorshDeserializer,
129    BorshSchema,
130    PartialEq,
131    Eq,
132    PartialOrd,
133    Ord,
134    Hash,
135    Debug,
136    Serialize,
137    Deserialize,
138)]
139pub struct BlockHeight(pub u64);
140
141impl Default for BlockHeight {
142    fn default() -> Self {
143        Self::sentinel()
144    }
145}
146
147impl Display for BlockHeight {
148    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149        write!(f, "{}", self.0)
150    }
151}
152
153impl From<BlockHeight> for u64 {
154    fn from(height: BlockHeight) -> Self {
155        height.0
156    }
157}
158
159impl FromStr for BlockHeight {
160    type Err = ParseIntError;
161
162    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
163        Ok(Self(s.parse::<u64>()?))
164    }
165}
166
167/// Hash of a block as fixed-size byte array
168#[derive(
169    Clone,
170    Default,
171    BorshSerialize,
172    BorshDeserialize,
173    BorshDeserializer,
174    PartialEq,
175    Eq,
176    PartialOrd,
177    Ord,
178    Hash,
179    Serialize,
180    Deserialize,
181)]
182pub struct BlockHash(pub [u8; BLOCK_HASH_LENGTH]);
183
184impl Display for BlockHash {
185    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186        write!(f, "{}", HEXUPPER.encode(&self.0))
187    }
188}
189
190impl From<Hash> for BlockHash {
191    fn from(hash: Hash) -> Self {
192        BlockHash(hash.0)
193    }
194}
195
196impl From<u64> for BlockHeight {
197    fn from(height: u64) -> Self {
198        BlockHeight(height)
199    }
200}
201
202impl From<tendermint::block::Height> for BlockHeight {
203    fn from(height: tendermint::block::Height) -> Self {
204        Self(u64::from(height))
205    }
206}
207
208impl TryFrom<BlockHeight> for tendermint::block::Height {
209    type Error = tendermint::Error;
210
211    fn try_from(height: BlockHeight) -> std::result::Result<Self, Self::Error> {
212        Self::try_from(height.0)
213    }
214}
215
216impl TryFrom<i64> for BlockHeight {
217    type Error = String;
218
219    fn try_from(value: i64) -> std::result::Result<Self, Self::Error> {
220        value
221            .try_into()
222            .map(BlockHeight)
223            .map_err(|e| format!("Unexpected height value {}, {}", value, e))
224    }
225}
226
227impl BlockHeight {
228    /// The first block height 1.
229    pub const fn first() -> Self {
230        Self(1)
231    }
232
233    /// A sentinel value block height 0 may be used before any block is
234    /// committed or in queries to read from the latest committed block.
235    pub const fn sentinel() -> Self {
236        Self(0)
237    }
238
239    /// Get the height of the next block
240    pub fn next_height(&self) -> BlockHeight {
241        BlockHeight(
242            self.0
243                .checked_add(1)
244                .expect("Block height must not overflow"),
245        )
246    }
247
248    /// Get the height of the previous block
249    pub fn prev_height(&self) -> Option<BlockHeight> {
250        Some(BlockHeight(self.0.checked_sub(1)?))
251    }
252
253    /// Checked block height addition.
254    #[must_use = "this returns the result of the operation, without modifying \
255                  the original"]
256    pub fn checked_add(self, rhs: impl Into<BlockHeight>) -> Option<Self> {
257        let BlockHeight(rhs) = rhs.into();
258        Some(Self(self.0.checked_add(rhs)?))
259    }
260
261    /// Checked block height subtraction.
262    #[must_use = "this returns the result of the operation, without modifying \
263                  the original"]
264    pub fn checked_sub(self, rhs: impl Into<BlockHeight>) -> Option<Self> {
265        let BlockHeight(rhs) = rhs.into();
266        Some(Self(self.0.checked_sub(rhs)?))
267    }
268}
269
270impl TryFrom<&[u8]> for BlockHash {
271    type Error = ParseBlockHashError;
272
273    fn try_from(value: &[u8]) -> Result<Self, ParseBlockHashError> {
274        if value.len() != BLOCK_HASH_LENGTH {
275            return Err(ParseBlockHashError::ParseBlockHash(format!(
276                "Unexpected block hash length {}, expected {}",
277                value.len(),
278                BLOCK_HASH_LENGTH
279            )));
280        }
281        let mut hash = [0; 32];
282        hash.copy_from_slice(value);
283        Ok(BlockHash(hash))
284    }
285}
286
287#[allow(missing_docs)]
288#[derive(Error, Debug)]
289pub enum ParseBlockHashError {
290    #[error("Error parsing block hash: {0}")]
291    ParseBlockHash(String),
292}
293
294impl core::fmt::Debug for BlockHash {
295    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
296        let hash = HEXLOWER.encode(&self.0);
297        f.debug_tuple("BlockHash").field(&hash).finish()
298    }
299}
300
301/// Epoch identifier. Epochs are identified by consecutive numbers.
302#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
303#[derive(
304    Clone,
305    Copy,
306    Default,
307    Debug,
308    PartialEq,
309    Eq,
310    PartialOrd,
311    Ord,
312    Hash,
313    BorshSerialize,
314    BorshDeserialize,
315    BorshDeserializer,
316    BorshSchema,
317    Serialize,
318    Deserialize,
319)]
320pub struct Epoch(pub u64);
321
322impl Display for Epoch {
323    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
324        write!(f, "{}", self.0)
325    }
326}
327
328impl FromStr for Epoch {
329    type Err = ParseIntError;
330
331    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
332        let raw: u64 = u64::from_str(s)?;
333        Ok(Self(raw))
334    }
335}
336
337impl Epoch {
338    /// Change to the next epoch
339    pub fn next(&self) -> Self {
340        Self(self.0.checked_add(1).expect("Epoch shouldn't overflow"))
341    }
342
343    /// Change to the previous epoch.
344    pub fn prev(&self) -> Option<Self> {
345        Some(Self(self.0.checked_sub(1)?))
346    }
347
348    /// Iterate a range of consecutive epochs starting from `self` of a given
349    /// length. Work-around for `Step` implementation pending on stabilization of <https://github.com/rust-lang/rust/issues/42168>.
350    pub fn iter_range(self, len: u64) -> impl Iterator<Item = Epoch> + Clone {
351        let start_ix: u64 = self.into();
352        let end_ix: u64 = start_ix.checked_add(len).unwrap_or(u64::MAX);
353        (start_ix..end_ix).map(Epoch::from)
354    }
355
356    /// Iterate a range of epochs, inclusive of the start and end.
357    pub fn iter_bounds_inclusive(
358        start: Self,
359        end: Self,
360    ) -> impl DoubleEndedIterator<Item = Epoch> + Clone {
361        let start_ix = start.0;
362        let end_ix = end.0;
363        (start_ix..=end_ix).map(Epoch::from)
364    }
365
366    /// Checked epoch addition.
367    #[must_use = "this returns the result of the operation, without modifying \
368                  the original"]
369    pub fn checked_add(self, rhs: impl Into<Epoch>) -> Option<Self> {
370        let Epoch(rhs) = rhs.into();
371        Some(Self(self.0.checked_add(rhs)?))
372    }
373
374    /// Unchecked epoch addition.
375    ///
376    /// # Panic
377    ///
378    /// Panics on overflow. Care must be taken to only use this with trusted
379    /// values that are known to be in a limited range (e.g. system parameters
380    /// but not e.g. transaction variables).
381    pub fn unchecked_add(self, rhs: impl Into<Epoch>) -> Self {
382        self.checked_add(rhs)
383            .expect("Epoch addition shouldn't overflow")
384    }
385
386    /// Checked epoch subtraction. Computes self - rhs, returning None if
387    /// overflow occurred.
388    #[must_use = "this returns the result of the operation, without modifying \
389                  the original"]
390    pub fn checked_sub(self, rhs: impl Into<Epoch>) -> Option<Self> {
391        let Epoch(rhs) = rhs.into();
392        Some(Self(self.0.checked_sub(rhs)?))
393    }
394
395    /// Checked epoch division.
396    #[must_use = "this returns the result of the operation, without modifying \
397                  the original"]
398    pub fn checked_div(self, rhs: impl Into<Epoch>) -> Option<Self> {
399        let Epoch(rhs) = rhs.into();
400        Some(Self(self.0.checked_div(rhs)?))
401    }
402
403    /// Checked epoch multiplication.
404    #[must_use = "this returns the result of the operation, without modifying \
405                  the original"]
406    pub fn checked_mul(self, rhs: impl Into<Epoch>) -> Option<Self> {
407        let Epoch(rhs) = rhs.into();
408        Some(Self(self.0.checked_mul(rhs)?))
409    }
410
411    /// Checked epoch integral reminder.
412    #[must_use = "this returns the result of the operation, without modifying \
413                  the original"]
414    pub fn checked_rem(self, rhs: impl Into<Epoch>) -> Option<Self> {
415        let Epoch(rhs) = rhs.into();
416        Some(Self(self.0.checked_rem(rhs)?))
417    }
418
419    /// Checked epoch subtraction. Computes self - rhs, returning default
420    /// `Epoch(0)` if overflow occurred.
421    #[must_use = "this returns the result of the operation, without modifying \
422                  the original"]
423    pub fn saturating_sub(self, rhs: Epoch) -> Self {
424        self.checked_sub(rhs).unwrap_or_default()
425    }
426}
427
428impl From<u64> for Epoch {
429    fn from(epoch: u64) -> Self {
430        Epoch(epoch)
431    }
432}
433
434impl From<Epoch> for u64 {
435    fn from(epoch: Epoch) -> Self {
436        epoch.0
437    }
438}
439
440/// Predecessor block epochs
441#[derive(
442    Clone,
443    Debug,
444    Default,
445    PartialEq,
446    Eq,
447    PartialOrd,
448    Ord,
449    Hash,
450    BorshSerialize,
451    BorshDeserialize,
452    BorshDeserializer,
453)]
454pub struct Epochs {
455    /// The block heights of the first block of each known epoch.
456    /// Invariant: the values must be sorted in ascending order.
457    pub first_block_heights: Vec<BlockHeight>,
458}
459
460impl Epochs {
461    /// Record start of a new epoch at the given block height
462    pub fn new_epoch(&mut self, block_height: BlockHeight) {
463        self.first_block_heights.push(block_height);
464    }
465
466    /// Look up the epoch of a given block height. If the given height is
467    /// greater than the current height, the current epoch will be returned even
468    /// though an epoch for a future block cannot be determined.
469    pub fn get_epoch(&self, block_height: BlockHeight) -> Option<Epoch> {
470        if let Some((_first_known_epoch_height, rest)) =
471            self.first_block_heights.split_first()
472        {
473            let mut epoch = Epoch::default();
474            for next_block_height in rest {
475                if block_height < *next_block_height {
476                    return Some(epoch);
477                } else {
478                    epoch = epoch.next();
479                }
480            }
481            return Some(epoch);
482        }
483        None
484    }
485
486    /// Look up the starting block height of an epoch at or before a given
487    /// height.
488    pub fn get_epoch_start_height(
489        &self,
490        height: BlockHeight,
491    ) -> Option<BlockHeight> {
492        for start_height in self.first_block_heights.iter().rev() {
493            if *start_height <= height {
494                return Some(*start_height);
495            }
496        }
497        None
498    }
499
500    /// Look up the starting block height of the given epoch
501    pub fn get_start_height_of_epoch(
502        &self,
503        epoch: Epoch,
504    ) -> Option<BlockHeight> {
505        if epoch.0 > self.first_block_heights.len() as u64 {
506            return None;
507        }
508        let idx = usize::try_from(epoch.0).ok()?;
509        self.first_block_heights.get(idx).copied()
510    }
511
512    /// Return all starting block heights for each successive Epoch.
513    ///
514    /// __INVARIANT:__ The returned values are sorted in ascending order.
515    pub fn first_block_heights(&self) -> &[BlockHeight] {
516        &self.first_block_heights
517    }
518}
519
520/// The block header data from Tendermint header relevant for Namada storage
521#[derive(
522    Clone, Debug, BorshSerialize, BorshDeserialize, BorshDeserializer, Default,
523)]
524pub struct BlockHeader {
525    /// Merkle root hash of block
526    pub hash: Hash,
527    /// Timestamp associated to block
528    pub time: DateTimeUtc,
529    /// Hash of the addresses of the next validator set
530    pub next_validators_hash: Hash,
531}
532
533impl BlockHeader {
534    /// The number of bytes when this header is encoded
535    pub const fn encoded_len() -> usize {
536        // checked in `test_block_header_encoded_len`
537        103
538    }
539}
540
541#[allow(missing_docs)]
542#[derive(Debug, Error)]
543pub enum ChainIdValidationError {
544    #[error(
545        "The prefix separator character '{CHAIN_ID_PREFIX_SEP}' is missing"
546    )]
547    MissingSeparator,
548    #[error("The chain ID hash is not valid, expected {0}, got {1}")]
549    InvalidHash(String, String),
550    #[error("Invalid prefix {0}")]
551    Prefix(ChainIdPrefixParseError),
552}
553
554impl Default for ChainId {
555    fn default() -> Self {
556        Self(DEFAULT_CHAIN_ID.to_string())
557    }
558}
559
560impl fmt::Display for ChainId {
561    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
562        write!(f, "{}", self.0)
563    }
564}
565
566#[allow(missing_docs)]
567#[derive(Debug, Error)]
568pub enum ChainIdParseError {
569    #[error("Chain ID must be {CHAIN_ID_LENGTH} long, got {0}")]
570    UnexpectedLen(usize),
571    #[error(
572        "The chain ID contains forbidden characters: {0:?}. Only alphanumeric \
573         characters and `-`, `_` and `.` are allowed."
574    )]
575    ForbiddenCharacters(Vec<char>),
576}
577
578impl FromStr for ChainId {
579    type Err = ChainIdParseError;
580
581    fn from_str(s: &str) -> Result<Self, Self::Err> {
582        let len = s.len();
583        if len != CHAIN_ID_LENGTH {
584            return Err(ChainIdParseError::UnexpectedLen(len));
585        }
586        let mut forbidden_chars = s
587            .chars()
588            .filter(|char| {
589                !matches!(*char as u8, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'-' | b'_' | b'.')
590            })
591            .peekable();
592        if forbidden_chars.peek().is_some() {
593            return Err(ChainIdParseError::ForbiddenCharacters(
594                forbidden_chars.collect(),
595            ));
596        }
597        Ok(Self(s.to_owned()))
598    }
599}
600
601/// Chain ID prefix
602#[derive(
603    Debug,
604    Clone,
605    Serialize,
606    Deserialize,
607    BorshSerialize,
608    BorshDeserialize,
609    BorshDeserializer,
610)]
611#[serde(transparent)]
612pub struct ChainIdPrefix(String);
613
614impl fmt::Display for ChainIdPrefix {
615    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
616        write!(f, "{}", self.0)
617    }
618}
619
620impl ChainIdPrefix {
621    /// Extracts a string slice containing the entire chain ID prefix.
622    pub fn as_str(&self) -> &str {
623        &self.0
624    }
625
626    /// Return a temporary chain ID made only from the prefix. This is not a
627    /// valid chain ID and is only to be used temporarily in a network setup.
628    pub fn temp_chain_id(&self) -> ChainId {
629        ChainId(self.0.clone())
630    }
631}
632
633#[allow(missing_docs)]
634#[derive(Debug, Error)]
635pub enum ChainIdPrefixParseError {
636    #[error(
637        "Chain ID prefix must at least 1 and up to {CHAIN_ID_PREFIX_MAX_LEN} \
638         characters long, got {0}"
639    )]
640    UnexpectedLen(usize),
641    #[error(
642        "The prefix contains forbidden characters: {0:?}. Only alphanumeric \
643         characters and `-`, `_` and `.` are allowed."
644    )]
645    ForbiddenCharacters(Vec<char>),
646}
647
648impl FromStr for ChainIdPrefix {
649    type Err = ChainIdPrefixParseError;
650
651    fn from_str(s: &str) -> Result<Self, Self::Err> {
652        let len = s.len();
653        if !(1..=CHAIN_ID_PREFIX_MAX_LEN).contains(&len) {
654            return Err(ChainIdPrefixParseError::UnexpectedLen(len));
655        }
656        let mut forbidden_chars = s
657            .chars()
658            .filter(|char| {
659                !matches!(*char as u8, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'-' | b'_' | b'.')
660            })
661            .peekable();
662        if forbidden_chars.peek().is_some() {
663            return Err(ChainIdPrefixParseError::ForbiddenCharacters(
664                forbidden_chars.collect(),
665            ));
666        }
667        Ok(Self(s.to_owned()))
668    }
669}
670
671/// Helpers for testing with storage types.
672#[cfg(any(test, feature = "testing"))]
673pub mod testing {
674    use std::ops::{Add, AddAssign, Sub};
675
676    use proptest::prelude::*;
677
678    use super::*;
679    use crate::time::DateTimeUtc;
680
681    impl<T> Add<T> for BlockHeight
682    where
683        T: Into<BlockHeight>,
684    {
685        type Output = BlockHeight;
686
687        fn add(self, rhs: T) -> Self::Output {
688            self.checked_add(rhs.into()).unwrap()
689        }
690    }
691
692    impl<T> AddAssign<T> for BlockHeight
693    where
694        T: Into<BlockHeight>,
695    {
696        fn add_assign(&mut self, rhs: T) {
697            *self = self.checked_add(rhs.into()).unwrap()
698        }
699    }
700
701    impl<T> Add<T> for Epoch
702    where
703        T: Into<Epoch>,
704    {
705        type Output = Epoch;
706
707        fn add(self, rhs: T) -> Self::Output {
708            self.checked_add(rhs.into()).unwrap()
709        }
710    }
711
712    impl<T> Sub<T> for Epoch
713    where
714        T: Into<Epoch>,
715    {
716        type Output = Epoch;
717
718        fn sub(self, rhs: T) -> Self::Output {
719            self.checked_sub(rhs.into()).unwrap()
720        }
721    }
722
723    prop_compose! {
724        /// Generate an arbitrary epoch
725        pub fn arb_epoch()(epoch: u64) -> Epoch {
726            Epoch(epoch)
727        }
728    }
729
730    /// A dummy header used for testing
731    pub fn get_dummy_header() -> BlockHeader {
732        use crate::time::DurationSecs;
733        BlockHeader {
734            hash: Hash([0; 32]),
735            #[allow(
736                clippy::disallowed_methods,
737                clippy::arithmetic_side_effects
738            )]
739            time: DateTimeUtc::now() + DurationSecs(5),
740            next_validators_hash: Hash([0; 32]),
741        }
742    }
743}
744
745#[cfg(test)]
746mod tests {
747    use proptest::prelude::*;
748
749    use super::*;
750    use crate::borsh::BorshSerializeExt;
751
752    proptest! {
753        /// Test any chain ID that is generated via `from_genesis` function is valid.
754        #[test]
755        fn test_any_generated_chain_id_is_valid(
756            prefix in proptest::string::string_regex(r#"[A-Za-z0-9\.\-_]{1,19}"#).unwrap(),
757            genesis_bytes in any::<Vec<u8>>(),
758        ) {
759            let chain_id_prefix = ChainIdPrefix::from_str(&prefix).unwrap();
760            let chain_id = ChainId::from_genesis(chain_id_prefix, &genesis_bytes);
761            // There should be no validation errors
762            let errors = chain_id.validate(&genesis_bytes);
763            assert!(errors.is_empty(), "There should be no validation errors {:#?}", errors);
764        }
765    }
766
767    #[test]
768    fn test_predecessor_epochs_and_heights() {
769        let mut epochs = Epochs {
770            first_block_heights: vec![BlockHeight::first()],
771        };
772        println!("epochs {:#?}", epochs);
773        assert_eq!(
774            epochs.get_start_height_of_epoch(Epoch(0)),
775            Some(BlockHeight(1))
776        );
777        assert_eq!(epochs.get_epoch(BlockHeight(0)), Some(Epoch(0)));
778
779        // epoch 1
780        epochs.new_epoch(BlockHeight(10));
781        println!("epochs {:#?}", epochs);
782        assert_eq!(
783            epochs.get_start_height_of_epoch(Epoch(1)),
784            Some(BlockHeight(10))
785        );
786        assert_eq!(epochs.get_epoch(BlockHeight(0)), Some(Epoch(0)));
787        assert_eq!(epochs.get_epoch_start_height(BlockHeight(0)), None);
788        assert_eq!(
789            epochs.get_epoch_start_height(BlockHeight(1)),
790            Some(BlockHeight(1))
791        );
792        assert_eq!(epochs.get_epoch(BlockHeight(9)), Some(Epoch(0)));
793        assert_eq!(
794            epochs.get_epoch_start_height(BlockHeight(9)),
795            Some(BlockHeight(1))
796        );
797        assert_eq!(epochs.get_epoch(BlockHeight(10)), Some(Epoch(1)));
798        assert_eq!(
799            epochs.get_epoch_start_height(BlockHeight(10)),
800            Some(BlockHeight(10))
801        );
802        assert_eq!(epochs.get_epoch(BlockHeight(11)), Some(Epoch(1)));
803        assert_eq!(
804            epochs.get_epoch_start_height(BlockHeight(11)),
805            Some(BlockHeight(10))
806        );
807        assert_eq!(epochs.get_epoch(BlockHeight(100)), Some(Epoch(1)));
808        assert_eq!(
809            epochs.get_epoch_start_height(BlockHeight(100)),
810            Some(BlockHeight(10))
811        );
812
813        // epoch 2
814        epochs.new_epoch(BlockHeight(20));
815        println!("epochs {:#?}", epochs);
816        assert_eq!(
817            epochs.get_start_height_of_epoch(Epoch(2)),
818            Some(BlockHeight(20))
819        );
820        assert_eq!(epochs.get_epoch(BlockHeight(0)), Some(Epoch(0)));
821        assert_eq!(epochs.get_epoch(BlockHeight(9)), Some(Epoch(0)));
822        assert_eq!(epochs.get_epoch(BlockHeight(10)), Some(Epoch(1)));
823        assert_eq!(epochs.get_epoch(BlockHeight(11)), Some(Epoch(1)));
824        assert_eq!(
825            epochs.get_epoch_start_height(BlockHeight(11)),
826            Some(BlockHeight(10))
827        );
828        assert_eq!(epochs.get_epoch(BlockHeight(20)), Some(Epoch(2)));
829        assert_eq!(
830            epochs.get_epoch_start_height(BlockHeight(20)),
831            Some(BlockHeight(20))
832        );
833        assert_eq!(epochs.get_epoch(BlockHeight(100)), Some(Epoch(2)));
834        assert_eq!(
835            epochs.get_epoch_start_height(BlockHeight(100)),
836            Some(BlockHeight(20))
837        );
838
839        // epoch 3
840        epochs.new_epoch(BlockHeight(200));
841        println!("epochs {:#?}", epochs);
842        assert_eq!(
843            epochs.get_start_height_of_epoch(Epoch(3)),
844            Some(BlockHeight(200))
845        );
846        assert_eq!(epochs.get_epoch(BlockHeight(0)), Some(Epoch(0)));
847        assert_eq!(epochs.get_epoch(BlockHeight(9)), Some(Epoch(0)));
848        assert_eq!(epochs.get_epoch(BlockHeight(10)), Some(Epoch(1)));
849        assert_eq!(epochs.get_epoch(BlockHeight(11)), Some(Epoch(1)));
850        assert_eq!(epochs.get_epoch(BlockHeight(20)), Some(Epoch(2)));
851        assert_eq!(epochs.get_epoch(BlockHeight(100)), Some(Epoch(2)));
852        assert_eq!(
853            epochs.get_epoch_start_height(BlockHeight(100)),
854            Some(BlockHeight(20))
855        );
856        assert_eq!(epochs.get_epoch(BlockHeight(200)), Some(Epoch(3)));
857        assert_eq!(
858            epochs.get_epoch_start_height(BlockHeight(200)),
859            Some(BlockHeight(200))
860        );
861
862        // epoch 4
863        epochs.new_epoch(BlockHeight(300));
864        println!("epochs {:#?}", epochs);
865        assert_eq!(
866            epochs.get_start_height_of_epoch(Epoch(4)),
867            Some(BlockHeight(300))
868        );
869        assert_eq!(epochs.get_epoch(BlockHeight(20)), Some(Epoch(2)));
870        assert_eq!(epochs.get_epoch(BlockHeight(100)), Some(Epoch(2)));
871        assert_eq!(epochs.get_epoch(BlockHeight(200)), Some(Epoch(3)));
872        assert_eq!(epochs.get_epoch(BlockHeight(300)), Some(Epoch(4)));
873
874        // epoch 5
875        epochs.new_epoch(BlockHeight(499));
876        println!("epochs {:#?}", epochs);
877        assert_eq!(
878            epochs.get_start_height_of_epoch(Epoch(5)),
879            Some(BlockHeight(499))
880        );
881        assert_eq!(epochs.get_epoch(BlockHeight(20)), Some(Epoch(2)));
882        assert_eq!(epochs.get_epoch(BlockHeight(100)), Some(Epoch(2)));
883        assert_eq!(epochs.get_epoch(BlockHeight(200)), Some(Epoch(3)));
884        assert_eq!(epochs.get_epoch(BlockHeight(300)), Some(Epoch(4)));
885        assert_eq!(epochs.get_epoch(BlockHeight(499)), Some(Epoch(5)));
886
887        // epoch 6
888        epochs.new_epoch(BlockHeight(500));
889        println!("epochs {:#?}", epochs);
890        assert_eq!(
891            epochs.get_start_height_of_epoch(Epoch(6)),
892            Some(BlockHeight(500))
893        );
894        assert_eq!(epochs.get_epoch(BlockHeight(200)), Some(Epoch(3)));
895        assert_eq!(epochs.get_epoch(BlockHeight(300)), Some(Epoch(4)));
896        assert_eq!(epochs.get_epoch(BlockHeight(499)), Some(Epoch(5)));
897        assert_eq!(epochs.get_epoch(BlockHeight(500)), Some(Epoch(6)));
898
899        // epoch 7
900        epochs.new_epoch(BlockHeight(550));
901        println!("epochs {:#?}", epochs);
902        assert_eq!(
903            epochs.get_start_height_of_epoch(Epoch(7)),
904            Some(BlockHeight(550))
905        );
906        assert_eq!(epochs.get_epoch(BlockHeight(300)), Some(Epoch(4)));
907        assert_eq!(epochs.get_epoch(BlockHeight(499)), Some(Epoch(5)));
908        assert_eq!(epochs.get_epoch(BlockHeight(500)), Some(Epoch(6)));
909        assert_eq!(epochs.get_epoch(BlockHeight(550)), Some(Epoch(7)));
910
911        // epoch 8
912        epochs.new_epoch(BlockHeight(600));
913        println!("epochs {:#?}", epochs);
914        assert_eq!(
915            epochs.get_start_height_of_epoch(Epoch(7)),
916            Some(BlockHeight(550))
917        );
918        assert_eq!(
919            epochs.get_start_height_of_epoch(Epoch(8)),
920            Some(BlockHeight(600))
921        );
922        assert_eq!(epochs.get_epoch(BlockHeight(500)), Some(Epoch(6)));
923        assert_eq!(epochs.get_epoch(BlockHeight(550)), Some(Epoch(7)));
924        assert_eq!(epochs.get_epoch(BlockHeight(600)), Some(Epoch(8)));
925
926        // try to fetch height values out of range
927        // at this point, the min known epoch is 7
928        for e in [9, 10, 11, 12] {
929            assert!(
930                epochs.get_start_height_of_epoch(Epoch(e)).is_none(),
931                "Epoch: {e}"
932            );
933        }
934    }
935
936    #[test]
937    fn test_block_header_encoded_len() {
938        #[allow(clippy::disallowed_methods)]
939        let header = BlockHeader {
940            hash: Hash::zero(),
941            time: DateTimeUtc::now(),
942            next_validators_hash: Hash::zero(),
943        };
944        let len = header.serialize_to_vec().len();
945        assert_eq!(len, BlockHeader::encoded_len())
946    }
947}