/ console / network / src / consensus_heights.rs
consensus_heights.rs
  1  // Copyright (c) 2025 ADnet Contributors
  2  // This file is part of the AlphaVM library.
  3  
  4  // Licensed under the Apache License, Version 2.0 (the "License");
  5  // you may not use this file except in compliance with the License.
  6  // You may obtain a copy of the License at:
  7  
  8  // http://www.apache.org/licenses/LICENSE-2.0
  9  
 10  // Unless required by applicable law or agreed to in writing, software
 11  // distributed under the License is distributed on an "AS IS" BASIS,
 12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  // See the License for the specific language governing permissions and
 14  // limitations under the License.
 15  
 16  use crate::{FromBytes, ToBytes, io_error};
 17  
 18  use enum_iterator::{Sequence, last};
 19  use std::io;
 20  
 21  /// The different consensus versions.
 22  /// If you need the version active for a specific height, see: `N::CONSENSUS_VERSION`.
 23  #[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Sequence)]
 24  #[repr(u16)]
 25  pub enum ConsensusVersion {
 26      /// V1: The initial genesis consensus version.
 27      V1 = 1,
 28      /// V2: Update to the block reward and execution cost algorithms.
 29      V2 = 2,
 30      /// V3: Update to the number of validators and finalize scope RNG seed.
 31      V3 = 3,
 32      /// V4: Update to the Varuna version.
 33      V4 = 4,
 34      /// V5: Update to the number of validators and enable batch proposal spend limits.
 35      V5 = 5,
 36      /// V6: Update to the number of validators.
 37      V6 = 6,
 38      /// V7: Update to program rules.
 39      V7 = 7,
 40      /// V8: Update to inclusion version, record commitment version, and introduces sender ciphertexts.
 41      V8 = 8,
 42      /// V9: Support for program upgradability.
 43      V9 = 9,
 44      /// V10: Lower fees, appropriate record output type checking.
 45      V10 = 10,
 46      /// V11: Expand array size limit to 512 and introduce ECDSA signature verification opcodes.
 47      V11 = 11,
 48      /// V12: Prevent connection to forked nodes, disable StringType, enable block timestamp.
 49      V12 = 12,
 50  }
 51  
 52  impl ToBytes for ConsensusVersion {
 53      fn write_le<W: io::Write>(&self, writer: W) -> io::Result<()> {
 54          (*self as u16).write_le(writer)
 55      }
 56  }
 57  
 58  impl FromBytes for ConsensusVersion {
 59      fn read_le<R: io::Read>(reader: R) -> io::Result<Self> {
 60          match u16::read_le(reader)? {
 61              0 => Err(io_error("Zero is not a valid consensus version")),
 62              1 => Ok(Self::V1),
 63              2 => Ok(Self::V2),
 64              3 => Ok(Self::V3),
 65              4 => Ok(Self::V4),
 66              5 => Ok(Self::V5),
 67              6 => Ok(Self::V6),
 68              7 => Ok(Self::V7),
 69              8 => Ok(Self::V8),
 70              9 => Ok(Self::V9),
 71              10 => Ok(Self::V10),
 72              11 => Ok(Self::V11),
 73              12 => Ok(Self::V12),
 74              _ => Err(io_error("Invalid consensus version")),
 75          }
 76      }
 77  }
 78  
 79  impl ConsensusVersion {
 80      pub fn latest() -> Self {
 81          last::<ConsensusVersion>().expect("At least one ConsensusVersion should be defined.")
 82      }
 83  }
 84  
 85  impl std::fmt::Display for ConsensusVersion {
 86      fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 87          // Use Debug formatting for Display.
 88          write!(f, "{self:?}")
 89      }
 90  }
 91  
 92  /// The number of consensus versions.
 93  pub(crate) const NUM_CONSENSUS_VERSIONS: usize = enum_iterator::cardinality::<ConsensusVersion>();
 94  
 95  /// The consensus version height for `CanaryV0`.
 96  pub const CANARY_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
 97      (ConsensusVersion::V1, 0),
 98      (ConsensusVersion::V2, 2_900_000),
 99      (ConsensusVersion::V3, 4_560_000),
100      (ConsensusVersion::V4, 5_730_000),
101      (ConsensusVersion::V5, 5_780_000),
102      (ConsensusVersion::V6, 6_240_000),
103      (ConsensusVersion::V7, 6_880_000),
104      (ConsensusVersion::V8, 7_565_000),
105      (ConsensusVersion::V9, 8_028_000),
106      (ConsensusVersion::V10, 8_600_000),
107      (ConsensusVersion::V11, 9_510_000),
108      (ConsensusVersion::V12, 10_030_000),
109  ];
110  
111  /// The consensus version height for `MainnetV0`.
112  pub const MAINNET_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
113      (ConsensusVersion::V1, 0),
114      (ConsensusVersion::V2, 2_800_000),
115      (ConsensusVersion::V3, 4_900_000),
116      (ConsensusVersion::V4, 6_135_000),
117      (ConsensusVersion::V5, 7_060_000),
118      (ConsensusVersion::V6, 7_560_000),
119      (ConsensusVersion::V7, 7_570_000),
120      (ConsensusVersion::V8, 9_430_000),
121      (ConsensusVersion::V9, 10_272_000),
122      (ConsensusVersion::V10, 11_205_000),
123      (ConsensusVersion::V11, 12_870_000),
124      (ConsensusVersion::V12, 13_815_000),
125  ];
126  
127  /// The consensus version heights for `TestnetV0`.
128  pub const TESTNET_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
129      (ConsensusVersion::V1, 0),
130      (ConsensusVersion::V2, 2_950_000),
131      (ConsensusVersion::V3, 4_800_000),
132      (ConsensusVersion::V4, 6_625_000),
133      (ConsensusVersion::V5, 6_765_000),
134      (ConsensusVersion::V6, 7_600_000),
135      (ConsensusVersion::V7, 8_365_000),
136      (ConsensusVersion::V8, 9_173_000),
137      (ConsensusVersion::V9, 9_800_000),
138      (ConsensusVersion::V10, 10_525_000),
139      (ConsensusVersion::V11, 11_952_000),
140      (ConsensusVersion::V12, 12_669_000),
141  ];
142  
143  /// The consensus version heights when the `test_consensus_heights` feature is enabled.
144  pub const TEST_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
145      (ConsensusVersion::V1, 0),
146      (ConsensusVersion::V2, 5),
147      (ConsensusVersion::V3, 6),
148      (ConsensusVersion::V4, 7),
149      (ConsensusVersion::V5, 8),
150      (ConsensusVersion::V6, 9),
151      (ConsensusVersion::V7, 10),
152      (ConsensusVersion::V8, 11),
153      (ConsensusVersion::V9, 12),
154      (ConsensusVersion::V10, 13),
155      (ConsensusVersion::V11, 14),
156      (ConsensusVersion::V12, 15),
157  ];
158  
159  #[cfg(any(test, feature = "test", feature = "test_consensus_heights"))]
160  pub fn load_test_consensus_heights() -> [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] {
161      // Attempt to read the test consensus heights from the environment variable.
162      load_test_consensus_heights_inner(std::env::var("CONSENSUS_VERSION_HEIGHTS").ok())
163  }
164  
165  #[cfg(any(test, feature = "test", feature = "test_consensus_heights", feature = "wasm"))]
166  pub(crate) fn load_test_consensus_heights_inner(
167      consensus_version_heights: Option<String>,
168  ) -> [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] {
169      // Define a closure to verify the consensus heights.
170      let verify_consensus_heights = |heights: &[(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS]| {
171          // Assert that the genesis height is 0.
172          assert_eq!(heights[0].1, 0, "Genesis height must be 0.");
173          // Assert that the consensus heights are strictly increasing.
174          for window in heights.windows(2) {
175              if window[0] >= window[1] {
176                  panic!("Heights must be strictly increasing, but found: {window:?}");
177              }
178          }
179      };
180  
181      // Define consensus version heights container used for testing.
182      let mut test_consensus_heights = TEST_CONSENSUS_VERSION_HEIGHTS;
183  
184      // If version heights have been specified, verify and return them.
185      match consensus_version_heights {
186          Some(height_string) => {
187              let parsing_error = format!("Expected exactly {NUM_CONSENSUS_VERSIONS} ConsensusVersion heights.");
188              // Parse the heights from the environment variable.
189              let parsed_test_consensus_heights: [u32; NUM_CONSENSUS_VERSIONS] = height_string
190                  .replace(" ", "")
191                  .split(",")
192                  .map(|height| height.parse::<u32>().expect("Heights should be valid u32 values."))
193                  .collect::<Vec<u32>>()
194                  .try_into()
195                  .expect(&parsing_error);
196              // Set the parsed heights in the test consensus heights.
197              for (i, height) in parsed_test_consensus_heights.into_iter().enumerate() {
198                  test_consensus_heights[i] = (TEST_CONSENSUS_VERSION_HEIGHTS[i].0, height);
199              }
200              // Verify and return the parsed test consensus heights.
201              verify_consensus_heights(&test_consensus_heights);
202              test_consensus_heights
203          }
204          None => {
205              // Verify and return the default test consensus heights.
206              verify_consensus_heights(&test_consensus_heights);
207              test_consensus_heights
208          }
209      }
210  }
211  
212  /// Returns the consensus configuration value for the specified height.
213  ///
214  /// Arguments:
215  /// - `$network`: The network to use the constant of.
216  /// - `$constant`: The constant to search a value of.
217  /// - `$seek_height`: The block height to search the value for.
218  #[macro_export]
219  macro_rules! consensus_config_value {
220      ($network:ident, $constant:ident, $seek_height:expr) => {
221          // Search the consensus version enacted at the specified height.
222          $network::CONSENSUS_VERSION($seek_height).map_or(None, |seek_version| {
223              // Search the consensus value for the specified version.
224              // NOTE: calling `consensus_config_value_by_version!` here would require callers to import both macros.
225              match $network::$constant.binary_search_by(|(version, _)| version.cmp(&seek_version)) {
226                  // If a value was found for this consensus version, return it.
227                  Ok(index) => Some($network::$constant[index].1),
228                  // If the specified version was not found exactly, determine whether to return an appropriate value anyway.
229                  Err(index) => {
230                      // This constant is not yet in effect at this consensus version.
231                      if index == 0 {
232                          None
233                      // Return the appropriate value belonging to the consensus version *lower* than the sought version.
234                      } else {
235                          Some($network::$constant[index - 1].1)
236                      }
237                  }
238              }
239          })
240      };
241  }
242  
243  /// Returns the consensus configuration value for the specified ConsensusVersion.
244  ///
245  /// Arguments:
246  /// - `$network`: The network to use the constant of.
247  /// - `$constant`: The constant to search a value of.
248  /// - `$seek_version`: The ConsensusVersion to search the value for.
249  #[macro_export]
250  macro_rules! consensus_config_value_by_version {
251      ($network:ident, $constant:ident, $seek_version:expr) => {
252          // Search the consensus value for the specified version.
253          match $network::$constant.binary_search_by(|(version, _)| version.cmp(&$seek_version)) {
254              // If a value was found for this consensus version, return it.
255              Ok(index) => Some($network::$constant[index].1),
256              // If the specified version was not found exactly, determine whether to return an appropriate value anyway.
257              Err(index) => {
258                  // This constant is not yet in effect at this consensus version.
259                  if index == 0 {
260                      None
261                  // Return the appropriate value belonging to the consensus version *lower* than the sought version.
262                  } else {
263                      Some($network::$constant[index - 1].1)
264                  }
265              }
266          }
267      };
268  }
269  
270  #[cfg(test)]
271  mod tests {
272      use super::*;
273      use crate::{CanaryV0, MainnetV0, Network, TestnetV0};
274  
275      /// Ensure that the consensus constants are defined and correct at genesis.
276      /// It is possible this invariant no longer holds in the future, e.g. due to pruning or novel types of constants.
277      fn consensus_constants_at_genesis<N: Network>() {
278          let height = N::_CONSENSUS_VERSION_HEIGHTS.first().unwrap().1;
279          assert_eq!(height, 0);
280          let consensus_version = N::_CONSENSUS_VERSION_HEIGHTS.first().unwrap().0;
281          assert_eq!(consensus_version, ConsensusVersion::V1);
282          assert_eq!(consensus_version as usize, 1);
283      }
284  
285      /// Ensure that the consensus *versions* are unique, incrementing and start with 1.
286      fn consensus_versions<N: Network>() {
287          let mut previous_version = N::_CONSENSUS_VERSION_HEIGHTS.first().unwrap().0;
288          // Ensure that the consensus versions start with 1.
289          assert_eq!(previous_version as usize, 1);
290          // Ensure that the consensus versions are unique and incrementing by 1.
291          for (version, _) in N::_CONSENSUS_VERSION_HEIGHTS.iter().skip(1) {
292              assert_eq!(*version as usize, previous_version as usize + 1);
293              previous_version = *version;
294          }
295          // Ensure that the consensus versions are unique and incrementing.
296          let mut previous_version = N::MAX_CERTIFICATES.first().unwrap().0;
297          for (version, _) in N::MAX_CERTIFICATES.iter().skip(1) {
298              assert!(*version > previous_version);
299              previous_version = *version;
300          }
301          let mut previous_version = N::TRANSACTION_SPEND_LIMIT.first().unwrap().0;
302          for (version, _) in N::TRANSACTION_SPEND_LIMIT.iter().skip(1) {
303              assert!(*version > previous_version);
304              previous_version = *version;
305          }
306      }
307  
308      /// Ensure that consensus *heights* are unique and incrementing.
309      fn consensus_constants_increasing_heights<N: Network>() {
310          let mut previous_height = N::CONSENSUS_VERSION_HEIGHTS().first().unwrap().1;
311          for (version, height) in N::CONSENSUS_VERSION_HEIGHTS().iter().skip(1) {
312              assert!(*height > previous_height);
313              previous_height = *height;
314              // Ensure that N::CONSENSUS_VERSION returns the expected value.
315              assert_eq!(N::CONSENSUS_VERSION(*height).unwrap(), *version);
316              // Ensure that N::CONSENSUS_HEIGHT returns the expected value.
317              assert_eq!(N::CONSENSUS_HEIGHT(*version).unwrap(), *height);
318          }
319      }
320  
321      /// Ensure that version of all consensus-relevant constants are present in the consensus version heights.
322      fn consensus_constants_valid_heights<N: Network>() {
323          for (version, value) in N::MAX_CERTIFICATES.iter() {
324              // Ensure that the height at which an update occurs are present in CONSENSUS_VERSION_HEIGHTS.
325              let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
326              // Double-check that consensus_config_value returns the correct value.
327              assert_eq!(consensus_config_value!(N, MAX_CERTIFICATES, height).unwrap(), *value);
328          }
329          for (version, value) in N::TRANSACTION_SPEND_LIMIT.iter() {
330              // Ensure that the height at which an update occurs are present in CONSENSUS_VERSION_HEIGHTS.
331              let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
332              // Double-check that consensus_config_value returns the correct value.
333              assert_eq!(consensus_config_value!(N, TRANSACTION_SPEND_LIMIT, height).unwrap(), *value);
334          }
335      }
336  
337      /// Ensure that consensus_config_value returns a valid value for all consensus versions.
338      fn consensus_config_returns_some<N: Network>() {
339          for (_, height) in N::CONSENSUS_VERSION_HEIGHTS().iter() {
340              assert!(consensus_config_value!(N, MAX_CERTIFICATES, *height).is_some());
341              assert!(consensus_config_value!(N, TRANSACTION_SPEND_LIMIT, *height).is_some());
342          }
343      }
344  
345      /// Ensure that `MAX_CERTIFICATES` increases and is correctly defined.
346      /// See the constant declaration for an explanation why.
347      fn max_certificates_increasing<N: Network>() {
348          let mut previous_value = N::MAX_CERTIFICATES.first().unwrap().1;
349          for (_, value) in N::MAX_CERTIFICATES.iter().skip(1) {
350              assert!(*value >= previous_value);
351              previous_value = *value;
352          }
353      }
354  
355      /// Ensure that the number of constant definitions is the same across networks.
356      fn constants_equal_length<N1: Network, N2: Network, N3: Network>() {
357          // If we can construct an array, that means the underlying types must be the same.
358          let _ = [N1::CONSENSUS_VERSION_HEIGHTS, N2::CONSENSUS_VERSION_HEIGHTS, N3::CONSENSUS_VERSION_HEIGHTS];
359          let _ = [N1::MAX_CERTIFICATES, N2::MAX_CERTIFICATES, N3::MAX_CERTIFICATES];
360          let _ = [N1::TRANSACTION_SPEND_LIMIT, N2::TRANSACTION_SPEND_LIMIT, N3::TRANSACTION_SPEND_LIMIT];
361      }
362  
363      #[test]
364      #[allow(clippy::assertions_on_constants)]
365      fn test_consensus_constants() {
366          consensus_constants_at_genesis::<MainnetV0>();
367          consensus_constants_at_genesis::<TestnetV0>();
368          consensus_constants_at_genesis::<CanaryV0>();
369  
370          consensus_versions::<MainnetV0>();
371          consensus_versions::<TestnetV0>();
372          consensus_versions::<CanaryV0>();
373  
374          consensus_constants_increasing_heights::<MainnetV0>();
375          consensus_constants_increasing_heights::<TestnetV0>();
376          consensus_constants_increasing_heights::<CanaryV0>();
377  
378          consensus_constants_valid_heights::<MainnetV0>();
379          consensus_constants_valid_heights::<TestnetV0>();
380          consensus_constants_valid_heights::<CanaryV0>();
381  
382          consensus_config_returns_some::<MainnetV0>();
383          consensus_config_returns_some::<TestnetV0>();
384          consensus_config_returns_some::<CanaryV0>();
385  
386          max_certificates_increasing::<MainnetV0>();
387          max_certificates_increasing::<TestnetV0>();
388          max_certificates_increasing::<CanaryV0>();
389  
390          constants_equal_length::<MainnetV0, TestnetV0, CanaryV0>();
391      }
392  
393      /// Ensure (de-)serialization works correctly.
394      #[test]
395      fn test_to_bytes() {
396          let version = ConsensusVersion::V8;
397          let bytes = version.to_bytes_le().unwrap();
398          let result = ConsensusVersion::from_bytes_le(&bytes).unwrap();
399          assert_eq!(result, version);
400  
401          let version = ConsensusVersion::latest();
402          let bytes = version.to_bytes_le().unwrap();
403          let result = ConsensusVersion::from_bytes_le(&bytes).unwrap();
404          assert_eq!(result, version);
405  
406          let invalid_bytes = u16::MAX.to_bytes_le().unwrap();
407          let result = ConsensusVersion::from_bytes_le(&invalid_bytes);
408          assert!(result.is_err());
409      }
410  }