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}