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}
77
78impl ToBytes for ConsensusVersion {
79    fn write_le<W: io::Write>(&self, writer: W) -> io::Result<()> {
80        (*self as u16).write_le(writer)
81    }
82}
83
84impl FromBytes for ConsensusVersion {
85    fn read_le<R: io::Read>(reader: R) -> io::Result<Self> {
86        match u16::read_le(reader)? {
87            0 => Err(io_error("Zero is not a valid consensus version")),
88            1 => Ok(Self::V1),
89            2 => Ok(Self::V2),
90            3 => Ok(Self::V3),
91            4 => Ok(Self::V4),
92            5 => Ok(Self::V5),
93            6 => Ok(Self::V6),
94            7 => Ok(Self::V7),
95            8 => Ok(Self::V8),
96            9 => Ok(Self::V9),
97            10 => Ok(Self::V10),
98            11 => Ok(Self::V11),
99            12 => Ok(Self::V12),
100            13 => Ok(Self::V13),
101            14 => Ok(Self::V14),
102            15 => Ok(Self::V15),
103            16 => Ok(Self::V16),
104            17 => Ok(Self::V17),
105            18 => Ok(Self::V18),
106            19 => Ok(Self::V19),
107            _ => Err(io_error("Invalid consensus version")),
108        }
109    }
110}
111
112impl ConsensusVersion {
113    pub fn latest() -> Self {
114        last::<ConsensusVersion>().expect("At least one ConsensusVersion should be defined.")
115    }
116}
117
118impl std::fmt::Display for ConsensusVersion {
119    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120        // Use Debug formatting for Display.
121        write!(f, "{self:?}")
122    }
123}
124
125/// The number of consensus versions.
126pub(crate) const NUM_CONSENSUS_VERSIONS: usize = enum_iterator::cardinality::<ConsensusVersion>();
127
128/// The consensus version height for `CanaryV0`.
129pub const CANARY_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
130    (ConsensusVersion::V1, 0),
131    (ConsensusVersion::V2, 2_900_000),
132    (ConsensusVersion::V3, 4_560_000),
133    (ConsensusVersion::V4, 5_730_000),
134    (ConsensusVersion::V5, 5_780_000),
135    (ConsensusVersion::V6, 6_240_000),
136    (ConsensusVersion::V7, 6_880_000),
137    (ConsensusVersion::V8, 7_565_000),
138    (ConsensusVersion::V9, 8_028_000),
139    (ConsensusVersion::V10, 8_600_000),
140    (ConsensusVersion::V11, 9_510_000),
141    (ConsensusVersion::V12, 10_030_000),
142    (ConsensusVersion::V13, 10_881_000),
143    (ConsensusVersion::V14, 11_960_000),
144    (ConsensusVersion::V15, u32::MAX),
145    (ConsensusVersion::V16, u32::MAX),
146    (ConsensusVersion::V17, u32::MAX),
147    (ConsensusVersion::V18, u32::MAX),
148    (ConsensusVersion::V19, u32::MAX),
149];
150
151/// The consensus version height for `MainnetV0`.
152pub const MAINNET_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
153    (ConsensusVersion::V1, 0),
154    (ConsensusVersion::V2, 2_800_000),
155    (ConsensusVersion::V3, 4_900_000),
156    (ConsensusVersion::V4, 6_135_000),
157    (ConsensusVersion::V5, 7_060_000),
158    (ConsensusVersion::V6, 7_560_000),
159    (ConsensusVersion::V7, 7_570_000),
160    (ConsensusVersion::V8, 9_430_000),
161    (ConsensusVersion::V9, 10_272_000),
162    (ConsensusVersion::V10, 11_205_000),
163    (ConsensusVersion::V11, 12_870_000),
164    (ConsensusVersion::V12, 13_815_000),
165    (ConsensusVersion::V13, 16_850_000),
166    (ConsensusVersion::V14, 17_700_000),
167    (ConsensusVersion::V15, 19_264_000),
168    (ConsensusVersion::V16, 19_860_000),
169    (ConsensusVersion::V17, 19_860_001),
170    (ConsensusVersion::V18, 20_794_000),
171    (ConsensusVersion::V19, 21_342_000),
172];
173
174/// The consensus version heights for `TestnetV0`.
175pub const TESTNET_V0_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
176    (ConsensusVersion::V1, 0),
177    (ConsensusVersion::V2, 2_950_000),
178    (ConsensusVersion::V3, 4_800_000),
179    (ConsensusVersion::V4, 6_625_000),
180    (ConsensusVersion::V5, 6_765_000),
181    (ConsensusVersion::V6, 7_600_000),
182    (ConsensusVersion::V7, 8_365_000),
183    (ConsensusVersion::V8, 9_173_000),
184    (ConsensusVersion::V9, 9_800_000),
185    (ConsensusVersion::V10, 10_525_000),
186    (ConsensusVersion::V11, 11_952_000),
187    (ConsensusVersion::V12, 12_669_000),
188    (ConsensusVersion::V13, 14_906_000),
189    (ConsensusVersion::V14, 15_370_000),
190    (ConsensusVersion::V15, 16_886_000),
191    (ConsensusVersion::V16, 17_319_000),
192    (ConsensusVersion::V17, 18_295_000),
193    (ConsensusVersion::V18, 18_296_000),
194    (ConsensusVersion::V19, 18_813_000),
195];
196
197/// The consensus version heights when the `test_consensus_heights` feature is enabled.
198// We want each to come immediately after the previous one by default for faster testing.
199// Whether activating them all at height 0 is possible is open for investigation.
200// 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.
201pub const TEST_CONSENSUS_VERSION_HEIGHTS: [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] = [
202    (ConsensusVersion::V1, 0),
203    (ConsensusVersion::V2, 5),
204    (ConsensusVersion::V3, 6),
205    (ConsensusVersion::V4, 7),
206    (ConsensusVersion::V5, 8),
207    (ConsensusVersion::V6, 9),
208    (ConsensusVersion::V7, 10),
209    (ConsensusVersion::V8, 11),
210    (ConsensusVersion::V9, 12),
211    (ConsensusVersion::V10, 13),
212    (ConsensusVersion::V11, 14),
213    (ConsensusVersion::V12, 15),
214    (ConsensusVersion::V13, 16),
215    (ConsensusVersion::V14, 17),
216    (ConsensusVersion::V15, 18),
217    (ConsensusVersion::V16, 19),
218    (ConsensusVersion::V17, 20),
219    (ConsensusVersion::V18, 21),
220    (ConsensusVersion::V19, 22),
221];
222
223#[cfg(any(test, feature = "test", feature = "test_consensus_heights"))]
224pub fn load_test_consensus_heights() -> [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] {
225    // Attempt to read the test consensus heights from the environment variable.
226    load_test_consensus_heights_inner(std::env::var("CONSENSUS_VERSION_HEIGHTS").ok())
227}
228
229#[cfg(any(test, feature = "test", feature = "test_consensus_heights", feature = "wasm"))]
230pub(crate) fn load_test_consensus_heights_inner(
231    consensus_version_heights: Option<String>,
232) -> [(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS] {
233    // Define a closure to verify the consensus heights.
234    let verify_consensus_heights = |heights: &[(ConsensusVersion, u32); NUM_CONSENSUS_VERSIONS]| {
235        // Assert that the genesis height is 0.
236        assert_eq!(heights[0].1, 0, "Genesis height must be 0.");
237        // Assert that the consensus heights are strictly increasing.
238        for window in heights.windows(2) {
239            if window[0] >= window[1] {
240                panic!("Heights must be strictly increasing, but found: {window:?}");
241            }
242        }
243    };
244
245    // Define consensus version heights container used for testing.
246    let mut test_consensus_heights = TEST_CONSENSUS_VERSION_HEIGHTS;
247
248    // If version heights have been specified, verify and return them.
249    match consensus_version_heights {
250        Some(height_string) => {
251            let parsing_error = format!("Expected exactly {NUM_CONSENSUS_VERSIONS} ConsensusVersion heights.");
252            // Parse the heights from the environment variable.
253            let parsed_test_consensus_heights: [u32; NUM_CONSENSUS_VERSIONS] = height_string
254                .replace(" ", "")
255                .split(",")
256                .map(|height| height.parse::<u32>().expect("Heights should be valid u32 values."))
257                .collect::<Vec<u32>>()
258                .try_into()
259                .expect(&parsing_error);
260            // Set the parsed heights in the test consensus heights.
261            for (i, height) in parsed_test_consensus_heights.into_iter().enumerate() {
262                test_consensus_heights[i] = (TEST_CONSENSUS_VERSION_HEIGHTS[i].0, height);
263            }
264            // Verify and return the parsed test consensus heights.
265            verify_consensus_heights(&test_consensus_heights);
266            test_consensus_heights
267        }
268        None => {
269            // Verify and return the default test consensus heights.
270            verify_consensus_heights(&test_consensus_heights);
271            test_consensus_heights
272        }
273    }
274}
275
276/// Returns the consensus configuration value for the specified height.
277///
278/// Arguments:
279/// - `$network`: The network to use the constant of.
280/// - `$constant`: The constant to search a value of.
281/// - `$seek_height`: The block height to search the value for.
282#[macro_export]
283macro_rules! consensus_config_value {
284    ($network:ident, $constant:ident, $seek_height:expr) => {
285        // Search the consensus version enacted at the specified height.
286        $network::CONSENSUS_VERSION($seek_height).map_or(None, |seek_version| {
287            // Search the consensus value for the specified version.
288            // NOTE: calling `consensus_config_value_by_version!` here would require callers to import both macros.
289            match $network::$constant.binary_search_by(|(version, _)| version.cmp(&seek_version)) {
290                // If a value was found for this consensus version, return it.
291                Ok(index) => Some($network::$constant[index].1),
292                // If the specified version was not found exactly, determine whether to return an appropriate value anyway.
293                Err(index) => {
294                    // This constant is not yet in effect at this consensus version.
295                    if index == 0 {
296                        None
297                    // Return the appropriate value belonging to the consensus version *lower* than the sought version.
298                    } else {
299                        Some($network::$constant[index - 1].1)
300                    }
301                }
302            }
303        })
304    };
305}
306
307/// Returns the consensus configuration value for the specified ConsensusVersion.
308///
309/// Arguments:
310/// - `$network`: The network to use the constant of.
311/// - `$constant`: The constant to search a value of.
312/// - `$seek_version`: The ConsensusVersion to search the value for.
313#[macro_export]
314macro_rules! consensus_config_value_by_version {
315    ($network:ident, $constant:ident, $seek_version:expr) => {
316        // Search the consensus value for the specified version.
317        match $network::$constant.binary_search_by(|(version, _)| version.cmp(&$seek_version)) {
318            // If a value was found for this consensus version, return it.
319            Ok(index) => Some($network::$constant[index].1),
320            // If the specified version was not found exactly, determine whether to return an appropriate value anyway.
321            Err(index) => {
322                // This constant is not yet in effect at this consensus version.
323                if index == 0 {
324                    None
325                // Return the appropriate value belonging to the consensus version *lower* than the sought version.
326                } else {
327                    Some($network::$constant[index - 1].1)
328                }
329            }
330        }
331    };
332}
333
334/// Returns the Varuna version for the specified consensus version.
335pub fn varuna_version_from_consensus(consensus_version: ConsensusVersion) -> VarunaVersion {
336    // If new varuna versions are added, test_varuna_version_from_consensus below must be updated accordingly.
337    if consensus_version >= ConsensusVersion::V4 { VarunaVersion::V2 } else { VarunaVersion::V1 }
338}
339
340#[cfg(test)]
341mod tests {
342    use super::*;
343    use crate::{CanaryV0, MainnetV0, Network, TestnetV0};
344
345    /// Ensure that the consensus constants are defined and correct at genesis.
346    /// It is possible this invariant no longer holds in the future, e.g. due to pruning or novel types of constants.
347    fn consensus_constants_at_genesis<N: Network>() {
348        let height = N::_CONSENSUS_VERSION_HEIGHTS.first().unwrap().1;
349        assert_eq!(height, 0);
350        let consensus_version = N::_CONSENSUS_VERSION_HEIGHTS.first().unwrap().0;
351        assert_eq!(consensus_version, ConsensusVersion::V1);
352        assert_eq!(consensus_version as usize, 1);
353    }
354
355    /// Ensure that the consensus *versions* are unique, incrementing and start with 1.
356    fn consensus_versions<N: Network>() {
357        let mut previous_version = N::_CONSENSUS_VERSION_HEIGHTS.first().unwrap().0;
358        // Ensure that the consensus versions start with 1.
359        assert_eq!(previous_version as usize, 1);
360        // Ensure that the consensus versions are unique and incrementing by 1.
361        for (version, _) in N::_CONSENSUS_VERSION_HEIGHTS.iter().skip(1) {
362            assert_eq!(*version as usize, previous_version as usize + 1);
363            previous_version = *version;
364        }
365        // Ensure that the consensus versions are unique and incrementing.
366        let mut previous_version = N::MAX_CERTIFICATES.first().unwrap().0;
367        for (version, _) in N::MAX_CERTIFICATES.iter().skip(1) {
368            assert!(*version > previous_version);
369            previous_version = *version;
370        }
371        let mut previous_version = N::TRANSACTION_SPEND_LIMIT.first().unwrap().0;
372        for (version, _) in N::TRANSACTION_SPEND_LIMIT.iter().skip(1) {
373            assert!(*version > previous_version);
374            previous_version = *version;
375        }
376        let mut previous_version = N::CREDITS_PER_SECOND_OF_RUNTIME.first().unwrap().0;
377        for (version, _) in N::CREDITS_PER_SECOND_OF_RUNTIME.iter().skip(1) {
378            assert!(*version > previous_version);
379            previous_version = *version;
380        }
381        let mut previous_version = N::MAX_ARRAY_ELEMENTS.first().unwrap().0;
382        for (version, _) in N::MAX_ARRAY_ELEMENTS.iter().skip(1) {
383            assert!(*version > previous_version);
384            previous_version = *version;
385        }
386        let mut previous_version = N::MAX_PROGRAM_SIZE.first().unwrap().0;
387        for (version, _) in N::MAX_PROGRAM_SIZE.iter().skip(1) {
388            assert!(*version > previous_version);
389            previous_version = *version;
390        }
391        let mut previous_version = N::MAX_TRANSACTION_SIZE.first().unwrap().0;
392        for (version, _) in N::MAX_TRANSACTION_SIZE.iter().skip(1) {
393            assert!(*version > previous_version);
394            previous_version = *version;
395        }
396        let mut previous_version = N::MAX_WRITES.first().unwrap().0;
397        for (version, _) in N::MAX_WRITES.iter().skip(1) {
398            assert!(*version > previous_version);
399            previous_version = *version;
400        }
401        let mut previous_version = N::ANCHOR_TIMES.first().unwrap().0;
402        for (version, _) in N::ANCHOR_TIMES.iter().skip(1) {
403            assert!(*version > previous_version);
404            previous_version = *version;
405        }
406    }
407
408    /// Ensure that consensus *heights* are unique and incrementing.
409    fn consensus_constants_increasing_heights<N: Network>() {
410        let mut previous_height = N::CONSENSUS_VERSION_HEIGHTS().first().unwrap().1;
411        for (version, height) in N::CONSENSUS_VERSION_HEIGHTS().iter().skip(1) {
412            assert!(*height > previous_height);
413            previous_height = *height;
414            // Ensure that N::CONSENSUS_VERSION returns the expected value.
415            assert_eq!(N::CONSENSUS_VERSION(*height).unwrap(), *version);
416            // Ensure that N::CONSENSUS_HEIGHT returns the expected value.
417            assert_eq!(N::CONSENSUS_HEIGHT(*version).unwrap(), *height);
418        }
419    }
420
421    /// Ensure that version of all consensus-relevant constants are present in the consensus version heights.
422    fn consensus_constants_valid_heights<N: Network>() {
423        for (version, value) in N::MAX_CERTIFICATES.iter() {
424            // Ensure that the height at which an update occurs are present in CONSENSUS_VERSION_HEIGHTS.
425            let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
426            // Double-check that consensus_config_value returns the correct value.
427            assert_eq!(consensus_config_value!(N, MAX_CERTIFICATES, height).unwrap(), *value);
428        }
429        for (version, value) in N::TRANSACTION_SPEND_LIMIT.iter() {
430            // Ensure that the height at which an update occurs are present in CONSENSUS_VERSION_HEIGHTS.
431            let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
432            // Double-check that consensus_config_value returns the correct value.
433            assert_eq!(consensus_config_value!(N, TRANSACTION_SPEND_LIMIT, height).unwrap(), *value);
434        }
435        for (version, value) in N::CREDITS_PER_SECOND_OF_RUNTIME.iter() {
436            // Ensure that the height at which an update occurs are present in CONSENSUS_VERSION_HEIGHTS.
437            let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
438            // Double-check that consensus_config_value returns the correct value.
439            assert_eq!(consensus_config_value!(N, CREDITS_PER_SECOND_OF_RUNTIME, height).unwrap(), *value);
440        }
441        for (version, value) in N::MAX_ARRAY_ELEMENTS.iter() {
442            // Ensure that the height at which an update occurs are present in CONSENSUS_VERSION_HEIGHTS.
443            let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
444            // Double-check that consensus_config_value returns the correct value.
445            assert_eq!(consensus_config_value!(N, MAX_ARRAY_ELEMENTS, height).unwrap(), *value);
446        }
447        for (version, value) in N::MAX_PROGRAM_SIZE.iter() {
448            // Ensure that the height at which an update occurs are present in CONSENSUS_VERSION_HEIGHTS.
449            let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
450            // Double-check that consensus_config_value returns the correct value.
451            assert_eq!(consensus_config_value!(N, MAX_PROGRAM_SIZE, height).unwrap(), *value);
452        }
453        for (version, value) in N::MAX_TRANSACTION_SIZE.iter() {
454            // Ensure that the height at which an update occurs are present in CONSENSUS_VERSION_HEIGHTS.
455            let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
456            // Double-check that consensus_config_value returns the correct value.
457            assert_eq!(consensus_config_value!(N, MAX_TRANSACTION_SIZE, height).unwrap(), *value);
458        }
459        for (version, value) in N::MAX_WRITES.iter() {
460            // Ensure that the height at which an update occurs are present in CONSENSUS_VERSION_HEIGHTS.
461            let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| *c_version == *version).unwrap().1;
462            // Double-check that consensus_config_value returns the correct value.
463            assert_eq!(consensus_config_value!(N, MAX_WRITES, height).unwrap(), *value);
464        }
465        for (version, value) in N::ANCHOR_TIMES.iter() {
466            let height = N::CONSENSUS_VERSION_HEIGHTS().iter().find(|(c_version, _)| c_version == version).unwrap().1;
467            assert_eq!(consensus_config_value!(N, ANCHOR_TIMES, height).unwrap(), *value);
468        }
469    }
470
471    /// Ensure that consensus_config_value returns a valid value for all consensus versions.
472    fn consensus_config_returns_some<N: Network>() {
473        for (_, height) in N::CONSENSUS_VERSION_HEIGHTS().iter() {
474            assert!(consensus_config_value!(N, MAX_CERTIFICATES, *height).is_some());
475            assert!(consensus_config_value!(N, TRANSACTION_SPEND_LIMIT, *height).is_some());
476            assert!(consensus_config_value!(N, CREDITS_PER_SECOND_OF_RUNTIME, *height).is_some());
477            assert!(consensus_config_value!(N, MAX_ARRAY_ELEMENTS, *height).is_some());
478            assert!(consensus_config_value!(N, MAX_PROGRAM_SIZE, *height).is_some());
479            assert!(consensus_config_value!(N, MAX_TRANSACTION_SIZE, *height).is_some());
480            assert!(consensus_config_value!(N, MAX_WRITES, *height).is_some());
481            assert!(consensus_config_value!(N, ANCHOR_TIMES, *height).is_some());
482        }
483    }
484
485    /// Ensure that `MAX_CERTIFICATES` increases and is correctly defined.
486    /// See the constant declaration for an explanation why.
487    fn max_certificates_increasing<N: Network>() {
488        let mut previous_value = N::MAX_CERTIFICATES.first().unwrap().1;
489        for (_, value) in N::MAX_CERTIFICATES.iter().skip(1) {
490            assert!(*value >= previous_value);
491            previous_value = *value;
492        }
493    }
494
495    /// Ensure that `MAX_ARRAY_ELEMENTS` increases and is correctly defined.
496    /// See the constant declaration for an explanation why.
497    fn max_array_elements_increasing<N: Network>() {
498        let mut previous_value = N::MAX_ARRAY_ELEMENTS.first().unwrap().1;
499        for (_, value) in N::MAX_ARRAY_ELEMENTS.iter().skip(1) {
500            assert!(*value >= previous_value);
501            previous_value = *value;
502        }
503    }
504
505    /// Ensure that `MAX_TRANSACTION_SIZE` is at least 28KB greater than `MAX_PROGRAM_SIZE` for all consensus versions.
506    /// This overhead accounts for proofs, signatures, and other transaction metadata.
507    fn transaction_size_exceeds_program_size<N: Network>() {
508        const MIN_OVERHEAD: usize = 28_000; // 28 kB minimum overhead
509
510        for (_, height) in N::CONSENSUS_VERSION_HEIGHTS().iter() {
511            let max_program_size = consensus_config_value!(N, MAX_PROGRAM_SIZE, *height).unwrap();
512            let max_transaction_size = consensus_config_value!(N, MAX_TRANSACTION_SIZE, *height).unwrap();
513
514            assert!(
515                max_transaction_size >= max_program_size + MIN_OVERHEAD,
516                "At height {height}: MAX_TRANSACTION_SIZE ({max_transaction_size}) must be at least {MIN_OVERHEAD} bytes greater than MAX_PROGRAM_SIZE ({max_program_size})"
517            );
518        }
519    }
520
521    /// Ensure that `MAX_PROGRAM_SIZE` and `MAX_TRANSACTION_SIZE` are defined in lockstep:
522    /// the same number of entries, keyed by the same consensus versions in the same order, with the
523    /// transaction size strictly exceeding the program size at every version. The latest transaction
524    /// size must likewise exceed the latest program size, since a transaction must hold a program
525    /// plus its proofs, signatures, and metadata.
526    fn program_and_transaction_size_aligned<N: Network>() {
527        // Ensure both constants define the same number of entries.
528        assert_eq!(
529            N::MAX_PROGRAM_SIZE.len(),
530            N::MAX_TRANSACTION_SIZE.len(),
531            "MAX_PROGRAM_SIZE and MAX_TRANSACTION_SIZE must define the same number of entries"
532        );
533        // Ensure both constants are keyed by the same consensus versions, in the same order, and that
534        // the transaction size exceeds the program size at each corresponding version.
535        for (index, (program_version, program_size)) in N::MAX_PROGRAM_SIZE.iter().enumerate() {
536            let (transaction_version, transaction_size) = &N::MAX_TRANSACTION_SIZE[index];
537            assert_eq!(
538                program_version, transaction_version,
539                "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}"
540            );
541            assert!(
542                transaction_size > program_size,
543                "At consensus version {program_version}: MAX_TRANSACTION_SIZE ({transaction_size}) must be greater than MAX_PROGRAM_SIZE ({program_size})"
544            );
545        }
546        // Ensure the latest transaction size exceeds the latest program size.
547        assert!(
548            N::LATEST_MAX_TRANSACTION_SIZE() > N::LATEST_MAX_PROGRAM_SIZE(),
549            "LATEST_MAX_TRANSACTION_SIZE ({}) must be greater than LATEST_MAX_PROGRAM_SIZE ({})",
550            N::LATEST_MAX_TRANSACTION_SIZE(),
551            N::LATEST_MAX_PROGRAM_SIZE()
552        );
553    }
554
555    /// Ensure that the number of constant definitions is the same across networks.
556    fn constants_equal_length<N1: Network, N2: Network, N3: Network>() {
557        // If we can construct an array, that means the underlying types must be the same.
558        let _ = [N1::CONSENSUS_VERSION_HEIGHTS, N2::CONSENSUS_VERSION_HEIGHTS, N3::CONSENSUS_VERSION_HEIGHTS];
559        let _ = [N1::MAX_CERTIFICATES, N2::MAX_CERTIFICATES, N3::MAX_CERTIFICATES];
560        let _ = [N1::TRANSACTION_SPEND_LIMIT, N2::TRANSACTION_SPEND_LIMIT, N3::TRANSACTION_SPEND_LIMIT];
561        let _ =
562            [N1::CREDITS_PER_SECOND_OF_RUNTIME, N2::CREDITS_PER_SECOND_OF_RUNTIME, N3::CREDITS_PER_SECOND_OF_RUNTIME];
563        let _ = [N1::MAX_ARRAY_ELEMENTS, N2::MAX_ARRAY_ELEMENTS, N3::MAX_ARRAY_ELEMENTS];
564        let _ = [N1::MAX_PROGRAM_SIZE, N2::MAX_PROGRAM_SIZE, N3::MAX_PROGRAM_SIZE];
565        let _ = [N1::MAX_TRANSACTION_SIZE, N2::MAX_TRANSACTION_SIZE, N3::MAX_TRANSACTION_SIZE];
566        let _ = [N1::MAX_WRITES, N2::MAX_WRITES, N3::MAX_WRITES];
567        let _ = [N1::ANCHOR_TIMES, N2::ANCHOR_TIMES, N3::ANCHOR_TIMES];
568    }
569
570    /// Ensure that `LATEST_MAX_*` functions return valid values without panicking.
571    /// These functions use `.expect()` internally, so this test verifies the arrays are non-empty.
572    fn latest_max_functions_are_safe<N: Network>() {
573        // Verify LATEST_MAX_CERTIFICATES returns a positive value.
574        assert!(N::LATEST_MAX_CERTIFICATES() > 0, "LATEST_MAX_CERTIFICATES must be positive");
575        // Verify LATEST_MAX_PROGRAM_SIZE returns a positive value.
576        assert!(N::LATEST_MAX_PROGRAM_SIZE() > 0, "LATEST_MAX_PROGRAM_SIZE must be positive");
577        // Verify LATEST_MAX_TRANSACTION_SIZE returns a positive value.
578        assert!(N::LATEST_MAX_TRANSACTION_SIZE() > 0, "LATEST_MAX_TRANSACTION_SIZE must be positive");
579        // Verify LATEST_MAX_WRITES returns a positive value.
580        assert!(N::LATEST_MAX_WRITES() > 0, "LATEST_MAX_WRITES must be positive");
581    }
582
583    #[test]
584    #[allow(clippy::assertions_on_constants)]
585    fn test_consensus_constants() {
586        consensus_constants_at_genesis::<MainnetV0>();
587        consensus_constants_at_genesis::<TestnetV0>();
588        consensus_constants_at_genesis::<CanaryV0>();
589
590        consensus_versions::<MainnetV0>();
591        consensus_versions::<TestnetV0>();
592        consensus_versions::<CanaryV0>();
593
594        consensus_constants_increasing_heights::<MainnetV0>();
595        consensus_constants_increasing_heights::<TestnetV0>();
596        consensus_constants_increasing_heights::<CanaryV0>();
597
598        consensus_constants_valid_heights::<MainnetV0>();
599        consensus_constants_valid_heights::<TestnetV0>();
600        consensus_constants_valid_heights::<CanaryV0>();
601
602        consensus_config_returns_some::<MainnetV0>();
603        consensus_config_returns_some::<TestnetV0>();
604        consensus_config_returns_some::<CanaryV0>();
605
606        max_certificates_increasing::<MainnetV0>();
607        max_certificates_increasing::<TestnetV0>();
608        max_certificates_increasing::<CanaryV0>();
609
610        max_array_elements_increasing::<MainnetV0>();
611        max_array_elements_increasing::<TestnetV0>();
612        max_array_elements_increasing::<CanaryV0>();
613
614        transaction_size_exceeds_program_size::<MainnetV0>();
615        transaction_size_exceeds_program_size::<TestnetV0>();
616        transaction_size_exceeds_program_size::<CanaryV0>();
617
618        program_and_transaction_size_aligned::<MainnetV0>();
619        program_and_transaction_size_aligned::<TestnetV0>();
620        program_and_transaction_size_aligned::<CanaryV0>();
621
622        latest_max_functions_are_safe::<MainnetV0>();
623        latest_max_functions_are_safe::<TestnetV0>();
624        latest_max_functions_are_safe::<CanaryV0>();
625
626        constants_equal_length::<MainnetV0, TestnetV0, CanaryV0>();
627    }
628
629    /// Ensure (de-)serialization works correctly.
630    #[test]
631    fn test_to_bytes() {
632        let version = ConsensusVersion::V8;
633        let bytes = version.to_bytes_le().unwrap();
634        let result = ConsensusVersion::from_bytes_le(&bytes).unwrap();
635        assert_eq!(result, version);
636
637        let version = ConsensusVersion::latest();
638        let bytes = version.to_bytes_le().unwrap();
639        let result = ConsensusVersion::from_bytes_le(&bytes).unwrap();
640        assert_eq!(result, version);
641
642        let invalid_bytes = u16::MAX.to_bytes_le().unwrap();
643        let result = ConsensusVersion::from_bytes_le(&invalid_bytes);
644        assert!(result.is_err());
645    }
646
647    #[test]
648    fn test_reward_anchor_time() {
649        assert_eq!(MainnetV0::REWARD_ANCHOR_TIME, MainnetV0::ANCHOR_TIMES.first().unwrap().1);
650        assert_eq!(TestnetV0::REWARD_ANCHOR_TIME, TestnetV0::ANCHOR_TIMES.first().unwrap().1);
651        assert_eq!(CanaryV0::REWARD_ANCHOR_TIME, CanaryV0::ANCHOR_TIMES.first().unwrap().1);
652    }
653
654    #[test]
655    fn test_varuna_version_from_consensus() {
656        // First boundary: V4
657        assert_eq!(varuna_version_from_consensus(ConsensusVersion::V3), VarunaVersion::V1);
658        assert_eq!(varuna_version_from_consensus(ConsensusVersion::V4), VarunaVersion::V2);
659    }
660}