Skip to main content

snarkvm_console_network/
consensus_heights.rs

1// Copyright (c) 2019-2026 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 snarkvm_algorithms::snark::varuna::VarunaVersion;
20use std::io;
21
22/// The different consensus versions.
23/// If you need the version active for a specific height, see: `N::CONSENSUS_VERSION`.
24#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Sequence)]
25#[repr(u16)]
26pub enum ConsensusVersion {
27    /// V1: The initial genesis consensus version.
28    V1 = 1,
29    /// V2: Update to the block reward and execution cost algorithms.
30    V2 = 2,
31    /// V3: Update to the number of validators and finalize scope RNG seed.
32    V3 = 3,
33    /// V4: Update to the Varuna version.
34    V4 = 4,
35    /// V5: Update to the number of validators and enable batch proposal spend limits.
36    V5 = 5,
37    /// V6: Update to the number of validators.
38    V6 = 6,
39    /// V7: Update to program rules.
40    V7 = 7,
41    /// V8: Update to inclusion version, record commitment version, and introduces sender ciphertexts.
42    V8 = 8,
43    /// V9: Support for program upgradability.
44    V9 = 9,
45    /// V10: Lower fees, appropriate record output type checking.
46    V10 = 10,
47    /// V11: Expand array size limit to 512 and introduce ECDSA signature verification opcodes.
48    V11 = 11,
49    /// V12: Prevent connection to forked nodes, disable StringType, enable block timestamp.
50    V12 = 12,
51    /// V13: Introduces external structs.
52    V13 = 13,
53    /// V14: Increase the program size limit to 512 kB, the transaction size limit to 540 kB,
54    ///      the array size limit to 2048, and the `Future` argument bit size to 32 bits.
55    ///      Introduces `aleo::GENERATOR`, `aleo::GENERATOR_POWERS`, `snark.verify` opcodes,
56    ///      and dynamic dispatch, and identifier literal types.
57    V14 = 14,
58    /// V15: Introduces the record-existence check and `commit.*.raw` instruction variants.
59    ///      Increase the anchor time to 35.
60    V15 = 15,
61    /// V16: Moves the block's spend limit check to the finalize phase.
62    ///      Supports storing of transaction rejection reasons.
63    ///      Increase the program size limit to 2048 kB and the transaction size limit to 2304 kB.
64    ///      Update the deployment storage cost for programs exceeding 512 kB.
65    V16 = 16,
66    /// V17: NOTE: V17 landed chronologically on mainnet before it landed on testnet.
67    ///      Reverts the anchor time to 25.
68    V17 = 17,
69    /// V18: Enables native credits record translation, introduces block-wide deployment limits,
70    ///      and enforces canonical subDAG certificate ordering.
71    V18 = 18,
72    /// V19: Reverts from the V18 block-wide synthesis limit to per-transaction
73    ///      deployment variable and constraint limits. The first V19 block still
74    ///      uses the block-wide synthesis limit.
75    V19 = 19,
76    /// V20: Adds more accurate type checking for the root call, and bounds the size of every
77    /// `PlaintextType` declared in a deployed program.
78    V20 = 20,
79    /// V21:  Activates Varuna V3.
80    V21 = 21,
81    /// V22: Increases the maximum number of mappings in a program to 128.
82    ///      Lowers the maximum committee size on testnet to 40.
83    ///      Modifies the cost of the rand.chacha opcode to a more accurate value.
84    V22 = 22,
85}
86
87impl ToBytes for ConsensusVersion {
88    fn write_le<W: io::Write>(&self, writer: W) -> io::Result<()> {
89        (*self as u16).write_le(writer)
90    }
91}
92
93impl FromBytes for ConsensusVersion {
94    fn read_le<R: io::Read>(reader: R) -> io::Result<Self> {
95        match u16::read_le(reader)? {
96            0 => Err(io_error("Zero is not a valid consensus version")),
97            1 => Ok(Self::V1),
98            2 => Ok(Self::V2),
99            3 => Ok(Self::V3),
100            4 => Ok(Self::V4),
101            5 => Ok(Self::V5),
102            6 => Ok(Self::V6),
103            7 => Ok(Self::V7),
104            8 => Ok(Self::V8),
105            9 => Ok(Self::V9),
106            10 => Ok(Self::V10),
107            11 => Ok(Self::V11),
108            12 => Ok(Self::V12),
109            13 => Ok(Self::V13),
110            14 => Ok(Self::V14),
111            15 => Ok(Self::V15),
112            16 => Ok(Self::V16),
113            17 => Ok(Self::V17),
114            18 => Ok(Self::V18),
115            19 => Ok(Self::V19),
116            20 => Ok(Self::V20),
117            21 => Ok(Self::V21),
118            22 => Ok(Self::V22),
119            _ => Err(io_error("Invalid consensus version")),
120        }
121    }
122}
123
124impl ConsensusVersion {
125    pub fn latest() -> Self {
126        last::<ConsensusVersion>().expect("At least one ConsensusVersion should be defined.")
127    }
128}
129
130impl std::fmt::Display for ConsensusVersion {
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        // Use Debug formatting for Display.
133        write!(f, "{self:?}")
134    }
135}
136
137/// The number of consensus versions.
138pub(crate) const NUM_CONSENSUS_VERSIONS: usize = enum_iterator::cardinality::<ConsensusVersion>();
139
140/// The consensus version height for `CanaryV0`.
141pub const CANARY_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
142    (ConsensusVersion::V1, 0),
143    (ConsensusVersion::V2, 2_900_000),
144    (ConsensusVersion::V3, 4_560_000),
145    (ConsensusVersion::V4, 5_730_000),
146    (ConsensusVersion::V5, 5_780_000),
147    (ConsensusVersion::V6, 6_240_000),
148    (ConsensusVersion::V7, 6_880_000),
149    (ConsensusVersion::V8, 7_565_000),
150    (ConsensusVersion::V9, 8_028_000),
151    (ConsensusVersion::V10, 8_600_000),
152    (ConsensusVersion::V11, 9_510_000),
153    (ConsensusVersion::V12, 10_030_000),
154    (ConsensusVersion::V13, 10_881_000),
155    (ConsensusVersion::V14, 11_960_000),
156    (ConsensusVersion::V15, u32::MAX),
157    (ConsensusVersion::V16, u32::MAX),
158    (ConsensusVersion::V17, u32::MAX),
159    (ConsensusVersion::V18, u32::MAX),
160    (ConsensusVersion::V19, u32::MAX),
161    (ConsensusVersion::V20, u32::MAX),
162    (ConsensusVersion::V21, u32::MAX),
163    (ConsensusVersion::V22, u32::MAX),
164];
165
166/// The consensus version height for `MainnetV0`.
167pub const MAINNET_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
168    (ConsensusVersion::V1, 0),
169    (ConsensusVersion::V2, 2_800_000),
170    (ConsensusVersion::V3, 4_900_000),
171    (ConsensusVersion::V4, 6_135_000),
172    (ConsensusVersion::V5, 7_060_000),
173    (ConsensusVersion::V6, 7_560_000),
174    (ConsensusVersion::V7, 7_570_000),
175    (ConsensusVersion::V8, 9_430_000),
176    (ConsensusVersion::V9, 10_272_000),
177    (ConsensusVersion::V10, 11_205_000),
178    (ConsensusVersion::V11, 12_870_000),
179    (ConsensusVersion::V12, 13_815_000),
180    (ConsensusVersion::V13, 16_850_000),
181    (ConsensusVersion::V14, 17_700_000),
182    (ConsensusVersion::V15, 19_264_000),
183    (ConsensusVersion::V16, 19_860_000),
184    (ConsensusVersion::V17, 19_860_001),
185    (ConsensusVersion::V18, 20_794_000),
186    (ConsensusVersion::V19, 21_342_000),
187    (ConsensusVersion::V20, 22_175_000),
188    // Target: October 1, 2026 at 21:00 UTC (2 PM PDT)
189    (ConsensusVersion::V21, 22_437_000),
190    // Target: October 20, 2026 at ~18:00 UTC
191    (ConsensusVersion::V22, 22_962_000),
192];
193
194/// The consensus version heights for `TestnetV0`.
195pub const TESTNET_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
196    (ConsensusVersion::V1, 0),
197    (ConsensusVersion::V2, 2_950_000),
198    (ConsensusVersion::V3, 4_800_000),
199    (ConsensusVersion::V4, 6_625_000),
200    (ConsensusVersion::V5, 6_765_000),
201    (ConsensusVersion::V6, 7_600_000),
202    (ConsensusVersion::V7, 8_365_000),
203    (ConsensusVersion::V8, 9_173_000),
204    (ConsensusVersion::V9, 9_800_000),
205    (ConsensusVersion::V10, 10_525_000),
206    (ConsensusVersion::V11, 11_952_000),
207    (ConsensusVersion::V12, 12_669_000),
208    (ConsensusVersion::V13, 14_906_000),
209    (ConsensusVersion::V14, 15_370_000),
210    (ConsensusVersion::V15, 16_886_000),
211    (ConsensusVersion::V16, 17_319_000),
212    (ConsensusVersion::V17, 18_295_000),
213    (ConsensusVersion::V18, 18_296_000),
214    (ConsensusVersion::V19, 18_813_000),
215    (ConsensusVersion::V20, 19_374_000),
216    (ConsensusVersion::V21, 20_234_000),
217    // Target: October 13, 2026 at ~08:00 UTC (evening of October 12 PT)
218    (ConsensusVersion::V22, 20_415_000),
219];
220
221/// The consensus version heights when the `test_consensus_heights` feature is enabled.
222// We want each to come immediately after the previous one by default for faster testing.
223// Whether activating them all at height 0 is possible is open for investigation.
224// If a test needs to stay on one consensus version for a while, consider just locally testing or using a custom `CONSENSUS_VERSION_HEIGHTS` environment variable.
225pub const TEST_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
226    (ConsensusVersion::V1, 0),
227    (ConsensusVersion::V2, 5),
228    (ConsensusVersion::V3, 6),
229    (ConsensusVersion::V4, 7),
230    (ConsensusVersion::V5, 8),
231    (ConsensusVersion::V6, 9),
232    (ConsensusVersion::V7, 10),
233    (ConsensusVersion::V8, 11),
234    (ConsensusVersion::V9, 12),
235    (ConsensusVersion::V10, 13),
236    (ConsensusVersion::V11, 14),
237    (ConsensusVersion::V12, 15),
238    (ConsensusVersion::V13, 16),
239    (ConsensusVersion::V14, 17),
240    (ConsensusVersion::V15, 18),
241    (ConsensusVersion::V16, 19),
242    (ConsensusVersion::V17, 20),
243    (ConsensusVersion::V18, 21),
244    (ConsensusVersion::V19, 22),
245    (ConsensusVersion::V20, 23),
246    (ConsensusVersion::V21, 24),
247    (ConsensusVersion::V22, 25),
248];
249
250/// Asserts that the given consensus version heights are well-formed.
251///
252/// A height of `u32::MAX` means the version is unscheduled. Scheduled heights strictly increase,
253/// and once a version is unscheduled every later version must also be unscheduled.
254#[cfg(any(test, feature = "test", feature = "test_consensus_heights", feature = "wasm"))]
255pub(crate) fn verify_consensus_heights(heights: &[(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS]) {
256    assert_eq!(heights[0].1, 0, "Genesis height must be 0.");
257    for window in heights.windows(2) {
258        let ((previous_version, previous_height), (version, height)) = (window[0], window[1]);
259        if previous_height == u32::MAX {
260            assert!(
261                height == u32::MAX,
262                "{previous_version:?} is unscheduled, so all later versions must be unscheduled, but {version:?} is at height {height}."
263            );
264        } else {
265            assert!(
266                height > previous_height,
267                "Scheduled heights must strictly increase, but {previous_version:?} is at height {previous_height} and {version:?} is at height {height}."
268            );
269        }
270    }
271}
272
273#[cfg(any(test, feature = "test", feature = "test_consensus_heights"))]
274pub fn load_test_consensus_heights() -> [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] {
275    // Attempt to read the test consensus heights from the environment variable.
276    load_test_consensus_heights_inner(std::env::var("CONSENSUS_VERSION_HEIGHTS").ok())
277}
278
279#[cfg(any(test, feature = "test", feature = "test_consensus_heights", feature = "wasm"))]
280pub(crate) fn load_test_consensus_heights_inner(
281    consensus_version_heights: Option<String>,
282) -> [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] {
283    // Define consensus version heights container used for testing.
284    let mut test_consensus_heights = TEST_CONSENSUS_VERSION_HEIGHTS;
285
286    // If version heights have been specified, verify and return them.
287    match consensus_version_heights {
288        Some(height_string) => {
289            let parsing_error = format!("Expected exactly {NUM_CONSENSUS_VERSIONS} ConsensusVersion heights.");
290            // Parse the heights from the environment variable.
291            let parsed_test_consensus_heights: [u32; NUM_CONSENSUS_VERSIONS] = height_string
292                .replace(" ", "")
293                .split(",")
294                .map(|height| height.parse::<u32>().expect("Heights should be valid u32 values."))
295                .collect::<Vec<u32>>()
296                .try_into()
297                .expect(&parsing_error);
298            // Set the parsed heights in the test consensus heights.
299            for (i, height) in parsed_test_consensus_heights.into_iter().enumerate() {
300                test_consensus_heights[i] = (TEST_CONSENSUS_VERSION_HEIGHTS[i].0, height);
301            }
302            // Verify and return the parsed test consensus heights.
303            verify_consensus_heights(&test_consensus_heights);
304            test_consensus_heights
305        }
306        None => {
307            // Verify and return the default test consensus heights.
308            verify_consensus_heights(&test_consensus_heights);
309            test_consensus_heights
310        }
311    }
312}
313
314/// Returns the consensus configuration value for the specified height.
315///
316/// Arguments:
317/// - `$network`: The network to use the constant of.
318/// - `$constant`: The constant to search a value of.
319/// - `$seek_height`: The block height to search the value for.
320#[macro_export]
321macro_rules! consensus_config_value {
322    ($network:ident, $constant:ident, $seek_height:expr) => {
323        // Search the consensus version enacted at the specified height.
324        $network::CONSENSUS_VERSION($seek_height).map_or(None, |seek_version| {
325            // Search the consensus value for the specified version.
326            // NOTE: calling `consensus_config_value_by_version!` here would require callers to import both macros.
327            match $network::$constant.binary_search_by(|(version, _)| version.cmp(&seek_version)) {
328                // If a value was found for this consensus version, return it.
329                Ok(index) => Some($network::$constant[index].1),
330                // If the specified version was not found exactly, determine whether to return an appropriate value anyway.
331                Err(index) => {
332                    // This constant is not yet in effect at this consensus version.
333                    if index == 0 {
334                        None
335                    // Return the appropriate value belonging to the consensus version *lower* than the sought version.
336                    } else {
337                        Some($network::$constant[index - 1].1)
338                    }
339                }
340            }
341        })
342    };
343}
344
345/// Returns the consensus configuration value for the specified ConsensusVersion.
346///
347/// Arguments:
348/// - `$network`: The network to use the constant of.
349/// - `$constant`: The constant to search a value of.
350/// - `$seek_version`: The ConsensusVersion to search the value for.
351#[macro_export]
352macro_rules! consensus_config_value_by_version {
353    ($network:ident, $constant:ident, $seek_version:expr) => {
354        // Search the consensus value for the specified version.
355        match $network::$constant.binary_search_by(|(version, _)| version.cmp(&$seek_version)) {
356            // If a value was found for this consensus version, return it.
357            Ok(index) => Some($network::$constant[index].1),
358            // If the specified version was not found exactly, determine whether to return an appropriate value anyway.
359            Err(index) => {
360                // This constant is not yet in effect at this consensus version.
361                if index == 0 {
362                    None
363                // Return the appropriate value belonging to the consensus version *lower* than the sought version.
364                } else {
365                    Some($network::$constant[index - 1].1)
366                }
367            }
368        }
369    };
370}
371
372/// Returns the Varuna version for the specified consensus version.
373pub fn varuna_version_from_consensus(consensus_version: ConsensusVersion) -> VarunaVersion {
374    // If new varuna versions are added, test_varuna_version_from_consensus below must be updated accordingly.
375    if consensus_version >= ConsensusVersion::V21 {
376        VarunaVersion::V3
377    } else if consensus_version >= ConsensusVersion::V4 {
378        VarunaVersion::V2
379    } else {
380        VarunaVersion::V1
381    }
382}
383
384#[cfg(test)]
385mod tests {
386    use super::*;
387    use crate::{CanaryV0, MainnetV0, Network, TestnetV0};
388
389    /// Ensure that the consensus constants are defined and correct at genesis.
390    /// It is possible this invariant no longer holds in the future, e.g. due to pruning or novel types of constants.
391    fn consensus_constants_at_genesis<N: Network>() {
392        let height = N::_CONSENSUS_VERSION_HEIGHTS.first().unwrap().1;
393        assert_eq!(height, 0);
394        let consensus_version = N::_CONSENSUS_VERSION_HEIGHTS.first().unwrap().0;
395        assert_eq!(consensus_version, ConsensusVersion::V1);
396        assert_eq!(consensus_version as usize, 1);
397    }
398
399    /// Ensure that the consensus *versions* are unique, incrementing and start with 1.
400    fn consensus_versions<N: Network>() {
401        let mut previous_version = N::_CONSENSUS_VERSION_HEIGHTS.first().unwrap().0;
402        // Ensure that the consensus versions start with 1.
403        assert_eq!(previous_version as usize, 1);
404        // Ensure that the consensus versions are unique and incrementing by 1.
405        for (version, _) in N::_CONSENSUS_VERSION_HEIGHTS.iter().skip(1) {
406            assert_eq!(*version as usize, previous_version as usize + 1);
407            previous_version = *version;
408        }
409        // Ensure that the consensus versions are unique and incrementing.
410        let mut previous_version = N::MAX_CERTIFICATES.first().unwrap().0;
411        for (version, _) in N::MAX_CERTIFICATES.iter().skip(1) {
412            assert!(*version > previous_version);
413            previous_version = *version;
414        }
415        let mut previous_version = N::TRANSACTION_SPEND_LIMIT.first().unwrap().0;
416        for (version, _) in N::TRANSACTION_SPEND_LIMIT.iter().skip(1) {
417            assert!(*version > previous_version);
418            previous_version = *version;
419        }
420        let mut previous_version = N::CREDITS_PER_SECOND_OF_RUNTIME.first().unwrap().0;
421        for (version, _) in N::CREDITS_PER_SECOND_OF_RUNTIME.iter().skip(1) {
422            assert!(*version > previous_version);
423            previous_version = *version;
424        }
425        let mut previous_version = N::MAX_ARRAY_ELEMENTS.first().unwrap().0;
426        for (version, _) in N::MAX_ARRAY_ELEMENTS.iter().skip(1) {
427            assert!(*version > previous_version);
428            previous_version = *version;
429        }
430        let mut previous_version = N::MAX_MAPPINGS.first().unwrap().0;
431        for (version, _) in N::MAX_MAPPINGS.iter().skip(1) {
432            assert!(*version > previous_version);
433            previous_version = *version;
434        }
435        let mut previous_version = N::MAX_PROGRAM_SIZE.first().unwrap().0;
436        for (version, _) in N::MAX_PROGRAM_SIZE.iter().skip(1) {
437            assert!(*version > previous_version);
438            previous_version = *version;
439        }
440        let mut previous_version = N::MAX_TRANSACTION_SIZE.first().unwrap().0;
441        for (version, _) in N::MAX_TRANSACTION_SIZE.iter().skip(1) {
442            assert!(*version > previous_version);
443            previous_version = *version;
444        }
445        let mut previous_version = N::MAX_WRITES.first().unwrap().0;
446        for (version, _) in N::MAX_WRITES.iter().skip(1) {
447            assert!(*version > previous_version);
448            previous_version = *version;
449        }
450        let mut previous_version = N::ANCHOR_TIMES.first().unwrap().0;
451        for (version, _) in N::ANCHOR_TIMES.iter().skip(1) {
452            assert!(*version > previous_version);
453            previous_version = *version;
454        }
455    }
456
457    /// Ensure that consensus *heights* are unique and incrementing.
458    fn consensus_constants_increasing_heights<N: Network>() {
459        let mut previous_height = N::CONSENSUS_VERSION_HEIGHTS().first().unwrap().1;
460        for (version, height) in N::CONSENSUS_VERSION_HEIGHTS().iter().skip(1) {
461            assert!(*height > previous_height);
462            previous_height = *height;
463            // Ensure that N::CONSENSUS_VERSION returns the expected value.
464            assert_eq!(N::CONSENSUS_VERSION(*height).unwrap(), *version);
465            // Ensure that N::CONSENSUS_HEIGHT returns the expected value.
466            assert_eq!(N::CONSENSUS_HEIGHT(*version).unwrap(), *height);
467        }
468    }
469
470    /// Ensure that version of all consensus-relevant constants are present in the consensus version heights.
471    fn consensus_constants_valid_heights<N: Network>() {
472        for (version, value) in N::MAX_CERTIFICATES.iter() {
473            // Ensure that the height at which an update occurs are present in CONSENSUS_VERSION_HEIGHTS.
474            let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
475            // Double-check that consensus_config_value returns the correct value.
476            assert_eq!(consensus_config_value!(N, MAX_CERTIFICATES, height).unwrap(), *value);
477        }
478        for (version, value) in N::TRANSACTION_SPEND_LIMIT.iter() {
479            // Ensure that the height at which an update occurs are present in CONSENSUS_VERSION_HEIGHTS.
480            let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
481            // Double-check that consensus_config_value returns the correct value.
482            assert_eq!(consensus_config_value!(N, TRANSACTION_SPEND_LIMIT, height).unwrap(), *value);
483        }
484        for (version, value) in N::CREDITS_PER_SECOND_OF_RUNTIME.iter() {
485            // Ensure that the height at which an update occurs are present in CONSENSUS_VERSION_HEIGHTS.
486            let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
487            // Double-check that consensus_config_value returns the correct value.
488            assert_eq!(consensus_config_value!(N, CREDITS_PER_SECOND_OF_RUNTIME, height).unwrap(), *value);
489        }
490        for (version, value) in N::MAX_ARRAY_ELEMENTS.iter() {
491            // Ensure that the height at which an update occurs are present in CONSENSUS_VERSION_HEIGHTS.
492            let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
493            // Double-check that consensus_config_value returns the correct value.
494            assert_eq!(consensus_config_value!(N, MAX_ARRAY_ELEMENTS, height).unwrap(), *value);
495        }
496        for (version, value) in N::MAX_MAPPINGS.iter() {
497            // Ensure that the height at which an update occurs are present in CONSENSUS_VERSION_HEIGHTS.
498            let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
499            // Double-check that consensus_config_value returns the correct value.
500            assert_eq!(consensus_config_value!(N, MAX_MAPPINGS, height).unwrap(), *value);
501        }
502        for (version, value) in N::MAX_PROGRAM_SIZE.iter() {
503            // Ensure that the height at which an update occurs are present in CONSENSUS_VERSION_HEIGHTS.
504            let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
505            // Double-check that consensus_config_value returns the correct value.
506            assert_eq!(consensus_config_value!(N, MAX_PROGRAM_SIZE, height).unwrap(), *value);
507        }
508        for (version, value) in N::MAX_TRANSACTION_SIZE.iter() {
509            // Ensure that the height at which an update occurs are present in CONSENSUS_VERSION_HEIGHTS.
510            let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
511            // Double-check that consensus_config_value returns the correct value.
512            assert_eq!(consensus_config_value!(N, MAX_TRANSACTION_SIZE, height).unwrap(), *value);
513        }
514        for (version, value) in N::MAX_WRITES.iter() {
515            // Ensure that the height at which an update occurs are present in CONSENSUS_VERSION_HEIGHTS.
516            let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
517            // Double-check that consensus_config_value returns the correct value.
518            assert_eq!(consensus_config_value!(N, MAX_WRITES, height).unwrap(), *value);
519        }
520        for (version, value) in N::ANCHOR_TIMES.iter() {
521            let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| c_version == version).unwrap().1;
522            assert_eq!(consensus_config_value!(N, ANCHOR_TIMES, height).unwrap(), *value);
523        }
524    }
525
526    /// Ensure that consensus_config_value returns a valid value for all consensus versions.
527    fn consensus_config_returns_some<N: Network>() {
528        for (_, height) in N::CONSENSUS_VERSION_HEIGHTS().iter() {
529            assert!(consensus_config_value!(N, MAX_CERTIFICATES, *height).is_some());
530            assert!(consensus_config_value!(N, TRANSACTION_SPEND_LIMIT, *height).is_some());
531            assert!(consensus_config_value!(N, CREDITS_PER_SECOND_OF_RUNTIME, *height).is_some());
532            assert!(consensus_config_value!(N, MAX_ARRAY_ELEMENTS, *height).is_some());
533            assert!(consensus_config_value!(N, MAX_MAPPINGS, *height).is_some());
534            assert!(consensus_config_value!(N, MAX_PROGRAM_SIZE, *height).is_some());
535            assert!(consensus_config_value!(N, MAX_TRANSACTION_SIZE, *height).is_some());
536            assert!(consensus_config_value!(N, MAX_WRITES, *height).is_some());
537            assert!(consensus_config_value!(N, ANCHOR_TIMES, *height).is_some());
538        }
539    }
540
541    /// Ensure that `MAX_CERTIFICATES` increases and is correctly defined.
542    /// See the constant declaration for an explanation why.
543    /// The versions in `exempt_versions` are permitted to decrease the value.
544    fn max_certificates_increasing<N: Network>(exempt_versions: &[ConsensusVersion]) {
545        let mut previous_value = N::MAX_CERTIFICATES.first().unwrap().1;
546        for (version, value) in N::MAX_CERTIFICATES.iter().skip(1) {
547            if !exempt_versions.contains(version) {
548                assert!(*value >= previous_value, "MAX_CERTIFICATES must not decrease at {version:?}");
549            }
550            previous_value = *value;
551        }
552    }
553
554    /// Ensure that `MAX_ARRAY_ELEMENTS` increases and is correctly defined.
555    /// See the constant declaration for an explanation why.
556    fn max_array_elements_increasing<N: Network>() {
557        let mut previous_value = N::MAX_ARRAY_ELEMENTS.first().unwrap().1;
558        for (_, value) in N::MAX_ARRAY_ELEMENTS.iter().skip(1) {
559            assert!(*value >= previous_value);
560            previous_value = *value;
561        }
562    }
563
564    /// Ensure that `MAX_MAPPINGS` increases and is correctly defined.
565    fn max_mappings_increasing<N: Network>() {
566        let mut previous_value = N::MAX_MAPPINGS.first().unwrap().1;
567        for (_, value) in N::MAX_MAPPINGS.iter().skip(1) {
568            assert!(*value >= previous_value);
569            previous_value = *value;
570        }
571    }
572
573    /// Ensure that `MAX_TRANSACTION_SIZE` is at least 28KB greater than `MAX_PROGRAM_SIZE` for all consensus versions.
574    /// This overhead accounts for proofs, signatures, and other transaction metadata.
575    fn transaction_size_exceeds_program_size<N: Network>() {
576        const MIN_OVERHEAD: usize = 28_000; // 28 kB minimum overhead
577
578        for (_, height) in N::CONSENSUS_VERSION_HEIGHTS().iter() {
579            let max_program_size = consensus_config_value!(N, MAX_PROGRAM_SIZE, *height).unwrap();
580            let max_transaction_size = consensus_config_value!(N, MAX_TRANSACTION_SIZE, *height).unwrap();
581
582            assert!(
583                max_transaction_size >= max_program_size + MIN_OVERHEAD,
584                "At height {height}: MAX_TRANSACTION_SIZE ({max_transaction_size}) must be at least {MIN_OVERHEAD} bytes greater than MAX_PROGRAM_SIZE ({max_program_size})"
585            );
586        }
587    }
588
589    /// Ensure that `MAX_PROGRAM_SIZE` and `MAX_TRANSACTION_SIZE` are defined in lockstep:
590    /// the same number of entries, keyed by the same consensus versions in the same order, with the
591    /// transaction size strictly exceeding the program size at every version. The latest transaction
592    /// size must likewise exceed the latest program size, since a transaction must hold a program
593    /// plus its proofs, signatures, and metadata.
594    fn program_and_transaction_size_aligned<N: Network>() {
595        // Ensure both constants define the same number of entries.
596        assert_eq!(
597            N::MAX_PROGRAM_SIZE.len(),
598            N::MAX_TRANSACTION_SIZE.len(),
599            "MAX_PROGRAM_SIZE and MAX_TRANSACTION_SIZE must define the same number of entries"
600        );
601        // Ensure both constants are keyed by the same consensus versions, in the same order, and that
602        // the transaction size exceeds the program size at each corresponding version.
603        for (index, (program_version, program_size)) in N::MAX_PROGRAM_SIZE.iter().enumerate() {
604            let (transaction_version, transaction_size) = &N::MAX_TRANSACTION_SIZE[index];
605            assert_eq!(
606                program_version, transaction_version,
607                "MAX_PROGRAM_SIZE and MAX_TRANSACTION_SIZE must be keyed by the same consensus version at index {index}, but found {program_version} and {transaction_version}"
608            );
609            assert!(
610                transaction_size > program_size,
611                "At consensus version {program_version}: MAX_TRANSACTION_SIZE ({transaction_size}) must be greater than MAX_PROGRAM_SIZE ({program_size})"
612            );
613        }
614        // Ensure the latest transaction size exceeds the latest program size.
615        assert!(
616            N::LATEST_MAX_TRANSACTION_SIZE() > N::LATEST_MAX_PROGRAM_SIZE(),
617            "LATEST_MAX_TRANSACTION_SIZE ({}) must be greater than LATEST_MAX_PROGRAM_SIZE ({})",
618            N::LATEST_MAX_TRANSACTION_SIZE(),
619            N::LATEST_MAX_PROGRAM_SIZE()
620        );
621    }
622
623    /// Ensure that the number of constant definitions is the same across networks.
624    fn constants_equal_length<N1: Network, N2: Network, N3: Network>() {
625        // If we can construct an array, that means the underlying types must be the same.
626        let _ = [N1::CONSENSUS_VERSION_HEIGHTS, N2::CONSENSUS_VERSION_HEIGHTS, N3::CONSENSUS_VERSION_HEIGHTS];
627        let _ = [N1::MAX_CERTIFICATES, N2::MAX_CERTIFICATES, N3::MAX_CERTIFICATES];
628        let _ = [N1::TRANSACTION_SPEND_LIMIT, N2::TRANSACTION_SPEND_LIMIT, N3::TRANSACTION_SPEND_LIMIT];
629        let _ =
630            [N1::CREDITS_PER_SECOND_OF_RUNTIME, N2::CREDITS_PER_SECOND_OF_RUNTIME, N3::CREDITS_PER_SECOND_OF_RUNTIME];
631        let _ = [N1::MAX_ARRAY_ELEMENTS, N2::MAX_ARRAY_ELEMENTS, N3::MAX_ARRAY_ELEMENTS];
632        let _ = [N1::MAX_MAPPINGS, N2::MAX_MAPPINGS, N3::MAX_MAPPINGS];
633        let _ = [N1::MAX_PROGRAM_SIZE, N2::MAX_PROGRAM_SIZE, N3::MAX_PROGRAM_SIZE];
634        let _ = [N1::MAX_TRANSACTION_SIZE, N2::MAX_TRANSACTION_SIZE, N3::MAX_TRANSACTION_SIZE];
635        let _ = [N1::MAX_WRITES, N2::MAX_WRITES, N3::MAX_WRITES];
636        let _ = [N1::ANCHOR_TIMES, N2::ANCHOR_TIMES, N3::ANCHOR_TIMES];
637    }
638
639    /// Ensure that `LATEST_MAX_*` functions return valid values without panicking.
640    /// These functions use `.expect()` internally, so this test verifies the arrays are non-empty.
641    fn latest_max_functions_are_safe<N: Network>() {
642        // Verify LATEST_MAX_CERTIFICATES returns a positive value.
643        assert!(N::LATEST_MAX_CERTIFICATES() > 0, "LATEST_MAX_CERTIFICATES must be positive");
644        // Verify LATEST_MAX_MAPPINGS returns a positive value.
645        assert!(N::LATEST_MAX_MAPPINGS() > 0, "LATEST_MAX_MAPPINGS must be positive");
646        // Verify LATEST_MAX_PROGRAM_SIZE returns a positive value.
647        assert!(N::LATEST_MAX_PROGRAM_SIZE() > 0, "LATEST_MAX_PROGRAM_SIZE must be positive");
648        // Verify LATEST_MAX_TRANSACTION_SIZE returns a positive value.
649        assert!(N::LATEST_MAX_TRANSACTION_SIZE() > 0, "LATEST_MAX_TRANSACTION_SIZE must be positive");
650        // Verify LATEST_MAX_WRITES returns a positive value.
651        assert!(N::LATEST_MAX_WRITES() > 0, "LATEST_MAX_WRITES must be positive");
652    }
653
654    #[test]
655    #[allow(clippy::assertions_on_constants)]
656    fn test_consensus_constants() {
657        consensus_constants_at_genesis::<MainnetV0>();
658        consensus_constants_at_genesis::<TestnetV0>();
659        consensus_constants_at_genesis::<CanaryV0>();
660
661        consensus_versions::<MainnetV0>();
662        consensus_versions::<TestnetV0>();
663        consensus_versions::<CanaryV0>();
664
665        consensus_constants_increasing_heights::<MainnetV0>();
666        consensus_constants_increasing_heights::<TestnetV0>();
667        consensus_constants_increasing_heights::<CanaryV0>();
668
669        consensus_constants_valid_heights::<MainnetV0>();
670        consensus_constants_valid_heights::<TestnetV0>();
671        consensus_constants_valid_heights::<CanaryV0>();
672
673        consensus_config_returns_some::<MainnetV0>();
674        consensus_config_returns_some::<TestnetV0>();
675        consensus_config_returns_some::<CanaryV0>();
676
677        max_certificates_increasing::<MainnetV0>(&[]);
678        // Testnet lowers the maximum committee size at `V22`.
679        max_certificates_increasing::<TestnetV0>(&[ConsensusVersion::V22]);
680        max_certificates_increasing::<CanaryV0>(&[]);
681
682        max_array_elements_increasing::<MainnetV0>();
683        max_array_elements_increasing::<TestnetV0>();
684        max_array_elements_increasing::<CanaryV0>();
685
686        max_mappings_increasing::<MainnetV0>();
687        max_mappings_increasing::<TestnetV0>();
688        max_mappings_increasing::<CanaryV0>();
689
690        transaction_size_exceeds_program_size::<MainnetV0>();
691        transaction_size_exceeds_program_size::<TestnetV0>();
692        transaction_size_exceeds_program_size::<CanaryV0>();
693
694        program_and_transaction_size_aligned::<MainnetV0>();
695        program_and_transaction_size_aligned::<TestnetV0>();
696        program_and_transaction_size_aligned::<CanaryV0>();
697
698        latest_max_functions_are_safe::<MainnetV0>();
699        latest_max_functions_are_safe::<TestnetV0>();
700        latest_max_functions_are_safe::<CanaryV0>();
701
702        constants_equal_length::<MainnetV0, TestnetV0, CanaryV0>();
703    }
704
705    /// Ensure (de-)serialization works correctly.
706    #[test]
707    fn test_to_bytes() {
708        let version = ConsensusVersion::V8;
709        let bytes = version.to_bytes_le().unwrap();
710        let result = ConsensusVersion::from_bytes_le(&bytes).unwrap();
711        assert_eq!(result, version);
712
713        let version = ConsensusVersion::latest();
714        let bytes = version.to_bytes_le().unwrap();
715        let result = ConsensusVersion::from_bytes_le(&bytes).unwrap();
716        assert_eq!(result, version);
717
718        let invalid_bytes = u16::MAX.to_bytes_le().unwrap();
719        let result = ConsensusVersion::from_bytes_le(&invalid_bytes);
720        assert!(result.is_err());
721    }
722
723    #[test]
724    fn test_reward_anchor_time() {
725        assert_eq!(MainnetV0::REWARD_ANCHOR_TIME, MainnetV0::ANCHOR_TIMES.first().unwrap().1);
726        assert_eq!(TestnetV0::REWARD_ANCHOR_TIME, TestnetV0::ANCHOR_TIMES.first().unwrap().1);
727        assert_eq!(CanaryV0::REWARD_ANCHOR_TIME, CanaryV0::ANCHOR_TIMES.first().unwrap().1);
728    }
729
730    #[test]
731    fn test_varuna_version_from_consensus() {
732        // Check that all consensus versions map to a valid Varuna version.
733        for consensus_version in enum_iterator::all::<ConsensusVersion>() {
734            let varuna_version = varuna_version_from_consensus(consensus_version);
735            let valid = match varuna_version {
736                VarunaVersion::V1 => consensus_version < ConsensusVersion::V4,
737                VarunaVersion::V2 => (ConsensusVersion::V4..ConsensusVersion::V21).contains(&consensus_version),
738                VarunaVersion::V3 => consensus_version >= ConsensusVersion::V21,
739            };
740            assert!(valid, "{consensus_version:?} incorrectly maps to {varuna_version:?}");
741        }
742
743        // Add spot checks.
744        assert_eq!(varuna_version_from_consensus(ConsensusVersion::V3), VarunaVersion::V1);
745        assert_eq!(varuna_version_from_consensus(ConsensusVersion::V4), VarunaVersion::V2);
746        assert_eq!(varuna_version_from_consensus(ConsensusVersion::V20), VarunaVersion::V2);
747        assert_eq!(varuna_version_from_consensus(ConsensusVersion::V21), VarunaVersion::V3);
748        assert_eq!(varuna_version_from_consensus(ConsensusVersion::V22), VarunaVersion::V3);
749    }
750
751    /// Ensure that every published consensus height table is well-formed.
752    #[test]
753    fn test_published_consensus_heights_are_valid() {
754        verify_consensus_heights(&CANARY_V0_CONSENSUS_VERSION_HEIGHTS);
755        verify_consensus_heights(&MAINNET_V0_CONSENSUS_VERSION_HEIGHTS);
756        verify_consensus_heights(&TESTNET_V0_CONSENSUS_VERSION_HEIGHTS);
757        verify_consensus_heights(&TEST_CONSENSUS_VERSION_HEIGHTS);
758    }
759
760    /// Renders the default test heights as a `CONSENSUS_VERSION_HEIGHTS` string, with the given overrides applied.
761    fn test_heights_string(overrides: &[(usize, u32)]) -> String {
762        let mut heights = TEST_CONSENSUS_VERSION_HEIGHTS.map(|(_, height)| height);
763        for (index, height) in overrides {
764            heights[*index] = *height;
765        }
766        heights.iter().map(u32::to_string).collect::<Vec<_>>().join(",")
767    }
768
769    /// Ensure that a decreasing height is rejected.
770    #[test]
771    #[should_panic(expected = "must strictly increase")]
772    fn test_consensus_heights_reject_decreasing() {
773        load_test_consensus_heights_inner(Some(test_heights_string(&[(2, 4)])));
774    }
775
776    /// Ensure that a duplicated height is rejected.
777    #[test]
778    #[should_panic(expected = "must strictly increase")]
779    fn test_consensus_heights_reject_duplicate() {
780        load_test_consensus_heights_inner(Some(test_heights_string(&[(2, 5)])));
781    }
782
783    /// Ensure that a scheduled version following an unscheduled one is rejected.
784    #[test]
785    #[should_panic(expected = "all later versions must be unscheduled")]
786    fn test_consensus_heights_reject_scheduled_after_unscheduled() {
787        load_test_consensus_heights_inner(Some(test_heights_string(&[(18, u32::MAX)])));
788    }
789
790    /// Ensure that a trailing run of unscheduled versions is accepted.
791    #[test]
792    fn test_consensus_heights_accept_trailing_unscheduled() {
793        let heights = load_test_consensus_heights_inner(Some(test_heights_string(&[
794            (18, u32::MAX),
795            (19, u32::MAX),
796            (20, u32::MAX),
797            (21, u32::MAX),
798        ])));
799        assert_eq!(heights[17].1, 21);
800        assert_eq!(heights[18].1, u32::MAX);
801        assert_eq!(heights[19].1, u32::MAX);
802        assert_eq!(heights[20].1, u32::MAX);
803        assert_eq!(heights[21].1, u32::MAX);
804    }
805}