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 }