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