1use crate::{FromBytes, ToBytes, io_error};
17
18use enum_iterator::{Sequence, last};
19use std::io;
20
21#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Sequence)]
24#[repr(u16)]
25pub enum ConsensusVersion {
26 V1 = 1,
28 V2 = 2,
30 V3 = 3,
32 V4 = 4,
34 V5 = 5,
36 V6 = 6,
38 V7 = 7,
40 V8 = 8,
42 V9 = 9,
44 V10 = 10,
46 V11 = 11,
48 V12 = 12,
50 V13 = 13,
52}
53
54impl ToBytes for ConsensusVersion {
55 fn write_le<W: io::Write>(&self, writer: W) -> io::Result<()> {
56 (*self as u16).write_le(writer)
57 }
58}
59
60impl FromBytes for ConsensusVersion {
61 fn read_le<R: io::Read>(reader: R) -> io::Result<Self> {
62 match u16::read_le(reader)? {
63 0 => Err(io_error("Zero is not a valid consensus version")),
64 1 => Ok(Self::V1),
65 2 => Ok(Self::V2),
66 3 => Ok(Self::V3),
67 4 => Ok(Self::V4),
68 5 => Ok(Self::V5),
69 6 => Ok(Self::V6),
70 7 => Ok(Self::V7),
71 8 => Ok(Self::V8),
72 9 => Ok(Self::V9),
73 10 => Ok(Self::V10),
74 11 => Ok(Self::V11),
75 12 => Ok(Self::V12),
76 13 => Ok(Self::V13),
77 _ => Err(io_error("Invalid consensus version")),
78 }
79 }
80}
81
82impl ConsensusVersion {
83 pub fn latest() -> Self {
84 last::<ConsensusVersion>().expect("At least one ConsensusVersion should be defined.")
85 }
86}
87
88impl std::fmt::Display for ConsensusVersion {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 write!(f, "{self:?}")
92 }
93}
94
95pub(crate) const NUM_CONSENSUS_VERSIONS: usize = enum_iterator::cardinality::<ConsensusVersion>();
97
98pub const CANARY_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
100 (ConsensusVersion::V1, 0),
101 (ConsensusVersion::V2, 2_900_000),
102 (ConsensusVersion::V3, 4_560_000),
103 (ConsensusVersion::V4, 5_730_000),
104 (ConsensusVersion::V5, 5_780_000),
105 (ConsensusVersion::V6, 6_240_000),
106 (ConsensusVersion::V7, 6_880_000),
107 (ConsensusVersion::V8, 7_565_000),
108 (ConsensusVersion::V9, 8_028_000),
109 (ConsensusVersion::V10, 8_600_000),
110 (ConsensusVersion::V11, 9_510_000),
111 (ConsensusVersion::V12, 10_030_000),
112 (ConsensusVersion::V13, 10_881_000),
113];
114
115pub const MAINNET_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
117 (ConsensusVersion::V1, 0),
118 (ConsensusVersion::V2, 2_800_000),
119 (ConsensusVersion::V3, 4_900_000),
120 (ConsensusVersion::V4, 6_135_000),
121 (ConsensusVersion::V5, 7_060_000),
122 (ConsensusVersion::V6, 7_560_000),
123 (ConsensusVersion::V7, 7_570_000),
124 (ConsensusVersion::V8, 9_430_000),
125 (ConsensusVersion::V9, 10_272_000),
126 (ConsensusVersion::V10, 11_205_000),
127 (ConsensusVersion::V11, 12_870_000),
128 (ConsensusVersion::V12, 13_815_000),
129 (ConsensusVersion::V13, 16_850_000),
130];
131
132pub const TESTNET_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
134 (ConsensusVersion::V1, 0),
135 (ConsensusVersion::V2, 2_950_000),
136 (ConsensusVersion::V3, 4_800_000),
137 (ConsensusVersion::V4, 6_625_000),
138 (ConsensusVersion::V5, 6_765_000),
139 (ConsensusVersion::V6, 7_600_000),
140 (ConsensusVersion::V7, 8_365_000),
141 (ConsensusVersion::V8, 9_173_000),
142 (ConsensusVersion::V9, 9_800_000),
143 (ConsensusVersion::V10, 10_525_000),
144 (ConsensusVersion::V11, 11_952_000),
145 (ConsensusVersion::V12, 12_669_000),
146 (ConsensusVersion::V13, 14_906_000),
147];
148
149pub const TEST_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
151 (ConsensusVersion::V1, 0),
152 (ConsensusVersion::V2, 5),
153 (ConsensusVersion::V3, 6),
154 (ConsensusVersion::V4, 7),
155 (ConsensusVersion::V5, 8),
156 (ConsensusVersion::V6, 9),
157 (ConsensusVersion::V7, 10),
158 (ConsensusVersion::V8, 11),
159 (ConsensusVersion::V9, 12),
160 (ConsensusVersion::V10, 13),
161 (ConsensusVersion::V11, 14),
162 (ConsensusVersion::V12, 15),
163 (ConsensusVersion::V13, 16),
164];
165
166#[cfg(any(test, feature = "test", feature = "test_consensus_heights"))]
167pub fn load_test_consensus_heights() -> [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] {
168 load_test_consensus_heights_inner(std::env::var("CONSENSUS_VERSION_HEIGHTS").ok())
170}
171
172#[cfg(any(test, feature = "test", feature = "test_consensus_heights", feature = "wasm"))]
173pub(crate) fn load_test_consensus_heights_inner(
174 consensus_version_heights: Option<String>,
175) -> [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] {
176 let verify_consensus_heights = |heights: &[(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS]| {
178 assert_eq!(heights[0].1, 0, "Genesis height must be 0.");
180 for window in heights.windows(2) {
182 if window[0] >= window[1] {
183 panic!("Heights must be strictly increasing, but found: {window:?}");
184 }
185 }
186 };
187
188 let mut test_consensus_heights = TEST_CONSENSUS_VERSION_HEIGHTS;
190
191 match consensus_version_heights {
193 Some(height_string) => {
194 let parsing_error = format!("Expected exactly {NUM_CONSENSUS_VERSIONS} ConsensusVersion heights.");
195 let parsed_test_consensus_heights: [u32; NUM_CONSENSUS_VERSIONS] = height_string
197 .replace(" ", "")
198 .split(",")
199 .map(|height| height.parse::<u32>().expect("Heights should be valid u32 values."))
200 .collect::<Vec<u32>>()
201 .try_into()
202 .expect(&parsing_error);
203 for (i, height) in parsed_test_consensus_heights.into_iter().enumerate() {
205 test_consensus_heights[i] = (TEST_CONSENSUS_VERSION_HEIGHTS[i].0, height);
206 }
207 verify_consensus_heights(&test_consensus_heights);
209 test_consensus_heights
210 }
211 None => {
212 verify_consensus_heights(&test_consensus_heights);
214 test_consensus_heights
215 }
216 }
217}
218
219#[macro_export]
226macro_rules! consensus_config_value {
227 ($network:ident, $constant:ident, $seek_height:expr) => {
228 $network::CONSENSUS_VERSION($seek_height).map_or(None, |seek_version| {
230 match $network::$constant.binary_search_by(|(version, _)| version.cmp(&seek_version)) {
233 Ok(index) => Some($network::$constant[index].1),
235 Err(index) => {
237 if index == 0 {
239 None
240 } else {
242 Some($network::$constant[index - 1].1)
243 }
244 }
245 }
246 })
247 };
248}
249
250#[macro_export]
257macro_rules! consensus_config_value_by_version {
258 ($network:ident, $constant:ident, $seek_version:expr) => {
259 match $network::$constant.binary_search_by(|(version, _)| version.cmp(&$seek_version)) {
261 Ok(index) => Some($network::$constant[index].1),
263 Err(index) => {
265 if index == 0 {
267 None
268 } else {
270 Some($network::$constant[index - 1].1)
271 }
272 }
273 }
274 };
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280 use crate::{CanaryV0, MainnetV0, Network, TestnetV0};
281
282 fn consensus_constants_at_genesis<N: Network>() {
285 let height = N::_CONSENSUS_VERSION_HEIGHTS.first().unwrap().1;
286 assert_eq!(height, 0);
287 let consensus_version = N::_CONSENSUS_VERSION_HEIGHTS.first().unwrap().0;
288 assert_eq!(consensus_version, ConsensusVersion::V1);
289 assert_eq!(consensus_version as usize, 1);
290 }
291
292 fn consensus_versions<N: Network>() {
294 let mut previous_version = N::_CONSENSUS_VERSION_HEIGHTS.first().unwrap().0;
295 assert_eq!(previous_version as usize, 1);
297 for (version, _) in N::_CONSENSUS_VERSION_HEIGHTS.iter().skip(1) {
299 assert_eq!(*version as usize, previous_version as usize + 1);
300 previous_version = *version;
301 }
302 let mut previous_version = N::MAX_CERTIFICATES.first().unwrap().0;
304 for (version, _) in N::MAX_CERTIFICATES.iter().skip(1) {
305 assert!(*version > previous_version);
306 previous_version = *version;
307 }
308 let mut previous_version = N::TRANSACTION_SPEND_LIMIT.first().unwrap().0;
309 for (version, _) in N::TRANSACTION_SPEND_LIMIT.iter().skip(1) {
310 assert!(*version > previous_version);
311 previous_version = *version;
312 }
313 }
314
315 fn consensus_constants_increasing_heights<N: Network>() {
317 let mut previous_height = N::CONSENSUS_VERSION_HEIGHTS().first().unwrap().1;
318 for (version, height) in N::CONSENSUS_VERSION_HEIGHTS().iter().skip(1) {
319 assert!(*height > previous_height);
320 previous_height = *height;
321 assert_eq!(N::CONSENSUS_VERSION(*height).unwrap(), *version);
323 assert_eq!(N::CONSENSUS_HEIGHT(*version).unwrap(), *height);
325 }
326 }
327
328 fn consensus_constants_valid_heights<N: Network>() {
330 for (version, value) in N::MAX_CERTIFICATES.iter() {
331 let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
333 assert_eq!(consensus_config_value!(N, MAX_CERTIFICATES, height).unwrap(), *value);
335 }
336 for (version, value) in N::TRANSACTION_SPEND_LIMIT.iter() {
337 let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
339 assert_eq!(consensus_config_value!(N, TRANSACTION_SPEND_LIMIT, height).unwrap(), *value);
341 }
342 }
343
344 fn consensus_config_returns_some<N: Network>() {
346 for (_, height) in N::CONSENSUS_VERSION_HEIGHTS().iter() {
347 assert!(consensus_config_value!(N, MAX_CERTIFICATES, *height).is_some());
348 assert!(consensus_config_value!(N, TRANSACTION_SPEND_LIMIT, *height).is_some());
349 }
350 }
351
352 fn max_certificates_increasing<N: Network>() {
355 let mut previous_value = N::MAX_CERTIFICATES.first().unwrap().1;
356 for (_, value) in N::MAX_CERTIFICATES.iter().skip(1) {
357 assert!(*value >= previous_value);
358 previous_value = *value;
359 }
360 }
361
362 fn constants_equal_length<N1: Network, N2: Network, N3: Network>() {
364 let _ = [N1::CONSENSUS_VERSION_HEIGHTS, N2::CONSENSUS_VERSION_HEIGHTS, N3::CONSENSUS_VERSION_HEIGHTS];
366 let _ = [N1::MAX_CERTIFICATES, N2::MAX_CERTIFICATES, N3::MAX_CERTIFICATES];
367 let _ = [N1::TRANSACTION_SPEND_LIMIT, N2::TRANSACTION_SPEND_LIMIT, N3::TRANSACTION_SPEND_LIMIT];
368 }
369
370 #[test]
371 #[allow(clippy::assertions_on_constants)]
372 fn test_consensus_constants() {
373 consensus_constants_at_genesis::<MainnetV0>();
374 consensus_constants_at_genesis::<TestnetV0>();
375 consensus_constants_at_genesis::<CanaryV0>();
376
377 consensus_versions::<MainnetV0>();
378 consensus_versions::<TestnetV0>();
379 consensus_versions::<CanaryV0>();
380
381 consensus_constants_increasing_heights::<MainnetV0>();
382 consensus_constants_increasing_heights::<TestnetV0>();
383 consensus_constants_increasing_heights::<CanaryV0>();
384
385 consensus_constants_valid_heights::<MainnetV0>();
386 consensus_constants_valid_heights::<TestnetV0>();
387 consensus_constants_valid_heights::<CanaryV0>();
388
389 consensus_config_returns_some::<MainnetV0>();
390 consensus_config_returns_some::<TestnetV0>();
391 consensus_config_returns_some::<CanaryV0>();
392
393 max_certificates_increasing::<MainnetV0>();
394 max_certificates_increasing::<TestnetV0>();
395 max_certificates_increasing::<CanaryV0>();
396
397 constants_equal_length::<MainnetV0, TestnetV0, CanaryV0>();
398 }
399
400 #[test]
402 fn test_to_bytes() {
403 let version = ConsensusVersion::V8;
404 let bytes = version.to_bytes_le().unwrap();
405 let result = ConsensusVersion::from_bytes_le(&bytes).unwrap();
406 assert_eq!(result, version);
407
408 let version = ConsensusVersion::latest();
409 let bytes = version.to_bytes_le().unwrap();
410 let result = ConsensusVersion::from_bytes_le(&bytes).unwrap();
411 assert_eq!(result, version);
412
413 let invalid_bytes = u16::MAX.to_bytes_le().unwrap();
414 let result = ConsensusVersion::from_bytes_le(&invalid_bytes);
415 assert!(result.is_err());
416 }
417}