use std::ops::{Add, Sub};
use thiserror::Error;
use zcash_primitives::consensus::BlockHeight;
use crate::{serialization::SerializationError, BoxError};
#[cfg(feature = "json-conversion")]
pub mod json_conversion;
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Default))]
pub struct Height(pub u32);
#[derive(Error, Debug)]
pub enum HeightError {
#[error("The resulting height would overflow Height::MAX.")]
Overflow,
#[error("The resulting height would underflow Height::MIN.")]
Underflow,
}
impl std::str::FromStr for Height {
type Err = SerializationError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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 {
pub const MIN: Height = Height(0);
pub const MAX: Height = Height(u32::MAX / 2);
pub const MAX_AS_U32: u32 = Self::MAX.0;
pub const MAX_EXPIRY_HEIGHT: Height = Height(499_999_999);
pub fn next(self) -> Result<Self, HeightError> {
(self + 1).ok_or(HeightError::Overflow)
}
pub fn previous(self) -> Result<Self, HeightError> {
(self - 1).ok_or(HeightError::Underflow)
}
pub fn is_min(self) -> bool {
self == Self::MIN
}
pub fn as_usize(self) -> usize {
self.0.try_into().expect("fits in usize")
}
}
impl From<Height> for BlockHeight {
fn from(height: Height) -> Self {
BlockHeight::from_u32(height.0)
}
}
impl TryFrom<BlockHeight> for Height {
type Error = &'static str;
fn try_from(height: BlockHeight) -> Result<Self, Self::Error> {
Self::try_from(u32::from(height))
}
}
pub type HeightDiff = i64;
impl TryFrom<u32> for Height {
type Error = &'static str;
fn try_from(height: u32) -> Result<Self, Self::Error> {
assert_eq!(Height::MIN.0, 0);
if height <= Height::MAX.0 {
Ok(Height(height))
} else {
Err("heights must be less than or equal to Height::MAX")
}
}
}
impl From<Height> for u32 {
fn from(height: Height) -> Self {
height.0
}
}
impl From<Height> for u64 {
fn from(height: Height) -> Self {
height.0.into()
}
}
pub trait TryIntoHeight {
type Error;
fn try_into_height(&self) -> Result<Height, Self::Error>;
}
impl TryIntoHeight for u64 {
type Error = BoxError;
fn try_into_height(&self) -> Result<Height, Self::Error> {
u32::try_from(*self)?.try_into().map_err(Into::into)
}
}
impl TryIntoHeight for usize {
type Error = BoxError;
fn try_into_height(&self) -> Result<Height, Self::Error> {
u32::try_from(*self)?.try_into().map_err(Into::into)
}
}
impl TryIntoHeight for str {
type Error = BoxError;
fn try_into_height(&self) -> Result<Height, Self::Error> {
self.parse().map_err(Into::into)
}
}
impl TryIntoHeight for String {
type Error = BoxError;
fn try_into_height(&self) -> Result<Height, Self::Error> {
self.as_str().try_into_height()
}
}
impl TryIntoHeight for i32 {
type Error = BoxError;
fn try_into_height(&self) -> Result<Height, Self::Error> {
u32::try_from(*self)?.try_into().map_err(Into::into)
}
}
impl Sub<Height> for Height {
type Output = HeightDiff;
fn sub(self, rhs: Height) -> Self::Output {
let lhs = HeightDiff::from(self.0);
let rhs = HeightDiff::from(rhs.0);
lhs - rhs
}
}
impl Sub<HeightDiff> for Height {
type Output = Option<Self>;
fn sub(self, rhs: HeightDiff) -> Option<Self> {
let lhs = HeightDiff::from(self.0);
let res = lhs - rhs;
let res = u32::try_from(res).ok()?;
Height::try_from(res).ok()
}
}
impl Add<HeightDiff> for Height {
type Output = Option<Height>;
fn add(self, rhs: HeightDiff) -> Option<Height> {
let lhs = i64::from(self.0);
let res = lhs + rhs;
let res = u32::try_from(res).ok()?;
Height::try_from(res).ok()
}
}
#[test]
fn operator_tests() {
let _init_guard = zebra_test::init();
assert_eq!(Some(Height(2)), Height(1) + 1);
assert_eq!(None, Height::MAX + 1);
let height = Height(u32::pow(2, 31) - 2);
assert!(height < Height::MAX);
let max_height = (height + 1).expect("this addition should produce the max height");
assert!(height < max_height);
assert!(max_height <= Height::MAX);
assert_eq!(Height::MAX, max_height);
assert_eq!(None, max_height + 1);
assert_eq!(None, Height(Height::MAX_AS_U32 + 1) + 0);
assert_eq!(None, Height(i32::MAX as u32) + 1);
assert_eq!(None, Height(u32::MAX) + 0);
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);
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);
assert_eq!(Some(Height::MAX), Height(i32::MAX as u32 + 1) + -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);
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);
assert_eq!(Some(Height::MAX), Height(i32::MAX as u32 + 1) - 1);
assert_eq!(None, Height(u32::MAX) - 1);
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);
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.0 as HeightDiff, (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 HeightDiff), Height(0) - Height::MAX);
}