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 enum_iterator::{Sequence, last};
17
18/// The different consensus versions.
19/// If you need the version active for a specific height, see: `N::CONSENSUS_VERSION`.
20#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Sequence)]
21pub enum ConsensusVersion {
22    /// V1: The initial genesis consensus version.
23    V1 = 1,
24    /// V2: Update to the block reward and execution cost algorithms.
25    V2 = 2,
26    /// V3: Update to the number of validators and finalize scope RNG seed.
27    V3 = 3,
28    /// V4: Update to the Varuna version.
29    V4 = 4,
30    /// V5: Update to the number of validators and enable batch proposal spend limits.
31    V5 = 5,
32    /// V6: Update to the number of validators.
33    V6 = 6,
34    /// V7: Update to program rules.
35    V7 = 7,
36    /// V8: Update to inclusion version, record commitment version, and introduces sender ciphertexts.
37    V8 = 8,
38    /// V9: Support for program upgradability.
39    V9 = 9,
40    /// V10: Support for external records.
41    V10 = 10,
42}
43
44impl ConsensusVersion {
45    pub fn latest() -> Self {
46        last::<ConsensusVersion>().expect("At least one ConsensusVersion should be defined.")
47    }
48}
49
50/// The number of consensus versions.
51pub(crate) const NUM_CONSENSUS_VERSIONS: usize = 10;
52
53/// The consensus version height for `CanaryV0`.
54pub const CANARY_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
55    (ConsensusVersion::V1, 0),
56    (ConsensusVersion::V2, 2_900_000),
57    (ConsensusVersion::V3, 4_560_000),
58    (ConsensusVersion::V4, 5_730_000),
59    (ConsensusVersion::V5, 5_780_000),
60    (ConsensusVersion::V6, 6_240_000),
61    (ConsensusVersion::V7, 6_880_000),
62    (ConsensusVersion::V8, 7_565_000),
63    (ConsensusVersion::V9, 8_028_000),
64    (ConsensusVersion::V10, 8_600_000),
65];
66
67/// The consensus version height for `MainnetV0`.
68pub const MAINNET_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
69    (ConsensusVersion::V1, 0),
70    (ConsensusVersion::V2, 2_800_000),
71    (ConsensusVersion::V3, 4_900_000),
72    (ConsensusVersion::V4, 6_135_000),
73    (ConsensusVersion::V5, 7_060_000),
74    (ConsensusVersion::V6, 7_560_000),
75    (ConsensusVersion::V7, 7_570_000),
76    (ConsensusVersion::V8, 9_430_000),
77    (ConsensusVersion::V9, 10_272_000),
78    (ConsensusVersion::V10, 11_205_000),
79];
80
81/// The consensus version heights for `TestnetV0`.
82pub const TESTNET_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
83    (ConsensusVersion::V1, 0),
84    (ConsensusVersion::V2, 2_950_000),
85    (ConsensusVersion::V3, 4_800_000),
86    (ConsensusVersion::V4, 6_625_000),
87    (ConsensusVersion::V5, 6_765_000),
88    (ConsensusVersion::V6, 7_600_000),
89    (ConsensusVersion::V7, 8_365_000),
90    (ConsensusVersion::V8, 9_173_000),
91    (ConsensusVersion::V9, 9_800_000),
92    (ConsensusVersion::V10, 10_525_000),
93];
94
95/// The consensus version heights when the `test_consensus_heights` feature is enabled.
96pub const TEST_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
97    (ConsensusVersion::V1, 0),
98    (ConsensusVersion::V2, 10),
99    (ConsensusVersion::V3, 11),
100    (ConsensusVersion::V4, 12),
101    (ConsensusVersion::V5, 13),
102    (ConsensusVersion::V6, 14),
103    (ConsensusVersion::V7, 15),
104    (ConsensusVersion::V8, 16),
105    (ConsensusVersion::V9, 17),
106    (ConsensusVersion::V10, 18),
107];
108
109#[cfg(any(test, feature = "test", feature = "test_consensus_heights"))]
110pub fn load_test_consensus_heights() -> [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] {
111    // Define a closure to verify the consensus heights.
112    let verify_consensus_heights = |heights: &[(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS]| {
113        // Assert that the genesis height is 0.
114        assert_eq!(heights[0].1, 0, "Genesis height must be 0.");
115        // Assert that the consensus heights are strictly increasing.
116        for window in heights.windows(2) {
117            if window[0] >= window[1] {
118                panic!("Heights must be strictly increasing, but found: {window:?}");
119            }
120        }
121    };
122
123    // Define consensus version heights container used for testing.
124    let mut test_consensus_heights = TEST_CONSENSUS_VERSION_HEIGHTS;
125
126    // Check if we can read the heights from an environment variable.
127    match std::env::var("CONSENSUS_VERSION_HEIGHTS") {
128        Ok(height_string) => {
129            let parsing_error = format!("Expected exactly {NUM_CONSENSUS_VERSIONS} ConsensusVersion heights.");
130            // Parse the heights from the environment variable.
131            let parsed_test_consensus_heights: [u32; NUM_CONSENSUS_VERSIONS] = height_string
132                .replace(" ", "")
133                .split(",")
134                .map(|height| height.parse::<u32>().expect("Heights should be valid u32 values."))
135                .collect::<Vec<u32>>()
136                .try_into()
137                .expect(&parsing_error);
138            // Set the parsed heights in the test consensus heights.
139            for (i, height) in parsed_test_consensus_heights.into_iter().enumerate() {
140                test_consensus_heights[i] = (TEST_CONSENSUS_VERSION_HEIGHTS[i].0, height);
141            }
142            // Verify and return the parsed test consensus heights.
143            verify_consensus_heights(&test_consensus_heights);
144            test_consensus_heights
145        }
146        Err(_) => {
147            // Verify and return the default test consensus heights.
148            verify_consensus_heights(&test_consensus_heights);
149            test_consensus_heights
150        }
151    }
152}
153
154/// Returns the consensus configuration value for the specified height.
155///
156/// Arguments:
157/// - `$network`: The network to use the constant of.
158/// - `$constant`: The constant to search a value of.
159/// - `$seek_height`: The block height to search the value for.
160#[macro_export]
161macro_rules! consensus_config_value {
162    ($network:ident, $constant:ident, $seek_height:expr) => {
163        // Search the consensus version enacted at the specified height.
164        $network::CONSENSUS_VERSION($seek_height).map_or(None, |seek_version| {
165            // Search the consensus value for the specified version.
166            match $network::$constant.binary_search_by(|(version, _)| version.cmp(&seek_version)) {
167                // If a value was found for this consensus version, return it.
168                Ok(index) => Some($network::$constant[index].1),
169                // If the specified version was not found exactly, determine whether to return an appropriate value anyway.
170                Err(index) => {
171                    // This constant is not yet in effect at this consensus version.
172                    if index == 0 {
173                        None
174                    // Return the appropriate value belonging to the consensus version *lower* than the sought version.
175                    } else {
176                        Some($network::$constant[index - 1].1)
177                    }
178                }
179            }
180        })
181    };
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187    use crate::{CanaryV0, MainnetV0, Network, TestnetV0};
188
189    /// Ensure that the consensus constants are defined and correct at genesis.
190    /// It is possible this invariant no longer holds in the future, e.g. due to pruning or novel types of constants.
191    fn consensus_constants_at_genesis<N: Network>() {
192        let height = N::_CONSENSUS_VERSION_HEIGHTS.first().unwrap().1;
193        assert_eq!(height, 0);
194        let consensus_version = N::_CONSENSUS_VERSION_HEIGHTS.first().unwrap().0;
195        assert_eq!(consensus_version, ConsensusVersion::V1);
196        assert_eq!(consensus_version as usize, 1);
197    }
198
199    /// Ensure that the consensus *versions* are unique, incrementing and start with 1.
200    fn consensus_versions<N: Network>() {
201        let mut previous_version = N::_CONSENSUS_VERSION_HEIGHTS.first().unwrap().0;
202        // Ensure that the consensus versions start with 1.
203        assert_eq!(previous_version as usize, 1);
204        // Ensure that the consensus versions are unique and incrementing by 1.
205        for (version, _) in N::_CONSENSUS_VERSION_HEIGHTS.iter().skip(1) {
206            assert_eq!(*version as usize, previous_version as usize + 1);
207            previous_version = *version;
208        }
209        // Ensure that the consensus versions are unique and incrementing.
210        let mut previous_version = N::MAX_CERTIFICATES.first().unwrap().0;
211        for (version, _) in N::MAX_CERTIFICATES.iter().skip(1) {
212            assert!(*version > previous_version);
213            previous_version = *version;
214        }
215        let mut previous_version = N::TRANSACTION_SPEND_LIMIT.first().unwrap().0;
216        for (version, _) in N::TRANSACTION_SPEND_LIMIT.iter().skip(1) {
217            assert!(*version > previous_version);
218            previous_version = *version;
219        }
220    }
221
222    /// Ensure that consensus *heights* are unique and incrementing.
223    fn consensus_constants_increasing_heights<N: Network>() {
224        let mut previous_height = N::CONSENSUS_VERSION_HEIGHTS().first().unwrap().1;
225        for (version, height) in N::CONSENSUS_VERSION_HEIGHTS().iter().skip(1) {
226            assert!(*height > previous_height);
227            previous_height = *height;
228            // Ensure that N::CONSENSUS_VERSION returns the expected value.
229            assert_eq!(N::CONSENSUS_VERSION(*height).unwrap(), *version);
230            // Ensure that N::CONSENSUS_HEIGHT returns the expected value.
231            assert_eq!(N::CONSENSUS_HEIGHT(*version).unwrap(), *height);
232        }
233    }
234
235    /// Ensure that version of all consensus-relevant constants are present in the consensus version heights.
236    fn consensus_constants_valid_heights<N: Network>() {
237        for (version, value) in N::MAX_CERTIFICATES.iter() {
238            // Ensure that the height at which an update occurs are present in CONSENSUS_VERSION_HEIGHTS.
239            let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
240            // Double-check that consensus_config_value returns the correct value.
241            assert_eq!(consensus_config_value!(N, MAX_CERTIFICATES, height).unwrap(), *value);
242        }
243        for (version, value) in N::TRANSACTION_SPEND_LIMIT.iter() {
244            // Ensure that the height at which an update occurs are present in CONSENSUS_VERSION_HEIGHTS.
245            let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
246            // Double-check that consensus_config_value returns the correct value.
247            assert_eq!(consensus_config_value!(N, TRANSACTION_SPEND_LIMIT, height).unwrap(), *value);
248        }
249    }
250
251    /// Ensure that consensus_config_value returns a valid value for all consensus versions.
252    fn consensus_config_returns_some<N: Network>() {
253        for (_, height) in N::CONSENSUS_VERSION_HEIGHTS().iter() {
254            assert!(consensus_config_value!(N, MAX_CERTIFICATES, *height).is_some());
255            assert!(consensus_config_value!(N, TRANSACTION_SPEND_LIMIT, *height).is_some());
256        }
257    }
258
259    /// Ensure that `MAX_CERTIFICATES` increases and is correctly defined.
260    /// See the constant declaration for an explanation why.
261    fn max_certificates_increasing<N: Network>() {
262        let mut previous_value = N::MAX_CERTIFICATES.first().unwrap().1;
263        for (_, value) in N::MAX_CERTIFICATES.iter().skip(1) {
264            assert!(*value >= previous_value);
265            previous_value = *value;
266        }
267    }
268
269    /// Ensure that the number of constant definitions is the same across networks.
270    fn constants_equal_length<N1: Network, N2: Network, N3: Network>() {
271        // If we can construct an array, that means the underlying types must be the same.
272        let _ = [N1::CONSENSUS_VERSION_HEIGHTS, N2::CONSENSUS_VERSION_HEIGHTS, N3::CONSENSUS_VERSION_HEIGHTS];
273        let _ = [N1::MAX_CERTIFICATES, N2::MAX_CERTIFICATES, N3::MAX_CERTIFICATES];
274        let _ = [N1::TRANSACTION_SPEND_LIMIT, N2::TRANSACTION_SPEND_LIMIT, N3::TRANSACTION_SPEND_LIMIT];
275    }
276
277    #[test]
278    #[allow(clippy::assertions_on_constants)]
279    fn test_consensus_constants() {
280        consensus_constants_at_genesis::<MainnetV0>();
281        consensus_constants_at_genesis::<TestnetV0>();
282        consensus_constants_at_genesis::<CanaryV0>();
283
284        consensus_versions::<MainnetV0>();
285        consensus_versions::<TestnetV0>();
286        consensus_versions::<CanaryV0>();
287
288        consensus_constants_increasing_heights::<MainnetV0>();
289        consensus_constants_increasing_heights::<TestnetV0>();
290        consensus_constants_increasing_heights::<CanaryV0>();
291
292        consensus_constants_valid_heights::<MainnetV0>();
293        consensus_constants_valid_heights::<TestnetV0>();
294        consensus_constants_valid_heights::<CanaryV0>();
295
296        consensus_config_returns_some::<MainnetV0>();
297        consensus_config_returns_some::<TestnetV0>();
298        consensus_config_returns_some::<CanaryV0>();
299
300        max_certificates_increasing::<MainnetV0>();
301        max_certificates_increasing::<TestnetV0>();
302        max_certificates_increasing::<CanaryV0>();
303
304        constants_equal_length::<MainnetV0, TestnetV0, CanaryV0>();
305    }
306
307    #[test]
308    fn test_latest_consensus_version() {
309        // Ensure the test matches the latest ConsensusVersion variant.
310        assert_eq!(ConsensusVersion::latest(), ConsensusVersion::V10); // UPDATE ME, if changed.
311    }
312}