Skip to main content

zebra_chain/block/
height.rs

1//! Block height.
2
3use std::ops::{Add, Sub};
4use thiserror::Error;
5use zcash_protocol::consensus::BlockHeight;
6
7use crate::{serialization::SerializationError, BoxError};
8
9#[cfg(feature = "json-conversion")]
10pub mod json_conversion;
11
12/// The length of the chain back to the genesis block.
13///
14/// Two [`Height`]s can't be added, but they can be *subtracted* to get their difference,
15/// represented as an [`HeightDiff`]. This difference can then be added to or subtracted from a
16/// [`Height`]. Note the similarity with `chrono::DateTime` and `chrono::Duration`.
17///
18/// # Invariants
19///
20/// Users should not construct block heights greater than `Height::MAX`.
21///
22/// # Consensus
23///
24/// There are multiple formats for serializing a height, so we don't implement
25/// `ZcashSerialize` or `ZcashDeserialize` for `Height`.
26#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
27#[cfg_attr(any(test, feature = "proptest-impl"), derive(Default))]
28pub struct Height(pub u32);
29
30#[derive(Error, Debug)]
31pub enum HeightError {
32    #[error("The resulting height would overflow Height::MAX.")]
33    Overflow,
34    #[error("The resulting height would underflow Height::MIN.")]
35    Underflow,
36}
37
38impl std::str::FromStr for Height {
39    type Err = SerializationError;
40    fn from_str(s: &str) -> Result<Self, Self::Err> {
41        match s.parse() {
42            Ok(h) if (Height(h) <= Height::MAX) => Ok(Height(h)),
43            Ok(_) => Err(SerializationError::Parse("Height exceeds maximum height")),
44            Err(_) => Err(SerializationError::Parse("Height(u32) integer parse error")),
45        }
46    }
47}
48
49impl Height {
50    /// The minimum [`Height`].
51    ///
52    /// Due to the underlying type, it is impossible to construct block heights
53    /// less than [`Height::MIN`].
54    ///
55    /// Style note: Sometimes, [`Height::MIN`] is less readable than
56    /// `Height(0)`. Use whichever makes sense in context.
57    pub const MIN: Height = Height(0);
58
59    /// The maximum [`Height`].
60    ///
61    /// Users should not construct block heights greater than [`Height::MAX`].
62    ///
63    /// The spec says *"Implementations MUST support block heights up to and
64    /// including 2^31 − 1"*.
65    ///
66    /// Note that `u32::MAX / 2 == 2^31 - 1 == i32::MAX`.
67    pub const MAX: Height = Height(u32::MAX / 2);
68
69    /// The maximum [`Height`] as a [`u32`], for range patterns.
70    ///
71    /// `Height::MAX.0` can't be used in match range patterns, use this
72    /// alias instead.
73    pub const MAX_AS_U32: u32 = Self::MAX.0;
74
75    /// The maximum expiration [`Height`] that is allowed in all transactions
76    /// previous to Nu5 and in non-coinbase transactions from Nu5 activation
77    /// height and above.
78    pub const MAX_EXPIRY_HEIGHT: Height = Height(499_999_999);
79
80    /// Returns the next [`Height`].
81    ///
82    /// # Panics
83    ///
84    /// - If the current height is at its maximum.
85    pub fn next(self) -> Result<Self, HeightError> {
86        (self + 1).ok_or(HeightError::Overflow)
87    }
88
89    /// Returns the previous [`Height`].
90    ///
91    /// # Panics
92    ///
93    /// - If the current height is at its minimum.
94    pub fn previous(self) -> Result<Self, HeightError> {
95        (self - 1).ok_or(HeightError::Underflow)
96    }
97
98    /// Returns `true` if the [`Height`] is at its minimum.
99    pub fn is_min(self) -> bool {
100        self == Self::MIN
101    }
102
103    /// Returns the value as a `usize`.
104    pub fn as_usize(self) -> usize {
105        self.0.try_into().expect("fits in usize")
106    }
107}
108
109impl From<Height> for BlockHeight {
110    fn from(height: Height) -> Self {
111        BlockHeight::from_u32(height.0)
112    }
113}
114
115impl TryFrom<BlockHeight> for Height {
116    type Error = &'static str;
117
118    /// Checks that the `height` is within the valid [`Height`] range.
119    fn try_from(height: BlockHeight) -> Result<Self, Self::Error> {
120        Self::try_from(u32::from(height))
121    }
122}
123
124/// A difference between two [`Height`]s, possibly negative.
125///
126/// This can represent the difference between any height values,
127/// even if they are outside the valid height range (for example, in buggy RPC code).
128pub type HeightDiff = i64;
129
130// We don't implement TryFrom<u64>, because it causes type inference issues for integer constants.
131// Instead, use 1u64.try_into_height().
132
133impl TryFrom<u32> for Height {
134    type Error = &'static str;
135
136    /// Checks that the `height` is within the valid [`Height`] range.
137    fn try_from(height: u32) -> Result<Self, Self::Error> {
138        // Check the bounds.
139        //
140        // Clippy warns that `height >= Height::MIN.0` is always true.
141        assert_eq!(Height::MIN.0, 0);
142
143        if height <= Height::MAX.0 {
144            Ok(Height(height))
145        } else {
146            Err("heights must be less than or equal to Height::MAX")
147        }
148    }
149}
150
151impl TryFrom<i64> for Height {
152    type Error = core::num::TryFromIntError;
153
154    fn try_from(height: i64) -> Result<Self, Self::Error> {
155        u32::try_from(height).map(Height)
156    }
157}
158
159impl From<Height> for u32 {
160    fn from(height: Height) -> Self {
161        height.0
162    }
163}
164
165impl From<Height> for u64 {
166    fn from(height: Height) -> Self {
167        height.0.into()
168    }
169}
170
171impl From<Height> for i64 {
172    fn from(height: Height) -> Self {
173        height.0.into()
174    }
175}
176
177impl From<&Height> for i64 {
178    fn from(height: &Height) -> Self {
179        height.0.into()
180    }
181}
182
183/// Convenience trait for converting a type into a valid Zcash [`Height`].
184pub trait TryIntoHeight {
185    /// The error type returned by [`Height`] conversion failures.
186    type Error;
187
188    /// Convert `self` to a `Height`, if possible.
189    fn try_into_height(&self) -> Result<Height, Self::Error>;
190}
191
192impl TryIntoHeight for u64 {
193    type Error = BoxError;
194
195    fn try_into_height(&self) -> Result<Height, Self::Error> {
196        u32::try_from(*self)?.try_into().map_err(Into::into)
197    }
198}
199
200impl TryIntoHeight for usize {
201    type Error = BoxError;
202
203    fn try_into_height(&self) -> Result<Height, Self::Error> {
204        u32::try_from(*self)?.try_into().map_err(Into::into)
205    }
206}
207
208impl TryIntoHeight for str {
209    type Error = BoxError;
210
211    fn try_into_height(&self) -> Result<Height, Self::Error> {
212        self.parse().map_err(Into::into)
213    }
214}
215
216impl TryIntoHeight for String {
217    type Error = BoxError;
218
219    fn try_into_height(&self) -> Result<Height, Self::Error> {
220        self.as_str().try_into_height()
221    }
222}
223
224impl TryIntoHeight for i32 {
225    type Error = BoxError;
226
227    fn try_into_height(&self) -> Result<Height, Self::Error> {
228        u32::try_from(*self)?.try_into().map_err(Into::into)
229    }
230}
231
232// We don't implement Add<u32> or Sub<u32>, because they cause type inference issues for integer constants.
233
234impl Sub<Height> for Height {
235    type Output = HeightDiff;
236
237    /// Subtract two heights, returning the result, which can be negative.
238    /// Since [`HeightDiff`] is `i64` and [`Height`] is `u32`, the result is always correct.
239    fn sub(self, rhs: Height) -> Self::Output {
240        // All these conversions are exact, and the subtraction can't overflow or underflow.
241        let lhs = HeightDiff::from(self.0);
242        let rhs = HeightDiff::from(rhs.0);
243
244        lhs - rhs
245    }
246}
247
248impl Sub<HeightDiff> for Height {
249    type Output = Option<Self>;
250
251    /// Subtract a height difference from a height, returning `None` if the resulting height is
252    /// outside the valid `Height` range (this also checks the result is non-negative).
253    fn sub(self, rhs: HeightDiff) -> Option<Self> {
254        // We need to convert the height to [`i64`] so we can subtract negative [`HeightDiff`]s.
255        let lhs = HeightDiff::from(self.0);
256        let res = lhs - rhs;
257
258        // Check the bounds.
259        let res = u32::try_from(res).ok()?;
260        Height::try_from(res).ok()
261    }
262}
263
264impl Add<HeightDiff> for Height {
265    type Output = Option<Height>;
266
267    /// Add a height difference to a height, returning `None` if the resulting height is outside
268    /// the valid `Height` range (this also checks the result is non-negative).
269    fn add(self, rhs: HeightDiff) -> Option<Height> {
270        // We need to convert the height to [`i64`] so we can add negative [`HeightDiff`]s.
271        let lhs = i64::from(self.0);
272        let res = lhs + rhs;
273
274        // Check the bounds.
275        let res = u32::try_from(res).ok()?;
276        Height::try_from(res).ok()
277    }
278}
279
280#[test]
281fn operator_tests() {
282    let _init_guard = zebra_test::init();
283
284    // Elementary checks.
285    assert_eq!(Some(Height(2)), Height(1) + 1);
286    assert_eq!(None, Height::MAX + 1);
287
288    let height = Height(u32::pow(2, 31) - 2);
289    assert!(height < Height::MAX);
290
291    let max_height = (height + 1).expect("this addition should produce the max height");
292    assert!(height < max_height);
293    assert!(max_height <= Height::MAX);
294    assert_eq!(Height::MAX, max_height);
295    assert_eq!(None, max_height + 1);
296
297    // Bad heights aren't caught at compile-time or runtime, until we add or subtract
298    assert_eq!(None, Height(Height::MAX_AS_U32 + 1) + 0);
299    assert_eq!(None, Height(i32::MAX as u32) + 1);
300    assert_eq!(None, Height(u32::MAX) + 0);
301
302    // Adding negative numbers
303    assert_eq!(Some(Height(1)), Height(2) + -1);
304    assert_eq!(Some(Height(0)), Height(1) + -1);
305    assert_eq!(None, Height(0) + -1);
306    assert_eq!(Some(Height(Height::MAX_AS_U32 - 1)), Height::MAX + -1);
307
308    // Bad heights aren't caught at compile-time or runtime, until we add or subtract,
309    // and the result is invalid
310    assert_eq!(None, Height(Height::MAX_AS_U32 + 1) + 1);
311    assert_eq!(None, Height(i32::MAX as u32) + 1);
312    assert_eq!(None, Height(u32::MAX) + 1);
313
314    // Adding negative numbers
315    assert_eq!(Some(Height::MAX), Height(i32::MAX as u32 + 1) + -1);
316    assert_eq!(None, Height(u32::MAX) + -1);
317
318    assert_eq!(Some(Height(1)), Height(2) - 1);
319    assert_eq!(Some(Height(0)), Height(1) - 1);
320    assert_eq!(None, Height(0) - 1);
321    assert_eq!(Some(Height(Height::MAX_AS_U32 - 1)), Height::MAX - 1);
322
323    // Subtracting negative numbers
324    assert_eq!(Some(Height(2)), Height(1) - -1);
325    assert_eq!(Some(Height::MAX), Height(Height::MAX_AS_U32 - 1) - -1);
326    assert_eq!(None, Height::MAX - -1);
327
328    // Bad heights aren't caught at compile-time or runtime, until we add or subtract,
329    // and the result is invalid
330    assert_eq!(Some(Height::MAX), Height(i32::MAX as u32 + 1) - 1);
331    assert_eq!(None, Height(u32::MAX) - 1);
332
333    // Subtracting negative numbers
334    assert_eq!(None, Height(Height::MAX_AS_U32 + 1) - -1);
335    assert_eq!(None, Height(i32::MAX as u32) - -1);
336    assert_eq!(None, Height(u32::MAX) - -1);
337
338    assert_eq!(1, (Height(2) - Height(1)));
339    assert_eq!(0, (Height(1) - Height(1)));
340    assert_eq!(-1, Height(0) - Height(1));
341    assert_eq!(-5, Height(2) - Height(7));
342    assert_eq!(Height::MAX.0 as HeightDiff, (Height::MAX - Height(0)));
343    assert_eq!(1, (Height::MAX - Height(Height::MAX_AS_U32 - 1)));
344    assert_eq!(-1, Height(Height::MAX_AS_U32 - 1) - Height::MAX);
345    assert_eq!(-(Height::MAX_AS_U32 as HeightDiff), Height(0) - Height::MAX);
346}