snarkvm_console_network/
consensus_heights.rs

1// Copyright (c) 2019-2025 Provable Inc.
2// This file is part of the snarkVM 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
16use crate::{FromBytes, ToBytes, io_error};
17
18use enum_iterator::{Sequence, last};
19use 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)]
25pub 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
52impl 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
58impl 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
79impl ConsensusVersion {
80    pub fn latest() -> Self {
81        last::<ConsensusVersion>().expect("At least one ConsensusVersion should be defined.")
82    }
83}
84
85impl 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.
93pub(crate) const NUM_CONSENSUS_VERSIONS: usize = enum_iterator::cardinality::<ConsensusVersion>();
94
95/// The consensus version height for `CanaryV0`.
96pub 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`.
112pub 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`.
128pub 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.
144pub 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"))]
160pub 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"))]
166pub(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]
219macro_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]
250macro_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)]
271mod 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}