cuprate_types/
hard_fork.rs

1//! The [`HardFork`] type.
2use std::time::Duration;
3
4use strum::{
5    AsRefStr, Display, EnumCount, EnumIs, EnumString, FromRepr, IntoStaticStr, VariantArray,
6};
7
8use monero_serai::block::BlockHeader;
9
10/// Target block time for hf 1.
11///
12/// ref: <https://monero-book.cuprate.org/consensus_rules/blocks/difficulty.html#target-seconds>
13const BLOCK_TIME_V1: Duration = Duration::from_secs(60);
14/// Target block time from v2.
15///
16/// ref: <https://monero-book.cuprate.org/consensus_rules/blocks/difficulty.html#target-seconds>
17const BLOCK_TIME_V2: Duration = Duration::from_secs(120);
18
19/// An error working with a [`HardFork`].
20#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
21pub enum HardForkError {
22    /// The raw-HF value is not a valid [`HardFork`].
23    #[error("The hard-fork is unknown")]
24    HardForkUnknown,
25    /// The [`HardFork`] version is incorrect.
26    #[error("The block is on an incorrect hard-fork")]
27    VersionIncorrect,
28    /// The block's [`HardFork`] vote was below the current [`HardFork`].
29    #[error("The block's vote is for a previous hard-fork")]
30    VoteTooLow,
31}
32
33/// An identifier for every hard-fork Monero has had.
34#[derive(
35    Default,
36    Debug,
37    PartialEq,
38    Eq,
39    PartialOrd,
40    Ord,
41    Copy,
42    Clone,
43    Hash,
44    EnumCount,
45    Display,
46    AsRefStr,
47    EnumIs,
48    EnumString,
49    FromRepr,
50    IntoStaticStr,
51    VariantArray,
52)]
53#[cfg_attr(any(feature = "proptest"), derive(proptest_derive::Arbitrary))]
54#[repr(u8)]
55pub enum HardFork {
56    #[default]
57    V1 = 1,
58    V2,
59    V3,
60    V4,
61    V5,
62    V6,
63    V7,
64    V8,
65    V9,
66    V10,
67    V11,
68    V12,
69    V13,
70    V14,
71    V15,
72    V16,
73}
74
75impl HardFork {
76    /// The latest [`HardFork`].
77    ///
78    /// ```rust
79    /// # use cuprate_types::HardFork;
80    /// assert_eq!(HardFork::LATEST, HardFork::V16);
81    /// ```
82    pub const LATEST: Self = Self::VARIANTS[Self::COUNT - 1];
83
84    /// Returns the hard-fork for a blocks [`BlockHeader::hardfork_version`] field.
85    ///
86    /// ref: <https://monero-book.cuprate.org/consensus_rules/hardforks.html#blocks-version-and-vote>
87    ///
88    /// # Errors
89    /// Will return [`Err`] if the version is not a valid [`HardFork`].
90    ///
91    /// ```rust
92    /// # use cuprate_types::{HardFork, HardForkError};
93    /// # use strum::VariantArray;
94    /// assert_eq!(HardFork::from_version(0), Err(HardForkError::HardForkUnknown));
95    /// assert_eq!(HardFork::from_version(17), Err(HardForkError::HardForkUnknown));
96    ///
97    /// for (version, hf) in HardFork::VARIANTS.iter().enumerate() {
98    ///     // +1 because enumerate starts at 0, hf starts at 1.
99    ///     assert_eq!(*hf, HardFork::from_version(version as u8 + 1).unwrap());
100    /// }
101    /// ```
102    #[inline]
103    pub const fn from_version(version: u8) -> Result<Self, HardForkError> {
104        match Self::from_repr(version) {
105            Some(this) => Ok(this),
106            None => Err(HardForkError::HardForkUnknown),
107        }
108    }
109
110    /// Returns the hard-fork for a blocks [`BlockHeader::hardfork_signal`] (vote) field.
111    ///
112    /// <https://monero-book.cuprate.org/consensus_rules/hardforks.html#blocks-version-and-vote>
113    ///
114    /// ```rust
115    /// # use cuprate_types::{HardFork, HardForkError};
116    /// # use strum::VariantArray;
117    /// // 0 is interpreted as 1.
118    /// assert_eq!(HardFork::from_vote(0), HardFork::V1);
119    /// // Unknown defaults to `LATEST`.
120    /// assert_eq!(HardFork::from_vote(17), HardFork::V16);
121    ///
122    /// for (vote, hf) in HardFork::VARIANTS.iter().enumerate() {
123    ///     // +1 because enumerate starts at 0, hf starts at 1.
124    ///     assert_eq!(*hf, HardFork::from_vote(vote as u8 + 1));
125    /// }
126    /// ```
127    #[inline]
128    pub fn from_vote(vote: u8) -> Self {
129        if vote == 0 {
130            // A vote of 0 is interpreted as 1 as that's what Monero used to default to.
131            Self::V1
132        } else {
133            // This must default to the latest hard-fork!
134            Self::from_version(vote).unwrap_or(Self::LATEST)
135        }
136    }
137
138    /// Returns the [`HardFork`] version and vote from this block header.
139    ///
140    /// # Errors
141    /// Will return [`Err`] if the [`BlockHeader::hardfork_version`] is not a valid [`HardFork`].
142    #[inline]
143    pub fn from_block_header(header: &BlockHeader) -> Result<(Self, Self), HardForkError> {
144        Ok((
145            Self::from_version(header.hardfork_version)?,
146            Self::from_vote(header.hardfork_signal),
147        ))
148    }
149
150    /// Returns the raw hard-fork value, as it would appear in [`BlockHeader::hardfork_version`].
151    ///
152    /// ```rust
153    /// # use cuprate_types::{HardFork, HardForkError};
154    /// # use strum::VariantArray;
155    /// for (i, hf) in HardFork::VARIANTS.iter().enumerate() {
156    ///     // +1 because enumerate starts at 0, hf starts at 1.
157    ///     assert_eq!(hf.as_u8(), i as u8 + 1);
158    /// }
159    /// ```
160    pub const fn as_u8(self) -> u8 {
161        self as u8
162    }
163
164    /// Returns the next hard-fork.
165    pub fn next_fork(self) -> Option<Self> {
166        Self::from_version(self as u8 + 1).ok()
167    }
168
169    /// Returns the target block time for this hardfork.
170    ///
171    /// ref: <https://monero-book.cuprate.org/consensus_rules/blocks/difficulty.html#target-seconds>
172    pub const fn block_time(self) -> Duration {
173        match self {
174            Self::V1 => BLOCK_TIME_V1,
175            _ => BLOCK_TIME_V2,
176        }
177    }
178
179    /// Returns `true` if `self` is [`Self::LATEST`].
180    ///
181    /// ```rust
182    /// # use cuprate_types::HardFork;
183    /// # use strum::VariantArray;
184    ///
185    /// for hf in HardFork::VARIANTS.iter() {
186    ///     if *hf == HardFork::LATEST {
187    ///         assert!(hf.is_latest());
188    ///     } else {
189    ///         assert!(!hf.is_latest());
190    ///     }
191    /// }
192    /// ```
193    pub const fn is_latest(self) -> bool {
194        matches!(self, Self::LATEST)
195    }
196}