use crate::serialization::SerializationError; use std::{ convert::TryFrom, ops::{Add, Sub}, }; /// The height of a block is the length of the chain back to the genesis block. /// /// Block heights can't be added, but they can be *subtracted*, /// to get a difference of block heights, represented as an `i32`, /// and height differences can be added to block heights to get new heights. /// /// # Invariants /// /// Users should not construct block heights greater than `Height::MAX`. #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct Height(pub u32); impl std::str::FromStr for Height { type Err = SerializationError; fn from_str(s: &str) -> Result { match s.parse() { Ok(h) if (Height(h) <= Height::MAX) => Ok(Height(h)), Ok(_) => Err(SerializationError::Parse("Height exceeds maximum height")), Err(_) => Err(SerializationError::Parse("Height(u32) integer parse error")), } } } impl Height { /// The minimum Height. /// /// Due to the underlying type, it is impossible to construct block heights /// less than `Height::MIN`. /// /// Style note: Sometimes, `Height::MIN` is less readable than /// `Height(0)`. Use whichever makes sense in context. pub const MIN: Height = Height(0); /// The maximum Height. /// /// Users should not construct block heights greater than `Height::MAX`. pub const MAX: Height = Height(499_999_999); /// The maximum Height as a u32, for range patterns. /// /// `Height::MAX.0` can't be used in match range patterns, use this /// alias instead. pub const MAX_AS_U32: u32 = Self::MAX.0; } impl Add for Height { type Output = Option; fn add(self, rhs: Height) -> Option { // We know that both values are positive integers. Therefore, the result is // positive, and we can skip the conversions. The checked_add is required, // because the result may overflow. let height = self.0.checked_add(rhs.0)?; let height = Height(height); if height <= Height::MAX && height >= Height::MIN { Some(height) } else { None } } } impl Sub for Height { type Output = i32; /// Panics if the inputs or result are outside the valid i32 range. fn sub(self, rhs: Height) -> i32 { // We construct heights from integers without any checks, // so the inputs or result could be out of range. let lhs = i32::try_from(self.0) .expect("out of range input `self`: inputs should be valid Heights"); let rhs = i32::try_from(rhs.0).expect("out of range input `rhs`: inputs should be valid Heights"); lhs.checked_sub(rhs) .expect("out of range result: valid input heights should yield a valid result") } } // We don't implement Add or Sub, because they cause type inference issues for integer constants. impl Add for Height { type Output = Option; fn add(self, rhs: i32) -> Option { // Because we construct heights from integers without any checks, // the input values could be outside the valid range for i32. let lhs = i32::try_from(self.0).ok()?; let result = lhs.checked_add(rhs)?; let result = u32::try_from(result).ok()?; match result { h if (Height(h) <= Height::MAX && Height(h) >= Height::MIN) => Some(Height(h)), _ => None, } } } impl Sub for Height { type Output = Option; fn sub(self, rhs: i32) -> Option { // These checks are required, see above for details. let lhs = i32::try_from(self.0).ok()?; let result = lhs.checked_sub(rhs)?; let result = u32::try_from(result).ok()?; match result { h if (Height(h) <= Height::MAX && Height(h) >= Height::MIN) => Some(Height(h)), _ => None, } } } #[cfg(any(test, feature = "proptest-impl"))] use proptest::prelude::*; #[cfg(any(test, feature = "proptest-impl"))] impl Arbitrary for Height { type Parameters = (); fn arbitrary_with(_args: ()) -> Self::Strategy { (Height::MIN.0..=Height::MAX.0).prop_map(Height).boxed() } type Strategy = BoxedStrategy; } #[test] fn operator_tests() { assert_eq!(Some(Height(2)), Height(1) + Height(1)); assert_eq!(None, Height::MAX + Height(1)); // Bad heights aren't caught at compile-time or runtime, until we add or subtract assert_eq!(None, Height(Height::MAX_AS_U32 + 1) + Height(0)); assert_eq!(None, Height(i32::MAX as u32) + Height(0)); assert_eq!(None, Height(u32::MAX) + Height(0)); assert_eq!(Some(Height(2)), Height(1) + 1); assert_eq!(None, Height::MAX + 1); // Adding negative numbers assert_eq!(Some(Height(1)), Height(2) + -1); assert_eq!(Some(Height(0)), Height(1) + -1); assert_eq!(None, Height(0) + -1); assert_eq!(Some(Height(Height::MAX_AS_U32 - 1)), Height::MAX + -1); // Bad heights aren't caught at compile-time or runtime, until we add or subtract // `+ 0` would also cause an error here, but it triggers a spurious clippy lint assert_eq!(None, Height(Height::MAX_AS_U32 + 1) + 1); assert_eq!(None, Height(i32::MAX as u32) + 1); assert_eq!(None, Height(u32::MAX) + 1); // Adding negative numbers assert_eq!(None, Height(i32::MAX as u32) + -1); assert_eq!(None, Height(u32::MAX) + -1); assert_eq!(Some(Height(1)), Height(2) - 1); assert_eq!(Some(Height(0)), Height(1) - 1); assert_eq!(None, Height(0) - 1); assert_eq!(Some(Height(Height::MAX_AS_U32 - 1)), Height::MAX - 1); // Subtracting negative numbers assert_eq!(Some(Height(2)), Height(1) - -1); assert_eq!(Some(Height::MAX), Height(Height::MAX_AS_U32 - 1) - -1); assert_eq!(None, Height::MAX - -1); // Bad heights aren't caught at compile-time or runtime, until we add or subtract assert_eq!(None, Height(i32::MAX as u32) - 1); assert_eq!(None, Height(u32::MAX) - 1); // Subtracting negative numbers assert_eq!(None, Height(Height::MAX_AS_U32 + 1) - -1); assert_eq!(None, Height(i32::MAX as u32) - -1); assert_eq!(None, Height(u32::MAX) - -1); // Sub panics on out of range errors assert_eq!(1, Height(2) - Height(1)); assert_eq!(0, Height(1) - Height(1)); assert_eq!(-1, Height(0) - Height(1)); assert_eq!(-5, Height(2) - Height(7)); assert_eq!(Height::MAX_AS_U32 as i32, Height::MAX - Height(0)); assert_eq!(1, Height::MAX - Height(Height::MAX_AS_U32 - 1)); assert_eq!(-1, Height(Height::MAX_AS_U32 - 1) - Height::MAX); assert_eq!(-(Height::MAX_AS_U32 as i32), Height(0) - Height::MAX); }