1use 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
19pub const BLOCK_HASH_LENGTH: usize = 32;
21pub const BLOCK_HEIGHT_LENGTH: usize = 8;
23pub const CHAIN_ID_LENGTH: usize = 30;
25pub const CHAIN_ID_PREFIX_MAX_LEN: usize = 19;
27pub const CHAIN_ID_PREFIX_SEP: char = '.';
29
30pub const DEFAULT_CHAIN_ID: &str = "namada-internal.00000000000000";
32
33#[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 pub fn as_str(&self) -> &str {
56 &self.0
57 }
58
59 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 #[allow(clippy::arithmetic_side_effects)]
69 let width = CHAIN_ID_LENGTH - 1 - prefix.len();
70 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 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 #[allow(clippy::arithmetic_side_effects)]
95 let width = CHAIN_ID_LENGTH - 1 - prefix.len();
96 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 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#[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#[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 pub const fn first() -> Self {
230 Self(1)
231 }
232
233 pub const fn sentinel() -> Self {
236 Self(0)
237 }
238
239 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 pub fn prev_height(&self) -> Option<BlockHeight> {
250 Some(BlockHeight(self.0.checked_sub(1)?))
251 }
252
253 #[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 #[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#[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 pub fn next(&self) -> Self {
340 Self(self.0.checked_add(1).expect("Epoch shouldn't overflow"))
341 }
342
343 pub fn prev(&self) -> Option<Self> {
345 Some(Self(self.0.checked_sub(1)?))
346 }
347
348 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 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 #[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 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 #[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 #[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 #[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 #[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 #[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#[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 pub first_block_heights: Vec<BlockHeight>,
458}
459
460impl Epochs {
461 pub fn new_epoch(&mut self, block_height: BlockHeight) {
463 self.first_block_heights.push(block_height);
464 }
465
466 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 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 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 pub fn first_block_heights(&self) -> &[BlockHeight] {
516 &self.first_block_heights
517 }
518}
519
520#[derive(
522 Clone, Debug, BorshSerialize, BorshDeserialize, BorshDeserializer, Default,
523)]
524pub struct BlockHeader {
525 pub hash: Hash,
527 pub time: DateTimeUtc,
529 pub next_validators_hash: Hash,
531}
532
533impl BlockHeader {
534 pub const fn encoded_len() -> usize {
536 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#[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 pub fn as_str(&self) -> &str {
623 &self.0
624 }
625
626 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#[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 pub fn arb_epoch()(epoch: u64) -> Epoch {
726 Epoch(epoch)
727 }
728 }
729
730 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]
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 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 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 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 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 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 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 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 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 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 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}