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 V14 = 14,
57 V15 = 15,
61}
62
63impl ToBytes for ConsensusVersion {
64 fn write_le<W: io::Write>(&self, writer: W) -> io::Result<()> {
65 (*self as u16).write_le(writer)
66 }
67}
68
69impl FromBytes for ConsensusVersion {
70 fn read_le<R: io::Read>(reader: R) -> io::Result<Self> {
71 match u16::read_le(reader)? {
72 0 => Err(io_error("Zero is not a valid consensus version")),
73 1 => Ok(Self::V1),
74 2 => Ok(Self::V2),
75 3 => Ok(Self::V3),
76 4 => Ok(Self::V4),
77 5 => Ok(Self::V5),
78 6 => Ok(Self::V6),
79 7 => Ok(Self::V7),
80 8 => Ok(Self::V8),
81 9 => Ok(Self::V9),
82 10 => Ok(Self::V10),
83 11 => Ok(Self::V11),
84 12 => Ok(Self::V12),
85 13 => Ok(Self::V13),
86 14 => Ok(Self::V14),
87 15 => Ok(Self::V15),
88 _ => Err(io_error("Invalid consensus version")),
89 }
90 }
91}
92
93impl ConsensusVersion {
94 pub fn latest() -> Self {
95 last::<ConsensusVersion>().expect("At least one ConsensusVersion should be defined.")
96 }
97}
98
99impl std::fmt::Display for ConsensusVersion {
100 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101 write!(f, "{self:?}")
103 }
104}
105
106pub(crate) const NUM_CONSENSUS_VERSIONS: usize = enum_iterator::cardinality::<ConsensusVersion>();
108
109pub const CANARY_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
111 (ConsensusVersion::V1, 0),
112 (ConsensusVersion::V2, 2_900_000),
113 (ConsensusVersion::V3, 4_560_000),
114 (ConsensusVersion::V4, 5_730_000),
115 (ConsensusVersion::V5, 5_780_000),
116 (ConsensusVersion::V6, 6_240_000),
117 (ConsensusVersion::V7, 6_880_000),
118 (ConsensusVersion::V8, 7_565_000),
119 (ConsensusVersion::V9, 8_028_000),
120 (ConsensusVersion::V10, 8_600_000),
121 (ConsensusVersion::V11, 9_510_000),
122 (ConsensusVersion::V12, 10_030_000),
123 (ConsensusVersion::V13, 10_881_000),
124 (ConsensusVersion::V14, 11_960_000),
125 (ConsensusVersion::V15, u32::MAX),
126];
127
128pub const MAINNET_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
130 (ConsensusVersion::V1, 0),
131 (ConsensusVersion::V2, 2_800_000),
132 (ConsensusVersion::V3, 4_900_000),
133 (ConsensusVersion::V4, 6_135_000),
134 (ConsensusVersion::V5, 7_060_000),
135 (ConsensusVersion::V6, 7_560_000),
136 (ConsensusVersion::V7, 7_570_000),
137 (ConsensusVersion::V8, 9_430_000),
138 (ConsensusVersion::V9, 10_272_000),
139 (ConsensusVersion::V10, 11_205_000),
140 (ConsensusVersion::V11, 12_870_000),
141 (ConsensusVersion::V12, 13_815_000),
142 (ConsensusVersion::V13, 16_850_000),
143 (ConsensusVersion::V14, 17_700_000),
144 (ConsensusVersion::V15, 19_264_000),
145];
146
147pub const TESTNET_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
149 (ConsensusVersion::V1, 0),
150 (ConsensusVersion::V2, 2_950_000),
151 (ConsensusVersion::V3, 4_800_000),
152 (ConsensusVersion::V4, 6_625_000),
153 (ConsensusVersion::V5, 6_765_000),
154 (ConsensusVersion::V6, 7_600_000),
155 (ConsensusVersion::V7, 8_365_000),
156 (ConsensusVersion::V8, 9_173_000),
157 (ConsensusVersion::V9, 9_800_000),
158 (ConsensusVersion::V10, 10_525_000),
159 (ConsensusVersion::V11, 11_952_000),
160 (ConsensusVersion::V12, 12_669_000),
161 (ConsensusVersion::V13, 14_906_000),
162 (ConsensusVersion::V14, 15_370_000),
163 (ConsensusVersion::V15, 16_886_000),
164];
165
166pub const TEST_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
168 (ConsensusVersion::V1, 0),
169 (ConsensusVersion::V2, 5),
170 (ConsensusVersion::V3, 6),
171 (ConsensusVersion::V4, 7),
172 (ConsensusVersion::V5, 8),
173 (ConsensusVersion::V6, 9),
174 (ConsensusVersion::V7, 10),
175 (ConsensusVersion::V8, 11),
176 (ConsensusVersion::V9, 12),
177 (ConsensusVersion::V10, 13),
178 (ConsensusVersion::V11, 14),
179 (ConsensusVersion::V12, 15),
180 (ConsensusVersion::V13, 16),
181 (ConsensusVersion::V14, 17),
182 (ConsensusVersion::V15, 18),
183];
184
185#[cfg(any(test, feature = "test", feature = "test_consensus_heights"))]
186pub fn load_test_consensus_heights() -> [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] {
187 load_test_consensus_heights_inner(std::env::var("CONSENSUS_VERSION_HEIGHTS").ok())
189}
190
191#[cfg(any(test, feature = "test", feature = "test_consensus_heights", feature = "wasm"))]
192pub(crate) fn load_test_consensus_heights_inner(
193 consensus_version_heights: Option<String>,
194) -> [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] {
195 let verify_consensus_heights = |heights: &[(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS]| {
197 assert_eq!(heights[0].1, 0, "Genesis height must be 0.");
199 for window in heights.windows(2) {
201 if window[0] >= window[1] {
202 panic!("Heights must be strictly increasing, but found: {window:?}");
203 }
204 }
205 };
206
207 let mut test_consensus_heights = TEST_CONSENSUS_VERSION_HEIGHTS;
209
210 match consensus_version_heights {
212 Some(height_string) => {
213 let parsing_error = format!("Expected exactly {NUM_CONSENSUS_VERSIONS} ConsensusVersion heights.");
214 let parsed_test_consensus_heights: [u32; NUM_CONSENSUS_VERSIONS] = height_string
216 .replace(" ", "")
217 .split(",")
218 .map(|height| height.parse::<u32>().expect("Heights should be valid u32 values."))
219 .collect::<Vec<u32>>()
220 .try_into()
221 .expect(&parsing_error);
222 for (i, height) in parsed_test_consensus_heights.into_iter().enumerate() {
224 test_consensus_heights[i] = (TEST_CONSENSUS_VERSION_HEIGHTS[i].0, height);
225 }
226 verify_consensus_heights(&test_consensus_heights);
228 test_consensus_heights
229 }
230 None => {
231 verify_consensus_heights(&test_consensus_heights);
233 test_consensus_heights
234 }
235 }
236}
237
238#[macro_export]
245macro_rules! consensus_config_value {
246 ($network:ident, $constant:ident, $seek_height:expr) => {
247 $network::CONSENSUS_VERSION($seek_height).map_or(None, |seek_version| {
249 match $network::$constant.binary_search_by(|(version, _)| version.cmp(&seek_version)) {
252 Ok(index) => Some($network::$constant[index].1),
254 Err(index) => {
256 if index == 0 {
258 None
259 } else {
261 Some($network::$constant[index - 1].1)
262 }
263 }
264 }
265 })
266 };
267}
268
269#[macro_export]
276macro_rules! consensus_config_value_by_version {
277 ($network:ident, $constant:ident, $seek_version:expr) => {
278 match $network::$constant.binary_search_by(|(version, _)| version.cmp(&$seek_version)) {
280 Ok(index) => Some($network::$constant[index].1),
282 Err(index) => {
284 if index == 0 {
286 None
287 } else {
289 Some($network::$constant[index - 1].1)
290 }
291 }
292 }
293 };
294}
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299 use crate::{CanaryV0, MainnetV0, Network, TestnetV0};
300
301 fn consensus_constants_at_genesis<N: Network>() {
304 let height = N::_CONSENSUS_VERSION_HEIGHTS.first().unwrap().1;
305 assert_eq!(height, 0);
306 let consensus_version = N::_CONSENSUS_VERSION_HEIGHTS.first().unwrap().0;
307 assert_eq!(consensus_version, ConsensusVersion::V1);
308 assert_eq!(consensus_version as usize, 1);
309 }
310
311 fn consensus_versions<N: Network>() {
313 let mut previous_version = N::_CONSENSUS_VERSION_HEIGHTS.first().unwrap().0;
314 assert_eq!(previous_version as usize, 1);
316 for (version, _) in N::_CONSENSUS_VERSION_HEIGHTS.iter().skip(1) {
318 assert_eq!(*version as usize, previous_version as usize + 1);
319 previous_version = *version;
320 }
321 let mut previous_version = N::MAX_CERTIFICATES.first().unwrap().0;
323 for (version, _) in N::MAX_CERTIFICATES.iter().skip(1) {
324 assert!(*version > previous_version);
325 previous_version = *version;
326 }
327 let mut previous_version = N::TRANSACTION_SPEND_LIMIT.first().unwrap().0;
328 for (version, _) in N::TRANSACTION_SPEND_LIMIT.iter().skip(1) {
329 assert!(*version > previous_version);
330 previous_version = *version;
331 }
332 let mut previous_version = N::MAX_ARRAY_ELEMENTS.first().unwrap().0;
333 for (version, _) in N::MAX_ARRAY_ELEMENTS.iter().skip(1) {
334 assert!(*version > previous_version);
335 previous_version = *version;
336 }
337 let mut previous_version = N::MAX_PROGRAM_SIZE.first().unwrap().0;
338 for (version, _) in N::MAX_PROGRAM_SIZE.iter().skip(1) {
339 assert!(*version > previous_version);
340 previous_version = *version;
341 }
342 let mut previous_version = N::MAX_TRANSACTION_SIZE.first().unwrap().0;
343 for (version, _) in N::MAX_TRANSACTION_SIZE.iter().skip(1) {
344 assert!(*version > previous_version);
345 previous_version = *version;
346 }
347 let mut previous_version = N::MAX_WRITES.first().unwrap().0;
348 for (version, _) in N::MAX_WRITES.iter().skip(1) {
349 assert!(*version > previous_version);
350 previous_version = *version;
351 }
352 let mut previous_version = N::ANCHOR_TIMES.first().unwrap().0;
353 for (version, _) in N::ANCHOR_TIMES.iter().skip(1) {
354 assert!(*version > previous_version);
355 previous_version = *version;
356 }
357 }
358
359 fn consensus_constants_increasing_heights<N: Network>() {
361 let mut previous_height = N::CONSENSUS_VERSION_HEIGHTS().first().unwrap().1;
362 for (version, height) in N::CONSENSUS_VERSION_HEIGHTS().iter().skip(1) {
363 assert!(*height > previous_height);
364 previous_height = *height;
365 assert_eq!(N::CONSENSUS_VERSION(*height).unwrap(), *version);
367 assert_eq!(N::CONSENSUS_HEIGHT(*version).unwrap(), *height);
369 }
370 }
371
372 fn consensus_constants_valid_heights<N: Network>() {
374 for (version, value) in N::MAX_CERTIFICATES.iter() {
375 let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
377 assert_eq!(consensus_config_value!(N, MAX_CERTIFICATES, height).unwrap(), *value);
379 }
380 for (version, value) in N::TRANSACTION_SPEND_LIMIT.iter() {
381 let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
383 assert_eq!(consensus_config_value!(N, TRANSACTION_SPEND_LIMIT, height).unwrap(), *value);
385 }
386 for (version, value) in N::MAX_ARRAY_ELEMENTS.iter() {
387 let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
389 assert_eq!(consensus_config_value!(N, MAX_ARRAY_ELEMENTS, height).unwrap(), *value);
391 }
392 for (version, value) in N::MAX_PROGRAM_SIZE.iter() {
393 let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
395 assert_eq!(consensus_config_value!(N, MAX_PROGRAM_SIZE, height).unwrap(), *value);
397 }
398 for (version, value) in N::MAX_TRANSACTION_SIZE.iter() {
399 let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
401 assert_eq!(consensus_config_value!(N, MAX_TRANSACTION_SIZE, height).unwrap(), *value);
403 }
404 for (version, value) in N::MAX_WRITES.iter() {
405 let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
407 assert_eq!(consensus_config_value!(N, MAX_WRITES, height).unwrap(), *value);
409 }
410 for (version, value) in N::ANCHOR_TIMES.iter() {
411 let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| c_version == version).unwrap().1;
412 assert_eq!(consensus_config_value!(N, ANCHOR_TIMES, height).unwrap(), *value);
413 }
414 }
415
416 fn consensus_config_returns_some<N: Network>() {
418 for (_, height) in N::CONSENSUS_VERSION_HEIGHTS().iter() {
419 assert!(consensus_config_value!(N, MAX_CERTIFICATES, *height).is_some());
420 assert!(consensus_config_value!(N, TRANSACTION_SPEND_LIMIT, *height).is_some());
421 assert!(consensus_config_value!(N, MAX_ARRAY_ELEMENTS, *height).is_some());
422 assert!(consensus_config_value!(N, MAX_PROGRAM_SIZE, *height).is_some());
423 assert!(consensus_config_value!(N, MAX_TRANSACTION_SIZE, *height).is_some());
424 assert!(consensus_config_value!(N, MAX_WRITES, *height).is_some());
425 assert!(consensus_config_value!(N, ANCHOR_TIMES, *height).is_some());
426 }
427 }
428
429 fn max_certificates_increasing<N: Network>() {
432 let mut previous_value = N::MAX_CERTIFICATES.first().unwrap().1;
433 for (_, value) in N::MAX_CERTIFICATES.iter().skip(1) {
434 assert!(*value >= previous_value);
435 previous_value = *value;
436 }
437 }
438
439 fn max_array_elements_increasing<N: Network>() {
442 let mut previous_value = N::MAX_ARRAY_ELEMENTS.first().unwrap().1;
443 for (_, value) in N::MAX_ARRAY_ELEMENTS.iter().skip(1) {
444 assert!(*value >= previous_value);
445 previous_value = *value;
446 }
447 }
448
449 fn transaction_size_exceeds_program_size<N: Network>() {
452 const MIN_OVERHEAD: usize = 28_000; for (_, height) in N::CONSENSUS_VERSION_HEIGHTS().iter() {
455 let max_program_size = consensus_config_value!(N, MAX_PROGRAM_SIZE, *height).unwrap();
456 let max_transaction_size = consensus_config_value!(N, MAX_TRANSACTION_SIZE, *height).unwrap();
457
458 assert!(
459 max_transaction_size >= max_program_size + MIN_OVERHEAD,
460 "At height {height}: MAX_TRANSACTION_SIZE ({max_transaction_size}) must be at least {MIN_OVERHEAD} bytes greater than MAX_PROGRAM_SIZE ({max_program_size})"
461 );
462 }
463 }
464
465 fn constants_equal_length<N1: Network, N2: Network, N3: Network>() {
467 let _ = [N1::CONSENSUS_VERSION_HEIGHTS, N2::CONSENSUS_VERSION_HEIGHTS, N3::CONSENSUS_VERSION_HEIGHTS];
469 let _ = [N1::MAX_CERTIFICATES, N2::MAX_CERTIFICATES, N3::MAX_CERTIFICATES];
470 let _ = [N1::TRANSACTION_SPEND_LIMIT, N2::TRANSACTION_SPEND_LIMIT, N3::TRANSACTION_SPEND_LIMIT];
471 let _ = [N1::MAX_ARRAY_ELEMENTS, N2::MAX_ARRAY_ELEMENTS, N3::MAX_ARRAY_ELEMENTS];
472 let _ = [N1::MAX_PROGRAM_SIZE, N2::MAX_PROGRAM_SIZE, N3::MAX_PROGRAM_SIZE];
473 let _ = [N1::MAX_TRANSACTION_SIZE, N2::MAX_TRANSACTION_SIZE, N3::MAX_TRANSACTION_SIZE];
474 let _ = [N1::MAX_WRITES, N2::MAX_WRITES, N3::MAX_WRITES];
475 let _ = [N1::ANCHOR_TIMES, N2::ANCHOR_TIMES, N3::ANCHOR_TIMES];
476 }
477
478 fn latest_max_functions_are_safe<N: Network>() {
481 assert!(N::LATEST_MAX_CERTIFICATES() > 0, "LATEST_MAX_CERTIFICATES must be positive");
483 assert!(N::LATEST_MAX_PROGRAM_SIZE() > 0, "LATEST_MAX_PROGRAM_SIZE must be positive");
485 assert!(N::LATEST_MAX_TRANSACTION_SIZE() > 0, "LATEST_MAX_TRANSACTION_SIZE must be positive");
487 assert!(N::LATEST_MAX_WRITES() > 0, "LATEST_MAX_WRITES must be positive");
489 }
490
491 #[test]
492 #[allow(clippy::assertions_on_constants)]
493 fn test_consensus_constants() {
494 consensus_constants_at_genesis::<MainnetV0>();
495 consensus_constants_at_genesis::<TestnetV0>();
496 consensus_constants_at_genesis::<CanaryV0>();
497
498 consensus_versions::<MainnetV0>();
499 consensus_versions::<TestnetV0>();
500 consensus_versions::<CanaryV0>();
501
502 consensus_constants_increasing_heights::<MainnetV0>();
503 consensus_constants_increasing_heights::<TestnetV0>();
504 consensus_constants_increasing_heights::<CanaryV0>();
505
506 consensus_constants_valid_heights::<MainnetV0>();
507 consensus_constants_valid_heights::<TestnetV0>();
508 consensus_constants_valid_heights::<CanaryV0>();
509
510 consensus_config_returns_some::<MainnetV0>();
511 consensus_config_returns_some::<TestnetV0>();
512 consensus_config_returns_some::<CanaryV0>();
513
514 max_certificates_increasing::<MainnetV0>();
515 max_certificates_increasing::<TestnetV0>();
516 max_certificates_increasing::<CanaryV0>();
517
518 max_array_elements_increasing::<MainnetV0>();
519 max_array_elements_increasing::<TestnetV0>();
520 max_array_elements_increasing::<CanaryV0>();
521
522 transaction_size_exceeds_program_size::<MainnetV0>();
523 transaction_size_exceeds_program_size::<TestnetV0>();
524 transaction_size_exceeds_program_size::<CanaryV0>();
525
526 latest_max_functions_are_safe::<MainnetV0>();
527 latest_max_functions_are_safe::<TestnetV0>();
528 latest_max_functions_are_safe::<CanaryV0>();
529
530 constants_equal_length::<MainnetV0, TestnetV0, CanaryV0>();
531 }
532
533 #[test]
535 fn test_to_bytes() {
536 let version = ConsensusVersion::V8;
537 let bytes = version.to_bytes_le().unwrap();
538 let result = ConsensusVersion::from_bytes_le(&bytes).unwrap();
539 assert_eq!(result, version);
540
541 let version = ConsensusVersion::latest();
542 let bytes = version.to_bytes_le().unwrap();
543 let result = ConsensusVersion::from_bytes_le(&bytes).unwrap();
544 assert_eq!(result, version);
545
546 let invalid_bytes = u16::MAX.to_bytes_le().unwrap();
547 let result = ConsensusVersion::from_bytes_le(&invalid_bytes);
548 assert!(result.is_err());
549 }
550
551 #[test]
552 fn test_reward_anchor_time() {
553 assert_eq!(MainnetV0::REWARD_ANCHOR_TIME, MainnetV0::ANCHOR_TIMES.first().unwrap().1);
554 assert_eq!(TestnetV0::REWARD_ANCHOR_TIME, TestnetV0::ANCHOR_TIMES.first().unwrap().1);
555 assert_eq!(CanaryV0::REWARD_ANCHOR_TIME, CanaryV0::ANCHOR_TIMES.first().unwrap().1);
556 }
557}