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}
49
50impl ToBytes for ConsensusVersion {
51 fn write_le<W: io::Write>(&self, writer: W) -> io::Result<()> {
52 (*self as u16).write_le(writer)
53 }
54}
55
56impl FromBytes for ConsensusVersion {
57 fn read_le<R: io::Read>(reader: R) -> io::Result<Self> {
58 match u16::read_le(reader)? {
59 0 => Err(io_error("Zero is not a valid consensus version")),
60 1 => Ok(Self::V1),
61 2 => Ok(Self::V2),
62 3 => Ok(Self::V3),
63 4 => Ok(Self::V4),
64 5 => Ok(Self::V5),
65 6 => Ok(Self::V6),
66 7 => Ok(Self::V7),
67 8 => Ok(Self::V8),
68 9 => Ok(Self::V9),
69 10 => Ok(Self::V10),
70 11 => Ok(Self::V11),
71 _ => Err(io_error("Invalid consensus version")),
72 }
73 }
74}
75
76impl ConsensusVersion {
77 pub fn latest() -> Self {
78 last::<ConsensusVersion>().expect("At least one ConsensusVersion should be defined.")
79 }
80}
81
82impl std::fmt::Display for ConsensusVersion {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 write!(f, "{self:?}")
86 }
87}
88
89pub(crate) const NUM_CONSENSUS_VERSIONS: usize = 11;
91
92pub const CANARY_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
94 (ConsensusVersion::V1, 0),
95 (ConsensusVersion::V2, 2_900_000),
96 (ConsensusVersion::V3, 4_560_000),
97 (ConsensusVersion::V4, 5_730_000),
98 (ConsensusVersion::V5, 5_780_000),
99 (ConsensusVersion::V6, 6_240_000),
100 (ConsensusVersion::V7, 6_880_000),
101 (ConsensusVersion::V8, 7_565_000),
102 (ConsensusVersion::V9, 8_028_000),
103 (ConsensusVersion::V10, 8_600_000),
104 (ConsensusVersion::V11, 9_510_000),
105];
106
107pub const MAINNET_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
109 (ConsensusVersion::V1, 0),
110 (ConsensusVersion::V2, 2_800_000),
111 (ConsensusVersion::V3, 4_900_000),
112 (ConsensusVersion::V4, 6_135_000),
113 (ConsensusVersion::V5, 7_060_000),
114 (ConsensusVersion::V6, 7_560_000),
115 (ConsensusVersion::V7, 7_570_000),
116 (ConsensusVersion::V8, 9_430_000),
117 (ConsensusVersion::V9, 10_272_000),
118 (ConsensusVersion::V10, 11_205_000),
119 (ConsensusVersion::V11, 12_870_000),
120];
121
122pub const TESTNET_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
124 (ConsensusVersion::V1, 0),
125 (ConsensusVersion::V2, 2_950_000),
126 (ConsensusVersion::V3, 4_800_000),
127 (ConsensusVersion::V4, 6_625_000),
128 (ConsensusVersion::V5, 6_765_000),
129 (ConsensusVersion::V6, 7_600_000),
130 (ConsensusVersion::V7, 8_365_000),
131 (ConsensusVersion::V8, 9_173_000),
132 (ConsensusVersion::V9, 9_800_000),
133 (ConsensusVersion::V10, 10_525_000),
134 (ConsensusVersion::V11, 11_952_000),
135];
136
137pub const TEST_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
139 (ConsensusVersion::V1, 0),
140 (ConsensusVersion::V2, 5),
141 (ConsensusVersion::V3, 6),
142 (ConsensusVersion::V4, 7),
143 (ConsensusVersion::V5, 8),
144 (ConsensusVersion::V6, 9),
145 (ConsensusVersion::V7, 10),
146 (ConsensusVersion::V8, 11),
147 (ConsensusVersion::V9, 12),
148 (ConsensusVersion::V10, 13),
149 (ConsensusVersion::V11, 14),
150];
151
152#[cfg(any(test, feature = "test", feature = "test_consensus_heights"))]
153pub fn load_test_consensus_heights() -> [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] {
154 load_test_consensus_heights_inner(std::env::var("CONSENSUS_VERSION_HEIGHTS").ok())
156}
157
158#[cfg(any(test, feature = "test", feature = "test_consensus_heights", feature = "wasm"))]
159pub(crate) fn load_test_consensus_heights_inner(
160 consensus_version_heights: Option<String>,
161) -> [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] {
162 let verify_consensus_heights = |heights: &[(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS]| {
164 assert_eq!(heights[0].1, 0, "Genesis height must be 0.");
166 for window in heights.windows(2) {
168 if window[0] >= window[1] {
169 panic!("Heights must be strictly increasing, but found: {window:?}");
170 }
171 }
172 };
173
174 let mut test_consensus_heights = TEST_CONSENSUS_VERSION_HEIGHTS;
176
177 match consensus_version_heights {
179 Some(height_string) => {
180 let parsing_error = format!("Expected exactly {NUM_CONSENSUS_VERSIONS} ConsensusVersion heights.");
181 let parsed_test_consensus_heights: [u32; NUM_CONSENSUS_VERSIONS] = height_string
183 .replace(" ", "")
184 .split(",")
185 .map(|height| height.parse::<u32>().expect("Heights should be valid u32 values."))
186 .collect::<Vec<u32>>()
187 .try_into()
188 .expect(&parsing_error);
189 for (i, height) in parsed_test_consensus_heights.into_iter().enumerate() {
191 test_consensus_heights[i] = (TEST_CONSENSUS_VERSION_HEIGHTS[i].0, height);
192 }
193 verify_consensus_heights(&test_consensus_heights);
195 test_consensus_heights
196 }
197 None => {
198 verify_consensus_heights(&test_consensus_heights);
200 test_consensus_heights
201 }
202 }
203}
204
205#[macro_export]
212macro_rules! consensus_config_value {
213 ($network:ident, $constant:ident, $seek_height:expr) => {
214 $network::CONSENSUS_VERSION($seek_height).map_or(None, |seek_version| {
216 match $network::$constant.binary_search_by(|(version, _)| version.cmp(&seek_version)) {
219 Ok(index) => Some($network::$constant[index].1),
221 Err(index) => {
223 if index == 0 {
225 None
226 } else {
228 Some($network::$constant[index - 1].1)
229 }
230 }
231 }
232 })
233 };
234}
235
236#[macro_export]
243macro_rules! consensus_config_value_by_version {
244 ($network:ident, $constant:ident, $seek_version:expr) => {
245 match $network::$constant.binary_search_by(|(version, _)| version.cmp(&$seek_version)) {
247 Ok(index) => Some($network::$constant[index].1),
249 Err(index) => {
251 if index == 0 {
253 None
254 } else {
256 Some($network::$constant[index - 1].1)
257 }
258 }
259 }
260 };
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266 use crate::{CanaryV0, MainnetV0, Network, TestnetV0};
267
268 fn consensus_constants_at_genesis<N: Network>() {
271 let height = N::_CONSENSUS_VERSION_HEIGHTS.first().unwrap().1;
272 assert_eq!(height, 0);
273 let consensus_version = N::_CONSENSUS_VERSION_HEIGHTS.first().unwrap().0;
274 assert_eq!(consensus_version, ConsensusVersion::V1);
275 assert_eq!(consensus_version as usize, 1);
276 }
277
278 fn consensus_versions<N: Network>() {
280 let mut previous_version = N::_CONSENSUS_VERSION_HEIGHTS.first().unwrap().0;
281 assert_eq!(previous_version as usize, 1);
283 for (version, _) in N::_CONSENSUS_VERSION_HEIGHTS.iter().skip(1) {
285 assert_eq!(*version as usize, previous_version as usize + 1);
286 previous_version = *version;
287 }
288 let mut previous_version = N::MAX_CERTIFICATES.first().unwrap().0;
290 for (version, _) in N::MAX_CERTIFICATES.iter().skip(1) {
291 assert!(*version > previous_version);
292 previous_version = *version;
293 }
294 let mut previous_version = N::TRANSACTION_SPEND_LIMIT.first().unwrap().0;
295 for (version, _) in N::TRANSACTION_SPEND_LIMIT.iter().skip(1) {
296 assert!(*version > previous_version);
297 previous_version = *version;
298 }
299 }
300
301 fn consensus_constants_increasing_heights<N: Network>() {
303 let mut previous_height = N::CONSENSUS_VERSION_HEIGHTS().first().unwrap().1;
304 for (version, height) in N::CONSENSUS_VERSION_HEIGHTS().iter().skip(1) {
305 assert!(*height > previous_height);
306 previous_height = *height;
307 assert_eq!(N::CONSENSUS_VERSION(*height).unwrap(), *version);
309 assert_eq!(N::CONSENSUS_HEIGHT(*version).unwrap(), *height);
311 }
312 }
313
314 fn consensus_constants_valid_heights<N: Network>() {
316 for (version, value) in N::MAX_CERTIFICATES.iter() {
317 let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
319 assert_eq!(consensus_config_value!(N, MAX_CERTIFICATES, height).unwrap(), *value);
321 }
322 for (version, value) in N::TRANSACTION_SPEND_LIMIT.iter() {
323 let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
325 assert_eq!(consensus_config_value!(N, TRANSACTION_SPEND_LIMIT, height).unwrap(), *value);
327 }
328 }
329
330 fn consensus_config_returns_some<N: Network>() {
332 for (_, height) in N::CONSENSUS_VERSION_HEIGHTS().iter() {
333 assert!(consensus_config_value!(N, MAX_CERTIFICATES, *height).is_some());
334 assert!(consensus_config_value!(N, TRANSACTION_SPEND_LIMIT, *height).is_some());
335 }
336 }
337
338 fn max_certificates_increasing<N: Network>() {
341 let mut previous_value = N::MAX_CERTIFICATES.first().unwrap().1;
342 for (_, value) in N::MAX_CERTIFICATES.iter().skip(1) {
343 assert!(*value >= previous_value);
344 previous_value = *value;
345 }
346 }
347
348 fn constants_equal_length<N1: Network, N2: Network, N3: Network>() {
350 let _ = [N1::CONSENSUS_VERSION_HEIGHTS, N2::CONSENSUS_VERSION_HEIGHTS, N3::CONSENSUS_VERSION_HEIGHTS];
352 let _ = [N1::MAX_CERTIFICATES, N2::MAX_CERTIFICATES, N3::MAX_CERTIFICATES];
353 let _ = [N1::TRANSACTION_SPEND_LIMIT, N2::TRANSACTION_SPEND_LIMIT, N3::TRANSACTION_SPEND_LIMIT];
354 }
355
356 #[test]
357 #[allow(clippy::assertions_on_constants)]
358 fn test_consensus_constants() {
359 consensus_constants_at_genesis::<MainnetV0>();
360 consensus_constants_at_genesis::<TestnetV0>();
361 consensus_constants_at_genesis::<CanaryV0>();
362
363 consensus_versions::<MainnetV0>();
364 consensus_versions::<TestnetV0>();
365 consensus_versions::<CanaryV0>();
366
367 consensus_constants_increasing_heights::<MainnetV0>();
368 consensus_constants_increasing_heights::<TestnetV0>();
369 consensus_constants_increasing_heights::<CanaryV0>();
370
371 consensus_constants_valid_heights::<MainnetV0>();
372 consensus_constants_valid_heights::<TestnetV0>();
373 consensus_constants_valid_heights::<CanaryV0>();
374
375 consensus_config_returns_some::<MainnetV0>();
376 consensus_config_returns_some::<TestnetV0>();
377 consensus_config_returns_some::<CanaryV0>();
378
379 max_certificates_increasing::<MainnetV0>();
380 max_certificates_increasing::<TestnetV0>();
381 max_certificates_increasing::<CanaryV0>();
382
383 constants_equal_length::<MainnetV0, TestnetV0, CanaryV0>();
384 }
385
386 #[test]
388 fn test_to_bytes() {
389 let version = ConsensusVersion::V8;
390 let bytes = version.to_bytes_le().unwrap();
391 let result = ConsensusVersion::from_bytes_le(&bytes).unwrap();
392 assert_eq!(result, version);
393
394 let invalid_bytes = u16::MAX.to_bytes_le().unwrap();
395 let result = ConsensusVersion::from_bytes_le(&invalid_bytes);
396 assert!(result.is_err());
397 }
398}